关于依赖注入:一文搞懂│php-中的-DI-依赖注入

什么是 DI / 依赖注入依赖注入DI 其实实质上是指对类的依赖通过结构器实现 主动注入艰深来说,就是你以后操作一个类,然而这个类的某些办法或者性能不是单单只靠这个类就能实现的,而是要 借助另一个类 的能力实现的最间接的标记就是传参数据为对象的时候。严格来说,你想在一个类中操作另一个类,这两个类之间造成了相互依赖关系,传参的形式叫 注入 依赖注入呈现的起因在未应用依赖注入的时候,php 须要在一个类中应用另一个类的时候,往往都会进行如下操作比方我在 container 类中须要用到 adapter 类,就须要在应用之前进行实例化如果须要用到大量的外部类,这就会造成了 耦合度太高,很容易造成前期的 保护艰难艰深的来讲,也就是 container 脱离不了外部类去工作,这就叫 耦合度太高<?phpclass container{ private $adapter; public function __construct() { $this->adapter = new adapter(); }} 简略的依赖注入下面的代码耦合度太高,导致了 依赖注入 的呈现,次要是为了 解耦合如下案例,咱们只须要将所须要操作的类对象传入即可依赖注入 操作的参数是 对象,而不是一般参数,是不是有更好的了解了然而这样的简略依赖注入,会造成如果你依赖的类很多,你传参的时候会很长,容易凌乱<?phpclass container{ private $adapter; public function __construct(adapter $adapter) { $this->adapter = $adapter; }} 高阶的依赖注入为了解决下面 参数凌乱 的问题,这时候,依赖注入进行了优化通过魔术办法,__get 去设置对象这时候,咱们就能够解决依赖太多,参数凌乱的问题了<?phpclass container{ public $instance = []; public function __set($name, $value) { $this->instance[$name] = $value; }}$container = new container();$container->adapter = new adapter();$container->autofelix = new autofelix(); 依赖注入的利用咱们先定义一个 容器类,次要用来向容器中 注入 你想要操作的类应用的时候,只须要传容器这一个 对象 即可<?phpclass container{ public $instance = []; public function __set($name, $value) { $this->instance[$name] = $value; }}class adapter{ public $name = '我是调度器';}$container = new container();$container->adapter = new adapter();class autofelix{ private $container; public function __construct(container $container) { $this->container = $container; } public function who($class) { return $this->container->instance[$class]->name; }}$autofelix = new autofelix($container);$who = $autofelix->who('adapter');var_dump($who); //我是调度器 依赖注入高阶优化在下面的利用中,咱们 间接 将实例化后的对象注入容器中这样会导致,所有的对象还没有被应用就会被实例化一遍,造成 资源的损耗咱们能够 传入闭包,这样对象就不会被实例化而注入,当你本人须要应用的时候,再去实例化,就能够缩小 服务器资源的损耗 了<?php$container = new container();$container->adapter = new adapter();//高阶优化$container = new container();$container->adapter = function () { return new adapter();};

August 2, 2022 · 1 min · jiezi

依赖注入dependency-injection最通俗的讲解

这篇文章解释了什么是依赖注入(又称控制反转),以及它如何改善定义业务逻辑的代码。 服务和依赖服务可以是您编写的类,也可以是来自导入库的类。例如,它可以是一个 logger 或一个 database connection。因此,您可以编写一个无需任何外部帮助即可单独运行的服务,但也可能您会很快您会达到一个点,即其中一个服务将不得不使用另一个服务的代码的地步。 让我们看一个小的例子 我们将创建一个EmailSender。此类将用于发送电子邮件。它必须在数据库中写入已发送电子邮件的信息,并记录可能发生的错误。 EmailSender 将依赖于其他三项服务:用于发送电子邮件的 SmtpClient,用于与数据库交互的 EmailRepository 以及用于记录错误的 Logger。 通常情况下我们会怎么实现呢? 1.EmailSender 的三个依赖关系被声明为属性。依赖项注入的思想是,EmailSender不应该负责创建其依赖项,它们应该从外部注入。对EmailSender来说,其配置的详细信息应该是未知的。 interface SmtpClientInterface { send(toName: string, toEmail: string, subject: string, message: string)}interface EmailRepositoryInterface { insertEmail(address: string, email: Email, status: string) updateEmailStatus(id: number, status: string)}interface LoggerInterface { error(message: string)}class EmailSender { client: SmtpClientInterface repo: EmailRepositoryInterface logger: LoggerInterface send(user: User, email: Email) { try { this.repo.insertEmail(user.email, email, "sending") this.client.send(user.email, user.name, email.subject, email.message) this.repo.updateEmailStatus(email.id, "sent") } catch(e) { this.logger.error(e.toString()) } }}2.使用 setter,您可以在EmailSender上添加setSmtpClient(),setEmailRepository()和setLogger()方法。 ...

September 30, 2019 · 8 min · jiezi

日常划水短信验证码开发实例

前言我一生的文章都会放在这里,我的博客,我希望每一行代码,每一段文字都能帮助你。https://github.com/CrazyCodes...大家好,我是CrazyCodes,在日常开发中有没有遇到过发送短信验证码的接口需要开发?你是如何处理短信验证码发送的呢?本篇我分享下短信验证码发送的设计。 初学者以聚合数据为例,初学者会酱紫做 百度 找到一串既熟悉又陌生的代码 (咋整也记不住的代码) //初始化$curl = curl_init();//设置抓取的urlcurl_setopt($curl, CURLOPT_URL, 'http://www.baidu.com');//设置头文件的信息作为数据流输出curl_setopt($curl, CURLOPT_HEADER, 1);//设置获取的信息以文件流的形式返回,而不是直接输出。curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);//设置post方式提交curl_setopt($curl, CURLOPT_POST, 1);//设置post数据$post_data = array( "username" => "coder", "password" => "12345" );curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);//执行命令$data = curl_exec($curl);//关闭URL请求curl_close($curl);//显示获得的数据print_r($data);官方也给出了一段维护性略差的代码 <?php/* ***聚合数据(JUHE.CN)短信API服务接口PHP请求示例源码 ***DATE:2015-05-25*/header('content-type:text/html;charset=utf-8'); $sendUrl = 'http://v.juhe.cn/sms/send'; //短信接口的URL $smsConf = array( 'key' => '*****************', //您申请的APPKEY 'mobile' => '1891351****', //接受短信的用户手机号码 'tpl_id' => '111', //您申请的短信模板ID,根据实际情况修改 'tpl_value' =>'#code#=1234&#company#=聚合数据' //您设置的模板变量,根据实际情况修改); $content = juhecurl($sendUrl,$smsConf,1); //请求发送短信 if($content){ $result = json_decode($content,true); $error_code = $result['error_code']; if($error_code == 0){ //状态为0,说明短信发送成功 echo "短信发送成功,短信ID:".$result['result']['sid']; }else{ //状态非0,说明失败 $msg = $result['reason']; echo "短信发送失败(".$error_code."):".$msg; }}else{ //返回内容异常,以下可根据业务逻辑自行修改 echo "请求发送短信失败";} /** * 请求接口返回内容 * @param string $url [请求的URL地址] * @param string $params [请求的参数] * @param int $ipost [是否采用POST形式] * @return string */function juhecurl($url,$params=false,$ispost=0){ $httpInfo = array(); $ch = curl_init(); curl_setopt( $ch, CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_1 ); curl_setopt( $ch, CURLOPT_USERAGENT , 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.172 Safari/537.22' ); curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT , 30 ); curl_setopt( $ch, CURLOPT_TIMEOUT , 30); curl_setopt( $ch, CURLOPT_RETURNTRANSFER , true ); if( $ispost ) { curl_setopt( $ch , CURLOPT_POST , true ); curl_setopt( $ch , CURLOPT_POSTFIELDS , $params ); curl_setopt( $ch , CURLOPT_URL , $url ); } else { if($params){ curl_setopt( $ch , CURLOPT_URL , $url.'?'.$params ); }else{ curl_setopt( $ch , CURLOPT_URL , $url); } } $response = curl_exec( $ch ); if ($response === FALSE) { //echo "cURL Error: " . curl_error($ch); return false; } $httpCode = curl_getinfo( $ch , CURLINFO_HTTP_CODE ); $httpInfo = array_merge( $httpInfo , curl_getinfo( $ch ) ); curl_close( $ch ); return $response;}这样看,“也不是不好,就是想再改改,至于改什么,不知道,就是想再改改” ...

July 2, 2019 · 2 min · jiezi

使用-TypeScript-和依赖注入实现一个聊天机器人

翻译:疯狂的技术宅原文:https://www.toptal.com/typesc...本文首发微信公众号:前端先锋欢迎关注,每天都给你推送新鲜的前端技术文章 类型和可测试代码是避免错误的两种最有效方法,尤其是代码随会时间而变化。我们可以分别通过利用 TypeScript 和依赖注入(DI)将这两种技术应用于JavaScript开发。 在本 TypeScript 教程中,除编译以外,我们不会直接介绍 TypeScript 的基础知识。相反,我们将会演示 TypeScript 最佳实践,因为我们将介绍如何从头开始制作 Discord bot、连接测试和 DI,以及创建示例服务。我们将会使用: Node.jsTypeScriptDiscord.js,Discord API的包装器InversifyJS,一个依赖注入框架测试库:Mocha,Chai和ts-mockitoMongoose和MongoDB,以编写集成测试设置 Node.js 项目首先,让我们创建一个名为 typescript-bot 的新目录。然后输入并通过运行以下命令创建一个新的 Node.js 项目: npm init注意:你也可以用 yarn,但为了简洁起见,我们用了 npm。 这将会打开一个交互式向导,对 package.json 文件进行配置。对于所有问题,你只需简单的按回车键(或者如果需要,可以提供一些信息)。然后,安装我们的依赖项和 dev 依赖项(这些是测试所需的)。 npm i --save typescript discord.js inversify dotenv @types/node reflect-metadatanpm i --save-dev chai mocha ts-mockito ts-node @types/chai @types/mocha然后,将package.json 中生成的 `scripts 部分替换为: "scripts": { "start": "node src/index.js", "watch": "tsc -p tsconfig.json -w", "test": "mocha -r ts-node/register \"tests/**/*.spec.ts\""},为了能够递归地查找文件,需要在tests/**/*.spec.ts周围加上双引号。 (注意:在 Windows 下的语法可能会有所不同。) ...

May 29, 2019 · 5 min · jiezi

