设计思想依赖注入

Posted 半生归来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计思想依赖注入相关的知识,希望对你有一定的参考价值。

 场景:传统的思路是应用程序用到一个Foo类,就会创建Foo类并调用Foo类的方法,假如这个方法内需要一个Bar类,就会创建Bar类并调用Bar类的方法,而这个方法内需要一个Bim类,就会创建Bim类,接着做些其它工作。

【代码1】

技术分享
 1 class Bim
 2     {
 3         public function doSomething()
 4         {
 5             echo __METHOD__, ‘|‘;
 6         }
 7     }
 8     
 9     class Bar
10     {
11         public function doSomething()
12         {
13             $bim = new Bim();
14             $bim->doSomething();
15             echo __METHOD__, ‘|‘;
16         }
17     }
18     
19     class Foo
20     {
21         public function doSomething()
22         {
23             $bar = new Bar();
24             $bar->doSomething();
25             echo __METHOD__;
26         }
27     }
28     
29     $foo = new Foo();
30     $foo->doSomething(); //Bim::doSomething|Bar::doSomething|Foo::doSomething
View Code

 使用依赖注入的思路是应用程序用到Foo类,Foo类需要Bar类,Bar类需要Bim类,那么先创建Bim类,再创建Bar类并把Bim注入,再创建Foo类,并把Bar类注入,再调用Foo方法,Foo调用Bar方法,接着做些其它工作。

技术分享
 1 class Bim
 2     {
 3         public function doSomething()
 4         {
 5             echo __METHOD__, ‘|‘;
 6         }
 7     }
 8     
 9     class Bar
10     {
11         private $bim;
12     
13         public function __construct(Bim $bim)
14         {
15             $this->bim = $bim;
16         }
17     
18         public function doSomething()
19         {
20             $this->bim->doSomething();
21             echo __METHOD__, ‘|‘;
22         }
23     }
24     
25     class Foo
26     {
27         private $bar;
28     
29         public function __construct(Bar $bar)
30         {
31             $this->bar = $bar;
32         }
33     
34         public function doSomething()
35         {
36             $this->bar->doSomething();
37             echo __METHOD__;
38         }
39     }
40     
41     $foo = new Foo(new Bar(new Bim()));
42     $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
View Code

这就是控制反转模式。依赖关系的控制反转到调用链的起点。这样你可以完全控制依赖关系,通过调整不同的注入对象,来控制程序的行为。例如Foo类用到了memcache,可以在不修改Foo类代码的情况下,改用redis。

使用依赖注入容器后的思路是应用程序需要到Foo类,就从容器内取得Foo类,容器创建Bim类,再创建Bar类并把Bim注入,再创建Foo类,并把Bar注入,应用程序调用Foo方法,Foo调用Bar方法,接着做些其它工作.

总之容器负责实例化,注入依赖,处理依赖关系等工作

代码演示 依赖注入容器 (dependency injection container)

通过一个最简单的容器类来解释一下

技术分享
 1 class Container
 2     {
 3         private $s = array();
 4     
 5         function __set($k, $c)
 6         {
 7             $this->s[$k] = $c;
 8         }
 9     
10         function __get($k)
11         {
12             return $this->s[$k]($this);
13         }
14     }
View Code

这段代码使用了魔术方法,在给不可访问属性赋值时,__set() 会被调用。读取不可访问属性的值时,__get() 会被调用。

技术分享
 1 $c = new Container();
 2     
 3     $c->bim = function () {
 4         return new Bim();
 5     };
 6     $c->bar = function ($c) {
 7         return new Bar($c->bim);
 8     };
 9     $c->foo = function ($c) {
10         return new Foo($c->bar);
11     };
12     
13     // 从容器中取得Foo
14     $foo = $c->foo;
15     $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
View Code

这段代码使用了匿名函数

再来一段简单的代码演示一下,容器代码来自simple di container

技术分享
 1 class IoC
 2     {
 3         protected static $registry = [];
 4     
 5         public static function bind($name, Callable $resolver)
 6         {
 7             static::$registry[$name] = $resolver;
 8         }
 9     
10         public static function make($name)
11         {
12             if (isset(static::$registry[$name])) {
13                 $resolver = static::$registry[$name];
14                 return $resolver();
15             }
16             throw new Exception(‘Alias does not exist in the IoC registry.‘);
17         }
18     }
19     
20     IoC::bind(‘bim‘, function () {
21         return new Bim();
22     });
23     IoC::bind(‘bar‘, function () {
24         return new Bar(IoC::make(‘bim‘));
25     });
26     IoC::bind(‘foo‘, function () {
27         return new Foo(IoC::make(‘bar‘));
28     });
29     
30     
31     // 从容器中取得Foo
32     $foo = IoC::make(‘foo‘);
33     $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
View Code

这段代码使用了后期静态绑定

依赖注入容器 (dependency injection container) 高级功能

