关闭

理解PHP 依赖注入

1142人阅读 评论(0) 收藏 举报
分类:

好吧,标题党了,本文非原创啊!

看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了,现在确实很懒变得!

首先,我们假设,我们要开发一个组件命名为SomeComponent。这个组件中现在将要注入一个数据库连接。

在这个例子中,数据库连接在component中被创建,这种方法是不切实际的,这样做的话,我们将不能改变数据库连接参数及数据库类型等一些参数。

01 <?php
02  
03 class SomeComponent
04 {
05  
06     /**
07      * The instantiation of the connection is hardcoded inside
08      * the component so is difficult to replace it externally
09      * or change its behavior
10      */
11     public function someDbTask()
12     {
13         $connection = new Connection(array(
14             "host" => "localhost",
15             "username" => "root",
16             "password" => "secret",
17             "dbname" => "invo"
18         ));
19  
20         // ...
21     }
22  
23 }
24  
25 $some = new SomeComponent();
26 $some->someDbTask();

为了解决上面所说的问题,我们需要在使用前创建一个外部连接,并注入到容器中。就目前而言,这看起来是一个很好的解决方案:

01 <?php
02  
03 class SomeComponent
04 {
05  
06     protected $_connection;
07  
08     /**
09      * Sets the connection externally
10      */
11     public function setConnection($connection)
12     {
13         $this->_connection = $connection;
14     }
15  
16     public function someDbTask()
17     {
18         $connection = $this->_connection;
19  
20         // ...
21     }
22  
23 }
24  
25 $some = new SomeComponent();
26  
27 //Create the connection
28 $connection = new Connection(array(
29     "host" => "localhost",
30     "username" => "root",
31     "password" => "secret",
32     "dbname" => "invo"
33 ));
34  
35 //Inject the connection in the component
36 $some->setConnection($connection);
37  
38 $some->someDbTask();

现在我们来考虑一个问题,我们在应用程序中的不同地方使用此组件,将多次创建数据库连接。使用一种类似全局注册表的方式,从这获得一个数据库连接实例,而不是使用一次就创建一次。

01 <?php
02  
03 class Registry
04 {
05  
06     /**
07      * Returns the connection
08      */
09     public static function getConnection()
10     {
11        return new Connection(array(
12             "host" => "localhost",
13             "username" => "root",
14             "password" => "secret",
15             "dbname" => "invo"
16         ));
17     }
18  
19 }
20  
21 class SomeComponent
22 {
23  
24     protected $_connection;
25  
26     /**
27      * Sets the connection externally
28      */
29     public function setConnection($connection){
30         $this->_connection = $connection;
31     }
32  
33     public function someDbTask()
34     {
35         $connection = $this->_connection;
36  
37         // ...
38     }
39  
40 }
41  
42 $some = new SomeComponent();
43  
44 //Pass the connection defined in the registry
45 $some->setConnection(Registry::getConnection());
46  
47 $some->someDbTask();

现在,让我们来想像一下,我们必须在组件中实现两个方法,首先需要创建一个新的数据库连接,第二个总是获得一个共享连接:

01 <?php
02  
03 class Registry
04 {
05  
06     protected static $_connection;
07  
08     /**
09      * Creates a connection
10      */
11     protected static function _createConnection()
12     {
13         return new Connection(array(
14             "host" => "localhost",
15             "username" => "root",
16             "password" => "secret",
17             "dbname" => "invo"
18         ));
19     }
20  
21     /**
22      * Creates a connection only once and returns it
23      */
24     public static function getSharedConnection()
25     {
26         if (self::$_connection===null){
27             $connection = self::_createConnection();
28             self::$_connection = $connection;
29         }
30         return self::$_connection;
31     }
32  
33     /**
34      * Always returns a new connection
35      */
36     public static function getNewConnection()
37     {
38         return self::_createConnection();
39     }
40  
41 }
42  
43 class SomeComponent
44 {
45  
46     protected $_connection;
47  
48     /**
49      * Sets the connection externally
50      */
51     public function setConnection($connection){
52         $this->_connection = $connection;
53     }
54  
55     /**
56      * This method always needs the shared connection
57      */
58     public function someDbTask()
59     {
60         $connection = $this->_connection;
61  
62         // ...
63     }
64  
65     /**
66      * This method always needs a new connection
67      */
68     public function someOtherDbTask($connection)
69     {
70  
71     }
72  
73 }
74  
75 $some = new SomeComponent();
76  
77 //This injects the shared connection
78 $some->setConnection(Registry::getSharedConnection());
79  
80 $some->someDbTask();
81  
82 //Here, we always pass a new connection as parameter
83 $some->someOtherDbTask(Registry::getConnection());

到此为止,我们已经看到了如何使用依赖注入解决我们的问题。不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。但是从长远来看,这种形式的依赖注入也有一些缺点。

例如,如果组件中有较多的依赖关系,我们需要创建多个setter方法传递,或创建构造函数进行传递。另外,每次使用组件时,都需要创建依赖组件,使代码维护不太易,我们编写的代码可能像这样:

01 <?php
02  
03 //Create the dependencies or retrieve them from the registry
04 $connection = new Connection();
05 $session = new Session();
06 $fileSystem = new FileSystem();
07 $filter = new Filter();
08 $selector = new Selector();