实现PHP的自动依赖注入容器-EasyDI容器

[TOC] Last-Modified: 2019年5月10日16:15:36 1. 前言在看了一些容器实现代码后, 就手痒想要自己实现一个, 因此也就有了本文接下来的内容. 首先, 实现的容器需要具有以下几点特性: 符合PSR-11标准实现基本的容器存储功能具有自动依赖解决能力本项目代码由GitHub托管 可使用Composer进行安装 composer require yjx/easy-di 2. 项目代码结构|-src |-Exception |-InstantiateException.php (实现Psr\Container\ContainerExceptionInterface) |-InvalidArgumentException.php (实现Psr\Container\ContainerExceptionInterface) |-UnknownIdentifierException.php (实现Psr\Container\NotFoundExceptionInterface) |-Container.php # 容器|-tests |-UnitTest |-ContainerTest.php3. 容器完整代码代码版本 v1.0.1<?phpnamespace EasyDI;use EasyDI\Exception\UnknownIdentifierException;use EasyDI\Exception\InvalidArgumentException;use EasyDI\Exception\InstantiateException;use Psr\Container\ContainerExceptionInterface;use Psr\Container\ContainerInterface;use Psr\Container\NotFoundExceptionInterface;class Container implements ContainerInterface{ /** * 保存 参数, 已实例化的对象 * @var array */ private $instance = []; private $shared = []; private $raw = []; private $params = []; /** * 保存 定义的 工厂等 * @var array */ private $binding = []; public function __construct() { $this->raw(ContainerInterface::class, $this); $this->raw(self::class, $this); } /** * Finds an entry of the container by its identifier and returns it. * * @param string $id Identifier of the entry to look for. * * @throws NotFoundExceptionInterface No entry was found for **this** identifier. * @throws ContainerExceptionInterface Error while retrieving the entry. * * @return mixed Entry. */ public function get($id, $parameters = [], $shared=false) { if (!$this->has($id)) { throw new UnknownIdentifierException($id); } if (array_key_exists($id, $this->raw)) { return $this->raw[$id]; } if (array_key_exists($id, $this->instance)) { return $this->instance[$id]; } $define = array_key_exists($id, $this->binding) ? $this->binding[$id] : $id; if ($define instanceof \Closure) { $instance = $this->call($define, $parameters); } else { // string $class = $define; $params = (empty($this->params[$id]) ? [] : $this->params[$id]) + $parameters; // Case: "\\xxx\\xxx"=>"abc" if ($id !== $class && $this->has($class)) { $instance = $this->get($class, $params); } else { $dependencies = $this->getClassDependencies($class, $params); if (is_null($dependencies) || empty($dependencies)) { $instance = $this->getReflectionClass($class)->newInstanceWithoutConstructor(); } else { $instance = $this->getReflectionClass($class)->newInstanceArgs($dependencies); } } } if ($shared || (isset($this->shared[$id]) && $this->shared[$id])) { $this->instance[$id] = $instance; } return $instance; } /** * @param callback $function * @param array $parameters * @return mixed * @throws InvalidArgumentException 传入错误的参数 * @throws InstantiateException */ public function call($function, $parameters=[], $shared=false) { //参考 http://php.net/manual/zh/function.call-user-func-array.php#121292 实现解析$function $class = null; $method = null; $object = null; // Case1: function() {} if ($function instanceof \Closure) { $method = $function; } elseif (is_array($function) && count($function)==2) { // Case2: [$object, $methodName] if (is_object($function[0])) { $object = $function[0]; $class = get_class($object); } elseif (is_string($function[0])) { // Case3: [$className, $staticMethodName] $class = $function[0]; } if (is_string($function[1])) { $method = $function[1]; } } elseif (is_string($function) && strpos($function, '::') !== false) { // Case4: "class::staticMethod" list($class, $method) = explode('::', $function); } elseif (is_scalar($function)) { // Case5: "functionName" $method = $function; } else { throw new InvalidArgumentException("Case not allowed! Invalid Data supplied!"); } try { if (!is_null($class) && !is_null($method)) { $reflectionFunc = $this->getReflectionMethod($class, $method); } elseif (!is_null($method)) { $reflectionFunc = $this->getReflectionFunction($method); } else { throw new InvalidArgumentException("class:$class method:$method"); } } catch (\ReflectionException $e) {// var_dump($e->getTraceAsString()); throw new InvalidArgumentException("class:$class method:$method", 0, $e); } $parameters = $this->getFuncDependencies($reflectionFunc, $parameters); if ($reflectionFunc instanceof \ReflectionFunction) { return $reflectionFunc->invokeArgs($parameters); } elseif ($reflectionFunc->isStatic()) { return $reflectionFunc->invokeArgs(null, $parameters); } elseif (!empty($object)) { return $reflectionFunc->invokeArgs($object, $parameters); } elseif (!is_null($class) && $this->has($class)) { $object = $this->get($class, [], $shared); return $reflectionFunc->invokeArgs($object, $parameters); } throw new InvalidArgumentException("class:$class method:$method, unable to invoke."); } /** * @param $class * @param array $parameters * @throws \ReflectionException */ protected function getClassDependencies($class, $parameters=[]) { // 获取类的反射类 $reflectionClass = $this->getReflectionClass($class); if (!$reflectionClass->isInstantiable()) { throw new InstantiateException($class); } // 获取构造函数反射类 $reflectionMethod = $reflectionClass->getConstructor(); if (is_null($reflectionMethod)) { return null; } return $this->getFuncDependencies($reflectionMethod, $parameters, $class); } protected function getFuncDependencies(\ReflectionFunctionAbstract $reflectionFunc, $parameters=[], $class="") { $params = []; // 获取构造函数参数的反射类 $reflectionParameterArr = $reflectionFunc->getParameters(); foreach ($reflectionParameterArr as $reflectionParameter) { $paramName = $reflectionParameter->getName(); $paramPos = $reflectionParameter->getPosition(); $paramClass = $reflectionParameter->getClass(); $context = ['pos'=>$paramPos, 'name'=>$paramName, 'class'=>$paramClass, 'from_class'=>$class]; // 优先考虑 $parameters if (isset($parameters[$paramName]) || isset($parameters[$paramPos])) { $tmpParam = isset($parameters[$paramName]) ? $parameters[$paramName] : $parameters[$paramPos]; if (gettype($tmpParam) == 'object' && !is_a($tmpParam, $paramClass->getName())) { throw new InstantiateException($class."::".$reflectionFunc->getName(), $parameters + ['__context'=>$context, 'tmpParam'=>get_class($tmpParam)]); } $params[] = $tmpParam;// $params[] = isset($parameters[$paramName]) ? $parameters[$paramName] : $parameters[$pos]; } elseif (empty($paramClass)) { // 若参数不是class类型 // 优先使用默认值, 只能用于判断用户定义的函数/方法, 对系统定义的函数/方法无效, 也同样无法获取默认值 if ($reflectionParameter->isDefaultValueAvailable()) { $params[] = $reflectionParameter->getDefaultValue(); } elseif ($reflectionFunc->isUserDefined()) { throw new InstantiateException("UserDefined. ".$class."::".$reflectionFunc->getName()); } elseif ($reflectionParameter->isOptional()) { break; } else { throw new InstantiateException("SystemDefined. ".$class."::".$reflectionFunc->getName()); } } else { // 参数是类类型, 优先考虑解析 if ($this->has($paramClass->getName())) { $params[] = $this->get($paramClass->getName()); } elseif ($reflectionParameter->allowsNull()) { $params[] = null; } else { throw new InstantiateException($class."::".$reflectionFunc->getName()." {$paramClass->getName()} "); } } } return $params; } protected function getReflectionClass($class, $ignoreException=false) { static $cache = []; if (array_key_exists($class, $cache)) { return $cache[$class]; } try { $reflectionClass = new \ReflectionClass($class); } catch (\Exception $e) { if (!$ignoreException) { throw new InstantiateException($class, 0, $e); } $reflectionClass = null; } return $cache[$class] = $reflectionClass; } protected function getReflectionMethod($class, $name) { static $cache = []; if (is_object($class)) { $class = get_class($class); } if (array_key_exists($class, $cache) && array_key_exists($name, $cache[$class])) { return $cache[$class][$name]; } $reflectionFunc = new \ReflectionMethod($class, $name); return $cache[$class][$name] = $reflectionFunc; } protected function getReflectionFunction($name) { static $closureCache; static $cache = []; $isClosure = is_object($name) && $name instanceof \Closure; $isString = is_string($name); if (!$isString && !$isClosure) { throw new InvalidArgumentException("$name can't get reflection func."); } if ($isString && array_key_exists($name, $cache)) { return $cache[$name]; } if ($isClosure) { if (is_null($closureCache)) { $closureCache = new \SplObjectStorage(); } if ($closureCache->contains($name)) { return $closureCache[$name]; } } $reflectionFunc = new \ReflectionFunction($name); if ($isString) { $cache[$name] = $reflectionFunc; } if ($isClosure) { $closureCache->attach($name, $reflectionFunc); } return $reflectionFunc; } /** * Returns true if the container can return an entry for the given identifier. * Returns false otherwise. * * `has($id)` returning true does not mean that `get($id)` will not throw an exception. * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`. * * @param string $id Identifier of the entry to look for. * * @return bool */ public function has($id) { $has = array_key_exists($id, $this->binding) || array_key_exists($id, $this->raw) || array_key_exists($id, $this->instance); if (!$has) { $reflectionClass = $this->getReflectionClass($id, true); if (!empty($reflectionClass)) { $has = true; } } return $has; } public function needResolve($id) { return !(array_key_exists($id, $this->raw) && (array_key_exists($id, $this->instance) && $this->shared[$id])); } public function keys() { return array_unique(array_merge(array_keys($this->raw), array_keys($this->binding), array_keys($this->instance))); } public function instanceKeys() { return array_unique(array_keys($this->instance)); } public function unset($id) { unset($this->shared[$id], $this->binding[$id], $this->raw[$id], $this->instance[$id], $this->params[$id]); } public function singleton($id, $value, $params=[]) { $this->set($id, $value, $params, true); } /** * 想好定义数组, 和定义普通项 * @param $id * @param $value * @param bool $shared */ public function set($id, $value, $params=[], $shared=false) { if (is_object($value) && !($value instanceof \Closure)) { $this->raw($id, $value); return; } elseif ($value instanceof \Closure) { // no content } elseif (is_array($value)) { $value = [ 'class' => $id, 'params' => [], 'shared' => $shared ] + $value; if (!isset($value['class'])) { $value['class'] = $id; } $params = $value['params'] + $params; $shared = $value['shared']; $value = $value['class']; } elseif (is_string($value)) { // no content } $this->binding[$id] = $value; $this->shared[$id] = $shared; $this->params[$id] = $params; } public function raw($id, $value) { $this->unset($id); $this->raw[$id] = $value; } public function batchRaw(array $data) { foreach ($data as $key=>$value) { $this->raw($key, $value); } } public function batchSet(array $data, $shared=false) { foreach ($data as $key=>$value) { $this->set($key, $value, $shared); } }}3.1 容器主要提供方法容器提供方法: ...

