DependencyInjection中文名叫依赖注入,是一种设计模式。其实接触DI已经很久,总结总结自己的浅显认识,也欢迎资深人士指正。下面看看DI怎么配合框架使用。
首先 我们有个somecomponent类,它需要依赖数据库连接。
class SomeComponent
{
public function someDbTask()
{
$connection = new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
// ...
}
}
$some = new SomeComponent();
$some->someDbTask();
我们看到如上的使用方法,这样的话 ,connection的创建 完全在类内部,我们在外部完全没法修改连接参数,也完全没法复用。
好,那独立注入,来优化这个事情。把这种依赖的,通过set注入进去。
class SomeComponent
{
protected $_connection;
/**
* Sets the connection externally
*/
public function setConnection($connection)
{
$this->_connection = $connection;
}
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
}
$some = new SomeComponent();
//Create the connection
$connection = new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
//Inject the connection in the component
$some->setConnection($connection);
$some->someDbTask();
那么接着而来的问题是 每次使用的时候 我都需要生成一个connection 然后注入一下。
那么我们怎么解决呢 来个global registry
class Registry
{
/**
* Returns the connection
*/
public static function getConnection()
{
return new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
}
}
class SomeComponent
{
protected $_connection;
/**
* Sets the connection externally
*/
public function setConnection($connection)
{
$this->_connection = $connection;
}
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
}
$some = new SomeComponent();
//Pass the connection defined in the registry
$some->setConnection(Registry::getConnection());
$some->someDbTask();
进一步呢 我们优化getconnection来,类似单例模式。
class Registry
{
protected static $_connection;
/**
* Creates a connection
*/
protected static function _createConnection()
{
return new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
}
/**
* Creates a connection only once and returns it
*/
public static function getSharedConnection()
{
if (self::$_connection===null){
$connection = self::_createConnection();
self::$_connection = $connection;
}
return self::$_connection;
}
/**
* Always returns a new connection
*/
public static function getNewConnection()
{
return self::_createConnection();
}
}
$some = new SomeComponent();
//This injects the shared connection
$some->setConnection(Registry::getSharedConnection());
$some->someDbTask();
//Here, we always pass a new connection as parameter
$some->someOtherDbTask(Registry::getNewConnection());
没错 已经使用了DI模式,看起来代码已经解耦,可维护性也不错。
但是呢,进一步的这种依赖有可能很多的时候,就会出现问题。
//Create the dependencies or retrieve them from the registry
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
//Pass them as constructor parameters
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
// ... or using setters
$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);
有可能这段代码 在很多运行some类的某个方法的时候用到,那么每个用的地方都有这么一堆注入。
一旦某个依赖不需要了,修改起来也是灾难性的。
也许接着的方案就是 再把这些依赖 移到类的factory方法去。。
class SomeComponent
{
// ...
/**
* Define a factory method to create SomeComponent instances injecting its dependencies
*/
public static function factory()
{
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
return new self($connection, $session, $fileSystem, $filter, $selector);
}
}
啊哈,你看到 ,似乎我们的代码回到了最初开始的地方。
怎么办呢。。。最终我们需要一个容器来装这些依赖。
class SomeComponent
{
protected $_di;
public function __construct($di)
{
$this->_di = $di;
}
public function someDbTask()
{
// Get the connection service
// Always returns a new connection
$connection = $this->_di->get('db');
}
public function someOtherDbTask()
{
// Get a shared connection service,
// this will return the same connection everytime
$connection = $this->_di->getShared('db');
//This method also requires an input filtering service
$filter = $this->_di->get('filter');
}
}
DI容器 看起来和global registry 很像,用di这个容器做桥梁来获得依赖,减少了我们实现类的复杂性。
同时DI容器实现了 需要时候才初始化的特性,在不使用的时候不进行初始化,这个和类似zend_registry是个改进。
任何依赖如connection的修改,并不影响somecomponent的代码。
它不仅能注入一些依赖使得代码解耦,而且很好的管理一些全局的共享类实例,也使得测试变得简单,同时DI容器提供了类内部依赖的统一请求接口。
DI从字面上只是注入依赖这个事情,其实深层次的,配合DI容器构成ServiceLocator,可以达到很强大的效果。
那么提提IoC,IoC字面上就是控制反转,Inversion Of Control ,它的思想是对象的控制权进行转移,比如转移到Ioc容器,比如转移到配制文件,等等。而DI是这种思想的一种具体方式,将这种转移在运行期间,动态地将某种依赖关系注入到对象之中。
带来的好处已经提了很多,上面提及的是代码上的一些好处,使用上来说
1)使得我们可以轻松替换一个依赖组件,修改为我们自己写的或者第三方的。
2)我们有了这些依赖组件的初始化的完全控制权。
3)我们获得了全局类的一些统一的访问入口。
认识就这么多,因为我大部分是PHP的开发,猜想有可能在JAVA这种需要编译的语言上,这种模式的运用带来的好处更多。