技术分享
  1 class Bim
  2     {
  3         public function doSomething()
  4         {
  5             echo __METHOD__, ‘|‘;
  6         }
  7     }
  8     
  9     class Bar
 10     {
 11         private $bim;
 12     
 13         public function __construct(Bim $bim)
 14         {
 15             $this->bim = $bim;
 16         }
 17     
 18         public function doSomething()
 19         {
 20             $this->bim->doSomething();
 21             echo __METHOD__, ‘|‘;
 22         }
 23     }
 24     
 25     class Foo
 26     {
 27         private $bar;
 28     
 29         public function __construct(Bar $bar)
 30         {
 31             $this->bar = $bar;
 32         }
 33     
 34         public function doSomething()
 35         {
 36             $this->bar->doSomething();
 37             echo __METHOD__;
 38         }
 39     }
 40     
 41     class Container
 42     {
 43         private $s = array();
 44     
 45         public function __set($k, $c)
 46         {
 47             $this->s[$k] = $c;
 48         }
 49     
 50         public function __get($k)
 51         {
 52             // return $this->s[$k]($this);
 53             return $this->build($this->s[$k]);
 54         }
 55     
 56         /**
 57          * 自动绑定(Autowiring)自动解析(Automatic Resolution)
 58          *
 59          * @param string $className
 60          * @return object
 61          * @throws Exception
 62          */
 63         public function build($className)
 64         {
 65             // 如果是匿名函数(Anonymous functions),也叫闭包函数(closures)
 66             if ($className instanceof Closure) {
 67                 // 执行闭包函数,并将结果
 68                 return $className($this);
 69             }
 70     
 71             /** @var ReflectionClass $reflector */
 72             $reflector = new ReflectionClass($className);
 73     
 74             // 检查类是否可实例化, 排除抽象类abstract和对象接口interface
 75             if (!$reflector->isInstantiable()) {
 76                 throw new Exception("Can‘t instantiate this.");
 77             }
 78     
 79             /** @var ReflectionMethod $constructor 获取类的构造函数 */
 80             $constructor = $reflector->getConstructor();
 81     
 82             // 若无构造函数,直接实例化并返回
 83             if (is_null($constructor)) {
 84                 return new $className;
 85             }
 86     
 87             // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表
 88             $parameters = $constructor->getParameters();
 89     
 90             // 递归解析构造函数的参数
 91             $dependencies = $this->getDependencies($parameters);
 92     
 93             // 创建一个类的新实例,给出的参数将传递到类的构造函数。
 94             return $reflector->newInstanceArgs($dependencies);
 95         }
 96     
 97         /**
 98          * @param array $parameters
 99          * @return array
100          * @throws Exception
101          */
102         public function getDependencies($parameters)
103         {
104             $dependencies = [];
105     
106             /** @var ReflectionParameter $parameter */
107             foreach ($parameters as $parameter) {
108                 /** @var ReflectionClass $dependency */
109                 $dependency = $parameter->getClass();
110     
111                 if (is_null($dependency)) {
112                     // 是变量,有默认值则设置默认值
113                     $dependencies[] = $this->resolveNonClass($parameter);
114                 } else {
115                     // 是一个类,递归解析
116                     $dependencies[] = $this->build($dependency->name);
117                 }
118             }
119     
120             return $dependencies;
121         }
122     
123         /**
124          * @param ReflectionParameter $parameter
125          * @return mixed
126          * @throws Exception
127          */
128         public function resolveNonClass($parameter)
129         {
130             // 有默认值则返回默认值
131             if ($parameter->isDefaultValueAvailable()) {
132                 return $parameter->getDefaultValue();
133             }
134     
135             throw new Exception(‘I have no idea what to do here.‘);
136         }
137     }
138     
139     // ----
140     $c = new Container();
141     $c->bar = ‘Bar‘;
142     $c->foo = function ($c) {
143         return new Foo($c->bar);
144     };
145     // 从容器中取得Foo
146     $foo = $c->foo;
147     $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
148     
149     // ----
150     $di = new Container();
151     
152     $di->foo = ‘Foo‘;
153     
154     /** @var Foo $foo */
155     $foo = $di->foo;
156     
157     var_dump($foo);
158     /*
159     Foo#10 (1) {
160       private $bar =>
161       class Bar#14 (1) {
162         private $bim =>
163         class Bim#16 (0) {
164         }
165       }
166     }
167     */
168     
169     $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
View Code

以上代码的原理参考php官方文档:反射,PHP 5 具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。

若想进一步提供一个数组访问接口,如$di->foo可以写成$di‘foo‘],则需用到[ArrayAccess(数组式访问)接口 

一些复杂的容器会有许多特性

以上是关于设计思想依赖注入的主要内容,如果未能解决你的问题,请参考以下文章

spring练习,在Eclipse搭建的Spring开发环境中,使用set注入方式,实现对象的依赖关系,通过ClassPathXmlApplicationContext实体类获取Bean对象(代码片段

依赖注入

攻城狮学习时间~依赖注入设计模式

依赖注入[3]: 依赖注入模式

依赖注入 - 设计模式

设计模式软件设计七大原则 ( 依赖倒置原则 | 代码示例 )