May 10, 2019 · 7 min · jiezi

搞懂依赖注入, 用 PHP 手写简易 IOC 容器

前言好的设计会提高程序的可复用性和可维护性,也间接的提高了开发人员的生产力。今天,我们就来说一下在很多框架中都使用的依赖注入。 一些概念要搞清楚什么是依赖注入如何依赖注入,首先我们要明确一些概念。 DIP (Dependence Inversion Principle) 依赖倒置原则:程序要依赖于抽象接口,不要依赖于具体实现。 IOC (Inversion of Control) 控制反转:遵循依赖倒置原则的一种代码设计方案,依赖的创建 (控制) 由主动变为被动 (反转)。 DI (Dependency Injection) 依赖注入:控制反转的一种具体实现方法。通过参数的方式从外部传入依赖,将依赖的创建由主动变为被动 (实现了控制反转)。 光说理论有点不好理解,我们用代码举个例子。 首先,我们看依赖没有倒置时的一段代码: class Controller{ protected $service; public function __construct() { // 主动创建依赖 $this->service = new Service(12, 13); } }class Service{ protected $model; protected $count; public function __construct($param1, $param2) { $this->count = $param1 + $param2; // 主动创建依赖 $this->model = new Model('test_table'); }}class Model{ protected $table; public function __construct($table) { $this->table = $table; }}$controller = new Controller;上述代码的依赖关系是 Controller 依赖 Service,Service 依赖 Model。从控制的角度来看,Controller 主动创建依赖 Service,Service 主动创建依赖 Model。依赖是由需求方内部产生的,需求方需要关心依赖的具体实现。这样的设计使代码耦合性变高,每次底层发生改变(如参数变动),顶层就必须修改代码。 ...

April 22, 2019 · 4 min · jiezi

如何实现Laravel的服务容器

如何实现服务容器(Ioc Container)1. 容器的本质服务容器本身就是一个数组,键名就是服务名,值就是服务。服务可以是一个原始值,也可以是一个对象,可以说是任意数据。服务名可以是自定义名,也可以是对象的类名,也可以是接口名。// 服务容器$container = [ // 原始值 ’text’ => ‘这是一个字符串’, // 自定义服务名 ‘customName’ => new StdClass(), // 使用类名作为服务名 ‘StdClass’ => new StdClass(), // 使用接口名作为服务名 ‘Namespace\StdClassInterface’ => new StdClass(),];// ———– ↓↓↓↓示例代码↓↓↓↓ ———– //// 绑定服务到容器$container[‘standard’] = new StdClass();// 获取服务$standard = $container[‘standard’];var_dump($standard);2. 封装成类为了方便维护,我们把上面的数组封装到类里面。$instances还是上面的容器数组。我们增加两个方法,instance用来绑定服务,get用来从容器中获取服务。class BaseContainer{ // 已绑定的服务 protected $instances = []; // 绑定服务 public function instance($name, $instance) { $this->instances[$name] = $instance; } // 获取服务 public function get($name) { return isset($this->instances[$name]) ? $this->instances[$name] : null; }}// ———– ↓↓↓↓示例代码↓↓↓↓ ———– //$container = new BaseContainer();// 绑定服务$container->instance(‘StdClass’, new StdClass());// 获取服务$stdClass = $container->get(‘StdClass’);var_dump($stdClass);3. 按需实例化现在我们在绑定一个对象服务的时候,就必须要先把类实例化,如果绑定的服务没有被用到,那么类就会白白实例化,造成性能浪费。为了解决这个问题,我们增加一个bind函数,它支持绑定一个回调函数,在回调函数中实例化类。这样一来,我们只有在使用服务时,才回调这个函数,这样就实现了按需实例化。这时候,我们获取服务时,就不只是从数组中拿到服务并返回了,还需要判断如果是回调函数,就要执行回调函数。所以我们把get方法的名字改成make。意思就是生产一个服务,这个服务可以是已绑定的服务,也可以是已绑定的回调函数,也可以是一个类名,如果是类名,我们就直接实例化该类并返回。然后,我们增加一个新数组$bindings,用来存储绑定的回调函数。然后我们把bind方法改一下,判断下$instance如果是一个回调函数,就放到$bindings数组,否则就用make方法实例化类。class DeferContainer extend BaseContainer{ // 已绑定的回调函数 protected $bindings = []; // 绑定服务 public function bind($name, $instance) { if ($instance instanceof Closure) { // 如果$instance是一个回调函数,就绑定到bindings。 $this->bindings[$name] = $instance; } else { // 调用make方法,创建实例 $this->instances[$name] = $this->make($name); } } // 获取服务 public function make($name) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 执行回调函数并返回 $instance = call_user_func($this->bindings[$name]); } else { // 还没有绑定到容器中,直接new. $instance = new $name(); } return $instance; }}// ———– ↓↓↓↓示例代码↓↓↓↓ ———– //$container = new DeferContainer();// 绑定服务$container->bind(‘StdClass’, function () { echo “我被执行了\n”; return new StdClass();});// 获取服务$stdClass = $container->make(‘StdClass’);var_dump($stdClass);StdClass这个服务绑定的是一个回调函数,在回调函数中才会真正的实例化类。如果没有用到这个服务,那回调函数就不会被执行,类也不会被实例化。4. 单例从上面的代码中可以看出,每次调用make方法时,都会执行一次回调函数,并返回一个新的类实例。但是在某些情况下,我们希望这个实例是一个单例,无论make多少次,只实例化一次。这时候,我们给bind方法增加第三个参数$shared,用来标记是否是单例,默认不是单例。然后把回调函数和这个标记都存到$bindings数组里。为了方便绑定单例服务,再增加一个新的方法singleton,它直接调用bind,并且$shared参数强制为true。对于make方法,我们也要做修改。在执行$bindings里的回调函数以后,做一个判断,如果之前绑定时标记的shared是true,就把回调函数返回的结果存储到$instances里。由于我们是先从$instances里找服务,所以这样下次再make的时候就会直接返回,而不会再次执行回调函数。这样就实现了单例的绑定。class SingletonContainer extends DeferContainer{ // 绑定服务 public function bind($name, $instance, $shared = false) { if ($instance instanceof Closure) { // 如果$instance是一个回调函数,就绑定到bindings。 $this->bindings[$name] = [ ‘callback’ => $instance, // 标记是否单例 ‘shared’ => $shared ]; } else { // 调用make方法,创建实例 $this->instances[$name] = $this->make($name); } } // 绑定一个单例 public function singleton($name, $instance) { $this->bind($name, $instance, true); } // 获取服务 public function make($name) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 执行回调函数并返回 $instance = call_user_func($this->bindings[$name][‘callback’]); if ($this->bindings[$name][‘shared’]) { // 标记为单例时,存储到服务中 $this->instances[$name] = $instance; } } else { // 还没有绑定到容器中,直接new. $instance = new $name(); } return $instance; }}// ———– ↓↓↓↓示例代码↓↓↓↓ ———– //$container = new SingletonContainer();// 绑定服务$container->singleton(‘anonymous’, function () { return new class { public function __construct() { echo “我被实例化了\n”; } };});// 无论make多少次,只会实例化一次$container->make(‘anonymous’);$container->make(‘anonymous’);// 获取服务$anonymous = $container->make(‘anonymous’);var_dump($anonymous)上面的代码用singleton绑定了一个名为anonymous的服务,回调函数里返回了一个匿名类的实例。这个匿名类在被实例化时会输出一段文字。无论我们make多少次anonymous,这个回调函数只会被执行一次,匿名类也只会被实例化一次。5. 自动注入自动注入是Ioc容器的核心,没有自动注入就无法做到控制反转。自动注入就是指,在实例化一个类时,用反射类来获取__construct所需要的参数,然后根据参数的类型,从容器中找到已绑定的服务。我们只要有了__construct方法所需的所有参数,就能自动实例化该类,实现自动注入。现在,我们增加一个build方法,它只接收一个参数,就是类名。build方法会用反射类来获取__construct方法所需要的参数,然后返回实例化结果。另外一点就是,我们之前在调用make方法时,如果传的是一个未绑定的类,我们直接new了这个类。现在我们把未绑定的类交给build方法来构建,因为它支持自动注入。class InjectionContainer extends SingletonContainer{ // 获取服务 public function make($name) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 执行回调函数并返回 $instance = call_user_func($this->bindings[$name][‘callback’]); if ($this->bindings[$name][‘shared’]) { // 标记为单例时,存储到服务中 $this->instances[$name] = $instance; } } else { // 使用build方法构建此类 $instance = $this->build($name); } return $instance; } // 构建一个类,并自动注入服务 public function build($class) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 没有构造函数,直接new return new $class(); } $dependencies = []; // 获取构造函数所需的参数 foreach ($constructor->getParameters() as $dependency) { if (is_null($dependency->getClass())) { // 参数类型不是类时,无法从容器中获取依赖 if ($dependency->isDefaultValueAvailable()) { // 查找参数的默认值,如果有就使用默认值 $dependencies[] = $dependency->getDefaultValue(); } else { // 无法提供类所依赖的参数 throw new Exception(‘找不到依赖参数:’ . $dependency->getName()); } } else { // 参数类型是类时,就用make方法构建该类 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); }}// ———– ↓↓↓↓示例代码↓↓↓↓ ———– //class Redis{}class Cache{ protected $redis; // 构造函数中依赖Redis服务 public function __construct(Redis $redis) { $this->redis = $redis; }}$container = new InjectionContainer();// 绑定Redis服务$container->singleton(Redis::class, function () { return new Redis();});// 构建Cache类$cache = $container->make(Cache::class);var_dump($cache);6. 自定义依赖参数现在有个问题,如果类依赖的参数不是类或接口,只是一个普通变量,这时候就无法从容器中获取依赖参数了,也就无法实例化类了。那么接下来我们就支持一个新功能,在调用make方法时,支持传第二个参数$parameters,这是一个数组,无法从容器中获取的依赖,就从这个数组中找。当然,make方法是用不到这个参数的,因为它不负责实例化类,它直接传给build方法。在build方法寻找依赖的参数时,就先从$parameters中找。这样就实现了自定义依赖参数。需要注意的一点是,build方法是按照参数的名字来找依赖的,所以parameters中的键名也必须跟__construct中参数名一致。class ParametersContainer extends InjectionContainer{ // 获取服务 public function make($name, array $parameters = []) { if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 执行回调函数并返回 $instance = call_user_func($this->bindings[$name][‘callback’]); if ($this->bindings[$name][‘shared’]) { // 标记为单例时,存储到服务中 $this->instances[$name] = $instance; } } else { // 使用build方法构建此类 $instance = $this->build($name, $parameters); } return $instance; } // 构建一个类,并自动注入服务 public function build($class, array $parameters = []) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 没有构造函数,直接new return new $class(); } $dependencies = []; // 获取构造函数所需的参数 foreach ($constructor->getParameters() as $dependency) { if (isset($parameters[$dependency->getName()])) { // 先从自定义参数中查找 $dependencies[] = $parameters[$dependency->getName()]; continue; } if (is_null($dependency->getClass())) { // 参数类型不是类或接口时,无法从容器中获取依赖 if ($dependency->isDefaultValueAvailable()) { // 查找默认值,如果有就使用默认值 $dependencies[] = $dependency->getDefaultValue(); } else { // 无法提供类所依赖的参数 throw new Exception(‘找不到依赖参数:’ . $dependency->getName()); } } else { // 参数类型是类时,就用make方法构建该类 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); }}// ———– ↓↓↓↓示例代码↓↓↓↓ ———– //class Redis{}class Cache{ protected $redis; protected $name; protected $default; // 构造函数中依赖Redis服务和name参数,name的类型不是类,无法从容器中查找 public function __construct(Redis $redis, $name, $default = ‘默认值’) { $this->redis = $redis; $this->name = $name; $this->default = $default; }}$container = new ParametersContainer();// 绑定Redis服务$container->singleton(Redis::class, function () { return new Redis();});// 构建Cache类$cache = $container->make(Cache::class, [’name’ => ’test’]);var_dump($cache);提示:实际上,Laravel容器的build方法并没有第二个参数$parameters,它是用类属性来维护自定义参数。原理都是一样的,只是实现方式不一样。这里为了方便理解,不引入过多概念。7. 服务别名别名可以理解成小名、外号。服务别名就是给已绑定的服务设置一些外号,使我们通过外号也能找到该服务。这个就比较简单了,我们增加一个新的数组$aliases,用来存储别名。再增加一个方法alias,用来让外部注册别名。唯一需要我们修改的地方,就是在make时,要先从$aliases中找到真实的服务名。class AliasContainer extends ParametersContainer{ // 服务别名 protected $aliases = []; // 给服务绑定一个别名 public function alias($alias, $name) { $this->aliases[$alias] = $name; } // 获取服务 public function make($name, array $parameters = []) { // 先用别名查找真实服务名 $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; return parent::make($name, $parameters); }}// ———– ↓↓↓↓示例代码↓↓↓↓ ———– //$container = new AliasContainer();// 绑定服务$container->instance(’text’, ‘这是一个字符串’);// 给服务注册别名$container->alias(‘string’, ’text’);$container->alias(‘content’, ’text’);var_dump($container->make(‘string’));var_dump($container->make(‘content’));8. 扩展绑定有时候我们需要给已绑定的服务做一个包装,这时候就用到扩展绑定了。我们先看一个实际的用法,理解它的作用后,才看它是如何实现的。// 绑定日志服务$container->singleton(’log’, new Log());// 对已绑定的服务再次包装$container->extend(’log’, function(Log $log){ // 返回了一个新服务 return new RedisLog($log);});现在我们看它是如何实现的。增加一个$extenders数组,用来存放扩展器。再增加一个extend方法,用来注册扩展器。然后在make方法返回$instance之前,按顺序依次调用之前注册的扩展器。class ExtendContainer extends AliasContainer{ // 存放扩展器的数组 protected $extenders = []; // 给服务绑定扩展器 public function extend($name, $extender) { if (isset($this->instances[$name])) { // 已经实例化的服务,直接调用扩展器 $this->instances[$name] = $extender($this->instances[$name]); } else { $this->extenders[$name][] = $extender; } } // 获取服务 public function make($name, array $parameters = []) { $instance = parent::make($name, $parameters); if (isset($this->extenders[$name])) { // 调用扩展器 foreach ($this->extenders[$name] as $extender) { $instance = $extender($instance); } } return $instance; }}// ———– ↓↓↓↓示例代码↓↓↓↓ ———– //class Redis{ public $name; public function __construct($name = ‘default’) { $this->name = $name; } public function setName($name) { $this->name = $name; }}$container = new ExtendContainer();// 绑定Redis服务$container->singleton(Redis::class, function () { return new Redis();});// 给Redis服务绑定一个扩展器$container->extend(Redis::class, function (Redis $redis) { $redis->setName(‘扩展器’); return $redis;});$redis = $container->make(Redis::class);var_dump($redis->name);9. 上下文绑定有时侯我们可能有两个类使用同一个接口,但希望在每个类中注入不同的实现,例如两个控制器,分别为它们注入不同的Log服务。class ApiController{ public function __construct(Log $log) { }}class WebController{ public function __construct(Log $log) { }}最终我们要用以下方式实现:// 当ApiController依赖Log时,给它一个RedisLog$container->addContextualBinding(‘ApiController’,‘Log’,new RedisLog());// 当WebController依赖Log时,给它一个FileLog$container->addContextualBinding(‘WebController’,‘Log’,new FileLog());为了更直观更方便更语义化的使用,我们把这个过程改成链式操作:$container->when(‘ApiController’) ->needs(‘Log’) ->give(new RedisLog());我们增加一个$context数组,用来存储上下文。同时增加一个addContextualBinding方法,用来注册上下文绑定。以ApiController为例,$context的真实模样是:$context[‘ApiController’][‘Log’] = new RedisLog();然后build方法实例化类时,先从上下文中查找依赖参数,就实现了上下文绑定。接下来,看看链式操作是如何实现的。首先定义一个类Context,这个类有两个方法,needs和give。然后在容器中,增加一个when方法,它返回一个Context对象。在Context对象的give方法中,我们已经具备了注册上下文所需要的所有参数,所以就可以在give方法中调用addContextualBinding来注册上下文了。class ContextContainer extends ExtendContainer{ // 依赖上下文 protected $context = []; // 构建一个类,并自动注入服务 public function build($class, array $parameters = []) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 没有构造函数,直接new return new $class(); } $dependencies = []; // 获取构造函数所需的参数 foreach ($constructor->getParameters() as $dependency) { if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) { // 先从上下文中查找 $dependencies[] = $this->context[$class][$dependency->getName()]; continue; } if (isset($parameters[$dependency->getName()])) { // 从自定义参数中查找 $dependencies[] = $parameters[$dependency->getName()]; continue; } if (is_null($dependency->getClass())) { // 参数类型不是类或接口时,无法从容器中获取依赖 if ($dependency->isDefaultValueAvailable()) { // 查找默认值,如果有就使用默认值 $dependencies[] = $dependency->getDefaultValue(); } else { // 无法提供类所依赖的参数 throw new Exception(‘找不到依赖参数:’ . $dependency->getName()); } } else { // 参数类型是一个类时,就用make方法构建该类 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); } // 绑定上下文 public function addContextualBinding($when, $needs, $give) { $this->context[$when][$needs] = $give; } // 支持链式方式绑定上下文 public function when($when) { return new Context($when, $this); }}class Context{ protected $when; protected $needs; protected $container; public function __construct($when, ContextContainer $container) { $this->when = $when; $this->container = $container; } public function needs($needs) { $this->needs = $needs; return $this; } public function give($give) { // 调用容器绑定依赖上下文 $this->container->addContextualBinding($this->when, $this->needs, $give); }}// ———– ↓↓↓↓示例代码↓↓↓↓ ———– //class Dog{ public $name; public function __construct($name) { $this->name = $name; }}class Cat{ public $name; public function __construct($name) { $this->name = $name; }}$container = new ContextContainer();// 给Dog类设置上下文绑定$container->when(Dog::class) ->needs(’name’) ->give(‘小狗’);// 给Cat类设置上下文绑定$container->when(Cat::class) ->needs(’name’) ->give(‘小猫’);$dog = $container->make(Dog::class);$cat = $container->make(Cat::class);var_dump(‘Dog:’ . $dog->name);var_dump(‘Cat:’ . $cat->name);10. 完整代码class Container{ // 已绑定的服务 protected $instances = []; // 已绑定的回调函数 protected $bindings = []; // 服务别名 protected $aliases = []; // 存放扩展器的数组 protected $extenders = []; // 依赖上下文 protected $context = []; // 绑定服务实例 public function instance($name, $instance) { $this->instances[$name] = $instance; } // 绑定服务 public function bind($name, $instance, $shared = false) { if ($instance instanceof Closure) { // 如果$instance是一个回调函数,就绑定到bindings。 $this->bindings[$name] = [ ‘callback’ => $instance, // 标记是否单例 ‘shared’ => $shared ]; } else { // 调用make方法,创建实例 $this->instances[$name] = $this->make($name); } } // 绑定一个单例 public function singleton($name, $instance) { $this->bind($name, $instance, true); } // 给服务绑定一个别名 public function alias($alias, $name) { $this->aliases[$alias] = $name; } // 给服务绑定扩展器 public function extend($name, $extender) { if (isset($this->instances[$name])) { // 已经实例化的服务,直接调用扩展器 $this->instances[$name] = $extender($this->instances[$name]); } else { $this->extenders[$name][] = $extender; } } // 获取服务 public function make($name, array $parameters = []) { // 先用别名查找真实服务名 $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; if (isset($this->instances[$name])) { return $this->instances[$name]; } if (isset($this->bindings[$name])) { // 执行回调函数并返回 $instance = call_user_func($this->bindings[$name][‘callback’]); if ($this->bindings[$name][‘shared’]) { // 标记为单例时,存储到服务中 $this->instances[$name] = $instance; } } else { // 使用build方法构建此类 $instance = $this->build($name, $parameters); } if (isset($this->extenders[$name])) { // 调用扩展器 foreach ($this->extenders[$name] as $extender) { $instance = $extender($instance); } } return $instance; } // 构建一个类,并自动注入服务 public function build($class, array $parameters = []) { $reflector = new ReflectionClass($class); $constructor = $reflector->getConstructor(); if (is_null($constructor)) { // 没有构造函数,直接new return new $class(); } $dependencies = []; // 获取构造函数所需的参数 foreach ($constructor->getParameters() as $dependency) { if (isset($this->context[$class]) && isset($this->context[$class][$dependency->getName()])) { // 先从上下文中查找 $dependencies[] = $this->context[$class][$dependency->getName()]; continue; } if (isset($parameters[$dependency->getName()])) { // 从自定义参数中查找 $dependencies[] = $parameters[$dependency->getName()]; continue; } if (is_null($dependency->getClass())) { // 参数类型不是类或接口时,无法从容器中获取依赖 if ($dependency->isDefaultValueAvailable()) { // 查找默认值,如果有就使用默认值 $dependencies[] = $dependency->getDefaultValue(); } else { // 无法提供类所依赖的参数 throw new Exception(‘找不到依赖参数:’ . $dependency->getName()); } } else { // 参数类型是一个类时,就用make方法构建该类 $dependencies[] = $this->make($dependency->getClass()->name); } } return $reflector->newInstanceArgs($dependencies); } // 绑定上下文 public function addContextualBinding($when, $needs, $give) { $this->context[$when][$needs] = $give; } // 支持链式方式绑定上下文 public function when($when) { return new Context($when, $this); }}class Context{ protected $when; protected $needs; protected $container; public function __construct($when, Container $container) { $this->when = $when; $this->container = $container; } public function needs($needs) { $this->needs = $needs; return $this; } public function give($give) { // 调用容器绑定依赖上下文 $this->container->addContextualBinding($this->when, $this->needs, $give); }} ...

April 14, 2019 · 7 min · jiezi

PHP实现一个轻量级容器

什么是容器在开发过程中,经常会用到的一个概念就是依赖注入。我们借助依懒注入来解耦代码,选择性的按需加载服务,而这些通常都是借助容器来实现。容器实现对对象的统一管理,并且确保对象实例的唯一性容器可以很轻易的找到有很多实现示例,如 PHP-DI 、 YII-DI 等各种实现,通常他们要么大而全,要么高度适配特定业务,与实际需要存在冲突。出于需要,我们自己造一个轻量级的轮子,为了保持规范,我们基于 PSR-11 来实现。 PSR-11 PSR 是 php-fig 提供的标准化建议,虽然不是官方组织,但是得到广泛认可。PSR-11 提供了容器接口。它包含 ContainerInterface 和 两个异常接口,并提供使用建议。/** * Describes the interface of a container that exposes methods to read its entries. /interface ContainerInterface{ /* * Finds an entry of the container by its identifier and returns it. * * @param string $id Identifier of the entry to look for. * * @throws NotFoundExceptionInterface No entry was found for this identifier. * @throws ContainerExceptionInterface Error while retrieving the entry. * * @return mixed Entry. / public function get($id); /* * Returns true if the container can return an entry for the given identifier. * Returns false otherwise. * * has($id) returning true does not mean that get($id) will not throw an exception. * It does however mean that get($id) will not throw a NotFoundExceptionInterface. * * @param string $id Identifier of the entry to look for. * * @return bool / public function has($id);}实现示例我们先来实现接口中要求的两个方法abstract class AbstractContainer implements ContainerInterface{ protected $resolvedEntries = []; /* * @var array / protected $definitions = []; public function __construct($definitions = []) { foreach ($definitions as $id => $definition) { $this->injection($id, $definition); } } public function get($id) { if (!$this->has($id)) { throw new NotFoundException(“No entry or class found for {$id}”); } $instance = $this->make($id); return $instance; } public function has($id) { return isset($this->definitions[$id]); }实际我们容器中注入的对象是多种多样的,所以我们单独抽出实例化方法。 protected function make($name) { if (isset($this->resolvedEntries[$name])) { return $this->resolvedEntries[$name]; } $definition = $this->definitions[$name]; $params = []; if (is_array($definition) && isset($definition[‘class’])) { $params = $definition; $definition = $definition[‘class’]; unset($params[‘class’]); } $object = $this->reflector($definition, $params); return $this->resolvedEntries[$name] = $object; } public function reflector($concrete, array $params = []) { if ($concrete instanceof \Closure) { return $concrete($params); } elseif (is_string($concrete)) { $reflection = new \ReflectionClass($concrete); $dependencies = $this->getDependencies($reflection); foreach ($params as $index => $value) { $dependencies[$index] = $value; } return $reflection->newInstanceArgs($dependencies); } elseif (is_object($concrete)) { return $concrete; } } /* * @param \ReflectionClass $reflection * @return array / private function getDependencies($reflection) { $dependencies = []; $constructor = $reflection->getConstructor(); if ($constructor !== null) { $parameters = $constructor->getParameters(); $dependencies = $this->getParametersByDependencies($parameters); } return $dependencies; } /* * * 获取构造类相关参数的依赖 * @param array $dependencies * @return array $parameters * / private function getParametersByDependencies(array $dependencies) { $parameters = []; foreach ($dependencies as $param) { if ($param->getClass()) { $paramName = $param->getClass()->name; $paramObject = $this->reflector($paramName); $parameters[] = $paramObject; } elseif ($param->isArray()) { if ($param->isDefaultValueAvailable()) { $parameters[] = $param->getDefaultValue(); } else { $parameters[] = []; } } elseif ($param->isCallable()) { if ($param->isDefaultValueAvailable()) { $parameters[] = $param->getDefaultValue(); } else { $parameters[] = function ($arg) { }; } } else { if ($param->isDefaultValueAvailable()) { $parameters[] = $param->getDefaultValue(); } else { if ($param->allowsNull()) { $parameters[] = null; } else { $parameters[] = false; } } } } return $parameters; }如你所见,到目前为止我们只实现了从容器中取出实例,从哪里去提供实例定义呢,所以我们还需要提供一个方法. /* * @param string $id * @param string | array | callable $concrete * @throws ContainerException */ public function injection($id, $concrete) { if (!is_string($id)) { throw new \InvalidArgumentException(sprintf( ‘The id parameter must be of type string, %s given’, is_object($id) ? get_class($id) : gettype($id) )); } if (is_array($concrete) && !isset($concrete[‘class’])) { throw new ContainerException(‘数组必须包含类定义’); } $this->definitions[$id] = $concrete; }只有这样吗?对的,有了这些操作我们已经有一个完整的容器了,插箱即用。不过为了使用方便,我们可以再提供一些便捷的方法,比如数组式访问。class Container extends AbstractContainer implements \ArrayAccess{ public function offsetExists($offset) { return $this->has($offset); } public function offsetGet($offset) { return $this->get($offset); } public function offsetSet($offset, $value) { return $this->injection($offset, $value); } public function offsetUnset($offset) { unset($this->resolvedEntries[$offset]); unset($this->definitions[$offset]); }}这样我们就拥有了一个功能丰富,使用方便的轻量级容器了,赶快整合到你的项目中去吧。点击这里查看完整代码 ...

January 27, 2019 · 3 min · jiezi

PHP设计模式范例 — DesignPatternsPHP(2)结构型设计模式

【搬运于GitHub开源项目DesignPatternsPHP】项目地址:戳我2、结构型设计模式在软件工程中,结构型设计模式集是用来抽象真实程序中的对象实体之间的关系,并使这种关系可被描述,概括和具体化。2.1 适配器模式2.1.1 目的将某个类的接口转换成与另一个接口兼容。适配器通过将原始接口进行转换,给用户提供一个兼容接口,使得原来因为接口不同而无法一起使用的类可以得到兼容。2.1.2 例子数据库客户端库适配器使用不同的webservices,通过适配器来标准化输出数据,从而保证不同webservice输出的数据是一致的2.1.3 UML图2.1.4 代码你可以在 GitHub 上找到这些代码BookInterface.php<?phpnamespace DesignPatterns\Structural\Adapter;interface BookInterface{ public function turnPage(); public function open(); public function getPage(): int;}Book.php<?phpnamespace DesignPatterns\Structural\Adapter;class Book implements BookInterface{ /** * @var int / private $page; public function open() { $this->page = 1; } public function turnPage() { $this->page++; } public function getPage(): int { return $this->page; }}EBookAdapter.php<?phpnamespace DesignPatterns\Structural\Adapter;/* * This is the adapter here. Notice it implements BookInterface, * therefore you don’t have to change the code of the client which is using a Book /class EBookAdapter implements BookInterface{ /* * @var EBookInterface / protected $eBook; /* * @param EBookInterface $eBook / public function __construct(EBookInterface $eBook) { $this->eBook = $eBook; } /* * This class makes the proper translation from one interface to another. / public function open() { $this->eBook->unlock(); } public function turnPage() { $this->eBook->pressNext(); } /* * notice the adapted behavior here: EBookInterface::getPage() will return two integers, but BookInterface * supports only a current page getter, so we adapt the behavior here * * @return int / public function getPage(): int { return $this->eBook->getPage()[0]; }}EBookInterface.php<?phpnamespace DesignPatterns\Structural\Adapter;interface EBookInterface{ public function unlock(); public function pressNext(); /* * returns current page and total number of pages, like [10, 100] is page 10 of 100 * * @return int[] / public function getPage(): array;}Kindle.php<?phpnamespace DesignPatterns\Structural\Adapter;/* * this is the adapted class. In production code, this could be a class from another package, some vendor code. * Notice that it uses another naming scheme and the implementation does something similar but in another way /class Kindle implements EBookInterface{ /* * @var int / private $page = 1; /* * @var int / private $totalPages = 100; public function pressNext() { $this->page++; } public function unlock() { } /* * returns current page and total number of pages, like [10, 100] is page 10 of 100 * * @return int[] / public function getPage(): array { return [$this->page, $this->totalPages]; }}2.2 桥接模式2.2.1 目的解耦一个对象的实现与抽象,这样两者可以独立地变化。2.2.2 例子Symfony DoctrineBridge2.2.3 UML图2.2.4 代码你可以在 GitHub 上找到这些代码Formatter.php<?phpnamespace DesignPatterns\Structural\Bridge;interface Formatter{ public function format(string $text): string;}PlainTextFormatter.php<?phpnamespace DesignPatterns\Structural\Bridge;class PlainTextFormatter implements Formatter{ public function format(string $text): string { return $text; }}HtmlFormatter.php<?phpnamespace DesignPatterns\Structural\Bridge;class HtmlFormatter implements Formatter{ public function format(string $text): string { return sprintf(’<p>%s</p>’, $text); }}Service.php<?phpnamespace DesignPatterns\Structural\Bridge;abstract class Service{ /* * @var Formatter / protected $implementation; /* * @param Formatter $printer / public function __construct(Formatter $printer) { $this->implementation = $printer; } /* * @param Formatter $printer / public function setImplementation(Formatter $printer) { $this->implementation = $printer; } abstract public function get(): string;}HelloWorldService.php<?phpnamespace DesignPatterns\Structural\Bridge;class HelloWorldService extends Service{ public function get(): string { return $this->implementation->format(‘Hello World’); }}PingService.php<?phpnamespace DesignPatterns\Structural\Bridge;class PingService extends Service{ public function get(): string { return $this->implementation->format(‘pong’); }}2.3 组合模式2.3.1 目的以单个对象的方式来对待一组对象2.3.2 例子form类的实例包含多个子元素,而它也像单个子元素那样响应render()请求,当调用render()方法时,它会历遍所有的子元素,调用render()方法Zend_Config: 配置选项树, 其每一个分支都是Zend_Config对象2.3.3 UML图2.3.4 代码你可以在 GitHub 上找到这些代码RenderableInterface.php<?phpnamespace DesignPatterns\Structural\Composite;interface RenderableInterface{ public function render(): string;}Form.php<?phpnamespace DesignPatterns\Structural\Composite;/* * The composite node MUST extend the component contract. This is mandatory for building * a tree of components. /class Form implements RenderableInterface{ /* * @var RenderableInterface[] / private $elements; /* * runs through all elements and calls render() on them, then returns the complete representation * of the form. * * from the outside, one will not see this and the form will act like a single object instance * * @return string / public function render(): string { $formCode = ‘<form>’; foreach ($this->elements as $element) { $formCode .= $element->render(); } $formCode .= ‘</form>’; return $formCode; } /* * @param RenderableInterface $element / public function addElement(RenderableInterface $element) { $this->elements[] = $element; }}InputElement.php<?phpnamespace DesignPatterns\Structural\Composite;class InputElement implements RenderableInterface{ public function render(): string { return ‘<input type=“text” />’; }}TextElement.php<?phpnamespace DesignPatterns\Structural\Composite;class TextElement implements RenderableInterface{ /* * @var string / private $text; public function __construct(string $text) { $this->text = $text; } public function render(): string { return $this->text; }}2.4 数据映射器2.4.1 目的数据映射器是一个数据访问层,用于将数据在持久性数据存储(通常是一个关系数据库)和内存中的数据表示(领域层)之间进行相互转换。其目的是为了将数据的内存表示、持久存储、数据访问进行分离。该层由一个或者多个映射器组成(或者数据访问对象),并且进行数据的转换。映射器的实现在范围上有所不同。通用映射器将处理许多不同领域的实体类型,而专用映射器将处理一个或几个。此模式的主要特点是,与Active Record不同,其数据模式遵循单一职责原则(Single Responsibility Principle)。2.4.2 例子DB对象关系映射器(ORM): Doctrine2使用“EntityRepository”作为DAO2.4.3 UML图2.4.4 代码你可以在 GitHub 上找到这些代码User.php<?phpnamespace DesignPatterns\Structural\DataMapper;class User{ /* * @var string / private $username; /* * @var string / private $email; public static function fromState(array $state): User { // validate state before accessing keys! return new self( $state[‘username’], $state[’email’] ); } public function __construct(string $username, string $email) { // validate parameters before setting them! $this->username = $username; $this->email = $email; } /* * @return string / public function getUsername() { return $this->username; } /* * @return string / public function getEmail() { return $this->email; }}UserMapper.php<?phpnamespace DesignPatterns\Structural\DataMapper;class UserMapper{ /* * @var StorageAdapter / private $adapter; /* * @param StorageAdapter $storage / public function __construct(StorageAdapter $storage) { $this->adapter = $storage; } /* * finds a user from storage based on ID and returns a User object located * in memory. Normally this kind of logic will be implemented using the Repository pattern. * However the important part is in mapRowToUser() below, that will create a business object from the * data fetched from storage * * @param int $id * * @return User / public function findById(int $id): User { $result = $this->adapter->find($id); if ($result === null) { throw new \InvalidArgumentException(“User #$id not found”); } return $this->mapRowToUser($result); } private function mapRowToUser(array $row): User { return User::fromState($row); }}StorageAdapter.php<?phpnamespace DesignPatterns\Structural\DataMapper;class StorageAdapter{ /* * @var array / private $data = []; public function __construct(array $data) { $this->data = $data; } /* * @param int $id * * @return array|null / public function find(int $id) { if (isset($this->data[$id])) { return $this->data[$id]; } return null; }}2.5 装饰器2.5.1 目的动态地为类的实例添加功能2.5.2 例子Zend Framework: Zend_Form_Element 实例的装饰器Web Service层:REST服务的JSON与XML装饰器(当然,在此只能使用其中的一种)2.5.3 UML图2.5.4 代码你可以在 GitHub 上找到这些代码Booking.php<?phpnamespace DesignPatterns\Structural\Decorator;interface Booking{ public function calculatePrice(): int; public function getDescription(): string;}BookingDecorator.php<?phpnamespace DesignPatterns\Structural\Decorator;abstract class BookingDecorator implements Booking{ /* * @var Booking / protected $booking; public function __construct(Booking $booking) { $this->booking = $booking; }}DoubleRoomBooking.php<?phpnamespace DesignPatterns\Structural\Decorator;class DoubleRoomBooking implements Booking{ public function calculatePrice(): int { return 40; } public function getDescription(): string { return ‘double room’; }}ExtraBed.php<?phpnamespace DesignPatterns\Structural\Decorator;class ExtraBed extends BookingDecorator{ private const PRICE = 30; public function calculatePrice(): int { return $this->booking->calculatePrice() + self::PRICE; } public function getDescription(): string { return $this->booking->getDescription() . ’ with extra bed’; }}WiFi.php<?phpnamespace DesignPatterns\Structural\Decorator;class WiFi extends BookingDecorator{ private const PRICE = 2; public function calculatePrice(): int { return $this->booking->calculatePrice() + self::PRICE; } public function getDescription(): string { return $this->booking->getDescription() . ’ with wifi’; }}2.6 依赖注入2.6.1 目的实现了松耦合的软件架构,可得到更好的测试,管理和扩展的代码2.6.2 用例注入DatabaseConfiguration, DatabaseConnection将从$config获得所需的所有内容。没有DI(依赖注入),配置将直接在DatabaseConnection中创建,这不利于测试和扩展它。2.6.3 例子Doctrine2 ORM 使用了依赖注入,它通过配置注入了 Connection 对象。为了达到方便测试的目的,可以很容易的通过配置创建一个mock的 Connection 对象。Symfony 和 Zend Framework 2 也有了专门的依赖注入容器,用来通过配置数据创建需要的对象(比如在控制器中使用依赖注入容器获取所需的对象)2.6.4 UML图2.6.5 代码你可以在 GitHub 上找到这些代码DatabaseConfiguration.php<?phpnamespace DesignPatterns\Structural\DependencyInjection;class DatabaseConfiguration{ /* * @var string / private $host; /* * @var int / private $port; /* * @var string / private $username; /* * @var string / private $password; public function __construct(string $host, int $port, string $username, string $password) { $this->host = $host; $this->port = $port; $this->username = $username; $this->password = $password; } public function getHost(): string { return $this->host; } public function getPort(): int { return $this->port; } public function getUsername(): string { return $this->username; } public function getPassword(): string { return $this->password; }}DatabaseConnection.php<?phpnamespace DesignPatterns\Structural\DependencyInjection;class DatabaseConnection{ /* * @var DatabaseConfiguration / private $configuration; /* * @param DatabaseConfiguration $config / public function __construct(DatabaseConfiguration $config) { $this->configuration = $config; } public function getDsn(): string { // this is just for the sake of demonstration, not a real DSN // notice that only the injected config is used here, so there is // a real separation of concerns here return sprintf( ‘%s:%s@%s:%d’, $this->configuration->getUsername(), $this->configuration->getPassword(), $this->configuration->getHost(), $this->configuration->getPort() ); }}2.7 外观模式2.7.1 目的Facade模式的主要目标不是避免您必须阅读复杂API的手册。这只是副作用。主要目的是减少耦合并遵循Demeter定律。Facade通过嵌入多个(当然,有时只有一个)接口来解耦访客与子系统,当然也降低复杂度。Facade不会禁止你访问子系统你可以为一个子系统提供多个 Facade因此一个好的 Facade 里面不会有 new 。如果每个方法里都要构造多个对象,那么它就不是 Facade,而是生成器或者 [ 抽象 | 静态 | 简单 ] 工厂方法。优秀的 Facade 不会有 new,并且构造函数参数是接口类型的。如果你需要创建一个新实例,则在参数中传入一个工厂对象。2.7.2 UML图2.7.3 代码你可以在 GitHub 上找到这些代码Facade.php<?phpnamespace DesignPatterns\Structural\Facade;class Facade{ /* * @var OsInterface / private $os; /* * @var BiosInterface / private $bios; /* * @param BiosInterface $bios * @param OsInterface $os / public function __construct(BiosInterface $bios, OsInterface $os) { $this->bios = $bios; $this->os = $os; } public function turnOn() { $this->bios->execute(); $this->bios->waitForKeyPress(); $this->bios->launch($this->os); } public function turnOff() { $this->os->halt(); $this->bios->powerDown(); }}OsInterface.php<?phpnamespace DesignPatterns\Structural\Facade;interface OsInterface{ public function halt(); public function getName(): string;}BiosInterface.php<?phpnamespace DesignPatterns\Structural\Facade;interface BiosInterface{ public function execute(); public function waitForKeyPress(); public function launch(OsInterface $os); public function powerDown();}2.8 连贯接口2.8.1 目的用来编写易于阅读的代码,就像自然语言一样(如英语)2.8.2 例子Doctrine2 的 QueryBuilder,就像下面例子中类似PHPUnit 使用连贯接口来创建 mock 对象Yii 框架:CDbCommand 与 CActiveRecord 也使用此模式2.8.3 UML图2.8.4 代码你可以在 GitHub 上找到这些代码Sql.php<?phpnamespace DesignPatterns\Structural\FluentInterface;class Sql{ /* * @var array / private $fields = []; /* * @var array / private $from = []; /* * @var array / private $where = []; public function select(array $fields): Sql { $this->fields = $fields; return $this; } public function from(string $table, string $alias): Sql { $this->from[] = $table.’ AS ‘.$alias; return $this; } public function where(string $condition): Sql { $this->where[] = $condition; return $this; } public function __toString(): string { return sprintf( ‘SELECT %s FROM %s WHERE %s’, join(’, ‘, $this->fields), join(’, ‘, $this->from), join(’ AND ‘, $this->where) ); }}2.9 享元2.9.1 目的为了尽可能减少内存使用,Flyweight与类似的对象共享尽可能多的内存。当使用大量状态相差不大的对象时,就需要它。通常的做法是保持外部数据结构中的状态,并在需要时将其传递给flyweight对象。2.9.2 UML图2.9.3 代码你可以在 GitHub 上找到这些代码FlyweightInterface.php<?phpnamespace DesignPatterns\Structural\Flyweight;interface FlyweightInterface{ public function render(string $extrinsicState): string;}CharacterFlyweight.php<?phpnamespace DesignPatterns\Structural\Flyweight;/* * Implements the flyweight interface and adds storage for intrinsic state, if any. * Instances of concrete flyweights are shared by means of a factory. /class CharacterFlyweight implements FlyweightInterface{ /* * Any state stored by the concrete flyweight must be independent of its context. * For flyweights representing characters, this is usually the corresponding character code. * * @var string / private $name; public function __construct(string $name) { $this->name = $name; } public function render(string $font): string { // Clients supply the context-dependent information that the flyweight needs to draw itself // For flyweights representing characters, extrinsic state usually contains e.g. the font. return sprintf(‘Character %s with font %s’, $this->name, $font); }}FlyweightFactory.php<?phpnamespace DesignPatterns\Structural\Flyweight;/* * A factory manages shared flyweights. Clients should not instantiate them directly, * but let the factory take care of returning existing objects or creating new ones. /class FlyweightFactory implements \Countable{ /* * @var CharacterFlyweight[] / private $pool = []; public function get(string $name): CharacterFlyweight { if (!isset($this->pool[$name])) { $this->pool[$name] = new CharacterFlyweight($name); } return $this->pool[$name]; } public function count(): int { return count($this->pool); }}2.10 代理模式2.10.1 目的为昂贵或者无法复制的资源提供接口。2.10.2 例子Doctrine2 使用代理来实现框架特性(如延迟初始化),同时用户还是使用自己的实体类并且不会使用或者接触到代理2.10.3 UML图2.10.4 代码你可以在 GitHub 上找到这些代码BankAccount.php<?phpnamespace DesignPatterns\Structural\Proxy;interface BankAccount{ public function deposit(int $amount); public function getBalance(): int;}HeavyBankAccount.php<?phpnamespace DesignPatterns\Structural\Proxy;class HeavyBankAccount implements BankAccount{ /* * @var int[] / private $transactions = []; public function deposit(int $amount) { $this->transactions[] = $amount; } public function getBalance(): int { // this is the heavy part, imagine all the transactions even from // years and decades ago must be fetched from a database or web service // and the balance must be calculated from it return array_sum($this->transactions); }}BankAccountProxy.php<?phpnamespace DesignPatterns\Structural\Proxy;class BankAccountProxy extends HeavyBankAccount implements BankAccount{ /* * @var int / private $balance; public function getBalance(): int { // because calculating balance is so expensive, // the usage of BankAccount::getBalance() is delayed until it really is needed // and will not be calculated again for this instance if ($this->balance === null) { $this->balance = parent::getBalance(); } return $this->balance; }}2.11 注册模式2.11.1 目的要为整个应用程序中经常使用的对象实现中央存储,通常只使用静态方法(或使用单例模式)的抽象类来实现。请记住,这将引入全局状态,这在任何时候都应该避免!而是使用依赖注入来实现它!2.11.2 例子Zend Framework 1: Zend_Registry 持有应用的logger对象,前端控制器等。Yii 框架: CWebApplication 持有所有的应用组件,如 CWebUser, CUrlManager, 等。2.11.3 UML图2.11.4 代码你可以在 GitHub 上找到这些代码Registry.php<?phpnamespace DesignPatterns\Structural\Registry;abstract class Registry{ const LOGGER = ’logger’; /* * this introduces global state in your application which can not be mocked up for testing * and is therefor considered an anti-pattern! Use dependency injection instead! * * @var array / private static $storedValues = []; /* * @var array / private static $allowedKeys = [ self::LOGGER, ]; /* * @param string $key * @param mixed $value * * @return void / public static function set(string $key, $value) { if (!in_array($key, self::$allowedKeys)) { throw new \InvalidArgumentException(‘Invalid key given’); } self::$storedValues[$key] = $value; } /* * @param string $key * * @return mixed */ public static function get(string $key) { if (!in_array($key, self::$allowedKeys) || !isset(self::$storedValues[$key])) { throw new \InvalidArgumentException(‘Invalid key given’); } return self::$storedValues[$key]; }}相关文章:PHP设计模式范例 — DesignPatternsPHP(1)创建型设计模式 ...

January 18, 2019 · 9 min · jiezi

PHP设计模式范例 — DesignPatternsPHP(1)创建型设计模式

【搬运于GitHub开源项目DesignPatternsPHP】项目地址:戳我1、创建型设计模式在软件工程中,创建型设计模式承担着对象创建的职责,尝试创建适合程序上下文的对象,对象创建设计模式的产生是由于软件工程设计的问题,具体说是向设计中增加复杂度,创建型设计模式解决了程序设计中对象创建的问题。1.1 抽象工厂1.1.1 目的创建一系列相关或依赖的对象,而不指定它们的具体类。通常创建的类都实现相同的接口。抽象工厂的客户端并不关心这些对象是如何创建的,它只知道它们是如何组合在一起的。1.1.2 UML图1.1.3 代码你可以在 GitHub 上查看代码Parser.php<?phpnamespace DesignPatterns\Creational\AbstractFactory;interface Parser{ public function parse(string $input): array;}CsvParser.php<?phpnamespace DesignPatterns\Creational\AbstractFactory;class CsvParser implements Parser{ const OPTION_CONTAINS_HEADER = true; const OPTION_CONTAINS_NO_HEADER = false; /** * @var bool / private $skipHeaderLine; public function __construct(bool $skipHeaderLine) { $this->skipHeaderLine = $skipHeaderLine; } public function parse(string $input): array { $headerWasParsed = false; $parsedLines = []; foreach (explode(PHP_EOL, $input) as $line) { if (!$headerWasParsed && $this->skipHeaderLine === self::OPTION_CONTAINS_HEADER) { $headerWasParsed = true; continue; } $parsedLines[] = str_getcsv($line); } return $parsedLines; }}JsonParser.php<?phpnamespace DesignPatterns\Creational\AbstractFactory;class JsonParser implements Parser{ public function parse(string $input): array { return json_decode($input, true); }}ParserFactory.php<?phpnamespace DesignPatterns\Creational\AbstractFactory;class ParserFactory{ public function createCsvParser(bool $skipHeaderLine): CsvParser { return new CsvParser($skipHeaderLine); } public function createJsonParser(): JsonParser { return new JsonParser(); }}1.2 生成器模式1.2.1 目的生成器的目的是将复杂对象的创建过程(流程)进行抽象,生成器表现为接口的形式。在特定的情况下,比如如果生成器对将要创建的对象有足够多的了解,那么代表生成器的接口 interface 可以是一个抽象类(也就是说可以有一定的具体实现,就像众所周知的适配器模式)。如果对象有复杂的继承树,理论上创建对象的生成器也同样具有复杂的继承树。提示:生成器通常具有流畅的接口,推荐阅读关于 PHPUnit 的 mock 生成器获取更好的理解。1.2.2 例子PHPUnit: Mock 生成器1.2.3 UML图1.2.4 代码你可以在 GitHub 上找到这些代码Director.php<?phpnamespace DesignPatterns\Creational\Builder;use DesignPatterns\Creational\Builder\Parts\Vehicle;/* * Director is part of the builder pattern. It knows the interface of the builder * and builds a complex object with the help of the builder * * You can also inject many builders instead of one to build more complex objects /class Director{ public function build(BuilderInterface $builder): Vehicle { $builder->createVehicle(); $builder->addDoors(); $builder->addEngine(); $builder->addWheel(); return $builder->getVehicle(); }}BuilderInterface.php<?phpnamespace DesignPatterns\Creational\Builder;use DesignPatterns\Creational\Builder\Parts\Vehicle;interface BuilderInterface{ public function createVehicle(); public function addWheel(); public function addEngine(); public function addDoors(); public function getVehicle(): Vehicle;}TruckBuilder.php<?phpnamespace DesignPatterns\Creational\Builder;use DesignPatterns\Creational\Builder\Parts\Vehicle;class TruckBuilder implements BuilderInterface{ /* * @var Parts\Truck / private $truck; public function addDoors() { $this->truck->setPart(‘rightDoor’, new Parts\Door()); $this->truck->setPart(’leftDoor’, new Parts\Door()); } public function addEngine() { $this->truck->setPart(’truckEngine’, new Parts\Engine()); } public function addWheel() { $this->truck->setPart(‘wheel1’, new Parts\Wheel()); $this->truck->setPart(‘wheel2’, new Parts\Wheel()); $this->truck->setPart(‘wheel3’, new Parts\Wheel()); $this->truck->setPart(‘wheel4’, new Parts\Wheel()); $this->truck->setPart(‘wheel5’, new Parts\Wheel()); $this->truck->setPart(‘wheel6’, new Parts\Wheel()); } public function createVehicle() { $this->truck = new Parts\Truck(); } public function getVehicle(): Vehicle { return $this->truck; }}CarBuilder.php<?phpnamespace DesignPatterns\Creational\Builder;use DesignPatterns\Creational\Builder\Parts\Vehicle;class CarBuilder implements BuilderInterface{ /* * @var Parts\Car / private $car; public function addDoors() { $this->car->setPart(‘rightDoor’, new Parts\Door()); $this->car->setPart(’leftDoor’, new Parts\Door()); $this->car->setPart(’trunkLid’, new Parts\Door()); } public function addEngine() { $this->car->setPart(’engine’, new Parts\Engine()); } public function addWheel() { $this->car->setPart(‘wheelLF’, new Parts\Wheel()); $this->car->setPart(‘wheelRF’, new Parts\Wheel()); $this->car->setPart(‘wheelLR’, new Parts\Wheel()); $this->car->setPart(‘wheelRR’, new Parts\Wheel()); } public function createVehicle() { $this->car = new Parts\Car(); } public function getVehicle(): Vehicle { return $this->car; }}Parts/Vehicle.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;abstract class Vehicle{ /* * @var object[] / private $data = []; /* * @param string $key * @param object $value / public function setPart($key, $value) { $this->data[$key] = $value; }}Parts/Truck.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;class Truck extends Vehicle{}Parts/Car.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;class Engine{}Parts/Engine.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;class Engine{}Parts/Wheel.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;class Wheel{}Parts/Door.php<?phpnamespace DesignPatterns\Creational\Builder\Parts;class Door{}1.3 工厂方法1.3.1 目的SimpleFactory的优点是您可以子类化它来实现创建对象的不同方法。对于简单的情况,这个抽象类可能只是一个接口。这个模式是一个 “真正” 的设计模式,因为它遵循了依赖反转原则 Dependency Inversion Principle 众所周知这个 “D” 代表了真正的面向对象程序设计。它意味着工厂方法类依赖于类的抽象,而不是具体将被创建的类,这是工厂方法模式与简单工厂模式和静态工厂模式最重要的区别。1.3.2 UML图1.3.3 代码你可以在 GitHub 上找到这些代码Logger.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;interface Logger{ public function log(string $message);}StdoutLogger.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;class StdoutLogger implements Logger{ public function log(string $message) { echo $message; }}FileLogger.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;class FileLogger implements Logger{ /* * @var string / private $filePath; public function __construct(string $filePath) { $this->filePath = $filePath; } public function log(string $message) { file_put_contents($this->filePath, $message . PHP_EOL, FILE_APPEND); }}LoggerFactory.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;interface LoggerFactory{ public function createLogger(): Logger;}StdoutLoggerFactory.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;class StdoutLoggerFactory implements LoggerFactory{ public function createLogger(): Logger { return new StdoutLogger(); }}FileLoggerFactory.php<?phpnamespace DesignPatterns\Creational\FactoryMethod;class FileLoggerFactory implements LoggerFactory{ /* * @var string / private $filePath; public function __construct(string $filePath) { $this->filePath = $filePath; } public function createLogger(): Logger { return new FileLogger($this->filePath); }}1.4 多例多例模式已经被考虑列入到反模式中!请使用依赖注入获得更好的代码可测试性和可控性!1.4.1 目的使类仅有一个命名的对象的集合可供使用,像单例模式但是有多个实例。1.4.2 例子2 个数据库连接,比如,一个连接MySQL,另一个连接SQLite多个日志记录器(一个记录调试信息,另一个记录错误信息)1.4.3 UML 图1.4.4 代码你可以在 GitHub 上找到这些代码Multiton.php<?phpnamespace DesignPatterns\Creational\Multiton;final class Multiton{ const INSTANCE_1 = ‘1’; const INSTANCE_2 = ‘2’; /* * @var Multiton[] / private static $instances = []; /* * this is private to prevent from creating arbitrary instances / private function __construct() { } public static function getInstance(string $instanceName): Multiton { if (!isset(self::$instances[$instanceName])) { self::$instances[$instanceName] = new self(); } return self::$instances[$instanceName]; } /* * prevent instance from being cloned / private function __clone() { } /* * prevent instance from being unserialized / private function __wakeup() { }}1.5 对象池1.5.1 目的对象池设计模式 是创建型设计模式,它会对新创建的对象应用一系列的初始化操作,让对象保持立即可使用的状态 - 一个存放对象的 “池子” - 而不是对对象进行一次性的的使用(创建并使用,完成之后立即销毁)。对象池的使用者会对对象池发起请求,以期望获取一个对象,并使用获取到的对象进行一系列操作,当使用者对对象的使用完成之后,使用者会将由对象池的对象创建工厂创建的对象返回给对象池,而不是用完之后销毁获取到的对象。对象池在某些情况下会带来重要的性能提升,比如耗费资源的对象初始化操作,实例化类的代价很高,但每次实例化的数量较少的情况下。对象池中将被创建的对象会在真正被使用时被提前创建,避免在使用时让使用者浪费对象创建所需的大量时间(比如在对象某些操作需要访问网络资源的情况下)从池子中取得对象的时间是可预测的,但新建一个实例所需的时间是不确定。总之,对象池会为你节省宝贵的程序执行时间,比如像数据库连接,socket连接,大量耗费资源的代表数字资源的对象,像字体或者位图。不过,在特定情况下,简单的对象创建池(没有请求外部的资源,仅仅将自身保存在内存中)或许并不会提升效率和性能,这时候,就需要使用者酌情考虑了。1.5.2 UML图1.5.3 代码你可以在 GitHub 上找到这些代码WorkerPool.php<?phpnamespace DesignPatterns\Creational\Pool;class WorkerPool implements \Countable{ /* * @var StringReverseWorker[] / private $occupiedWorkers = []; /* * @var StringReverseWorker[] / private $freeWorkers = []; public function get(): StringReverseWorker { if (count($this->freeWorkers) == 0) { $worker = new StringReverseWorker(); } else { $worker = array_pop($this->freeWorkers); } $this->occupiedWorkers[spl_object_hash($worker)] = $worker; return $worker; } public function dispose(StringReverseWorker $worker) { $key = spl_object_hash($worker); if (isset($this->occupiedWorkers[$key])) { unset($this->occupiedWorkers[$key]); $this->freeWorkers[$key] = $worker; } } public function count(): int { return count($this->occupiedWorkers) + count($this->freeWorkers); }}StringReverseWorker.php<?phpnamespace DesignPatterns\Creational\Pool;class StringReverseWorker{ /* * @var \DateTime / private $createdAt; public function __construct() { $this->createdAt = new \DateTime(); } public function run(string $text) { return strrev($text); }}1.6 原型模式1.6.1 目的通过创建一个原型对象,然后复制原型对象来避免通过标准的方式创建大量的对象产生的开销(new Foo())。1.6.2 例子大量的数据对象(比如通过ORM获取1,000,000行数据库记录然后创建每一条记录对应的对象实体)1.6.3 UML图1.6.4 代码你可以在 GitHub 上找到这些代码BookPrototype.php<?phpnamespace DesignPatterns\Creational\Prototype;abstract class BookPrototype{ /* * @var string / protected $title; /* * @var string / protected $category; abstract public function __clone(); public function getTitle(): string { return $this->title; } public function setTitle($title) { $this->title = $title; }}BarBookPrototype.php<?phpnamespace DesignPatterns\Creational\Prototype;class BarBookPrototype extends BookPrototype{ /* * @var string / protected $category = ‘Bar’; public function __clone() { }}FooBookPrototype.php<?phpnamespace DesignPatterns\Creational\Prototype;class FooBookPrototype extends BookPrototype{ /* * @var string / protected $category = ‘Foo’; public function __clone() { }}1.7 简单工厂1.7.1 目的它与静态工厂不同,因为它不是静态的。因此,可以有多个参数化的工厂,可以子类化它,也可以模拟它。它总是比静态工厂更受欢迎!1.7.2 UML图1.7.3 代码你可以在 GitHub 上找到这些代码SimpleFactory.php<?phpnamespace DesignPatterns\Creational\SimpleFactory;class SimpleFactory{ public function createBicycle(): Bicycle { return new Bicycle(); }}Bicycle.php<?phpnamespace DesignPatterns\Creational\SimpleFactory;class Bicycle{ public function driveTo(string $destination) { }}1.7.4 使用 $factory = new SimpleFactory(); $bicycle = $factory->createBicycle(); $bicycle->driveTo(‘Paris’);1.8 单例模式1.8.1 目标使应用中只存在一个对象的实例,并且使这个单实例负责所有对该对象的调用。1.8.2 例子数据库连接器日志记录器 (可能有多个实例,比如有多个日志文件因为不同的目的记录不同到的日志)应用锁文件 (理论上整个应用只有一个锁文件)1.8.3 UML图1.8.4 代码你可以在 GitHub 上找到这些代码Singleton.php<?phpnamespace DesignPatterns\Creational\Singleton;final class Singleton{ /* * @var Singleton / private static $instance; /* * gets the instance via lazy initialization (created on first usage) / public static function getInstance(): Singleton { if (null === static::$instance) { static::$instance = new static(); } return static::$instance; } /* * is not allowed to call from outside to prevent from creating multiple instances, * to use the singleton, you have to obtain the instance from Singleton::getInstance() instead / private function __construct() { } /* * prevent the instance from being cloned (which would create a second instance of it) / private function __clone() { } /* * prevent from being unserialized (which would create a second instance of it) / private function _wakeup() { }}1.9 静态工厂1.9.1 目的和抽象工厂类似,静态工厂模式用来创建一系列互相关联或依赖的对象,和抽象工厂模式不同的是静态工厂模式只用一个静态方法就解决了所有类型的对象创建,通常被命名为 Factory 或者 Generators1.9.2 例子Zend Framework: zend_cache 后端或 _Frontend 使用工厂方法创建缓存后端和前端1.9.3 UML图1.9.4 代码你可以在 GitHub 上找到这些代码StaticFactory.php<?phpnamespace DesignPatterns\Creational\StaticFactory;/* * Note1: Remember, static means global state which is evil because it can’t be mocked for tests * Note2: Cannot be subclassed or mock-upped or have multiple different instances. /final class StaticFactory{ /* * @param string $type * * @return Formatter */ public static function factory(string $type): Formatter { if ($type == ’number’) { return new FormatNumber(); } elseif ($type == ‘string’) { return new FormatString(); } throw new \InvalidArgumentException(‘Unknown format given’); }}Formatter.php<?phpnamespace DesignPatterns\Creational\StaticFactory;interface Formatter{ public function format(string $input): string;}FormatString.php<?phpnamespace DesignPatterns\Creational\StaticFactory;class FormatString implements Formatter{ public function format(string $input): string { return $input; }}FormatNumber.php<?phpnamespace DesignPatterns\Creational\StaticFactory;class FormatNumber implements Formatter{ public function format(string $input): string { return number_format($input); }} ...

January 18, 2019 · 6 min · jiezi