Dependency Injection-依賴注入詳解
依賴注入是目前很多優秀框架都在使用的一個設計模式。Java的開發框架如Spring在用,PHP的Laravel/Phalcon/Symfony等也在用。好多不同語言的框架,設計思想大同小異,相互借鑑參考。熟悉了一個語言的開發框架,其它不同的框架甚至不同語言的開發框架,往往也很容易從設計理念和概念上理解。不過,有些語言因為設計特色,一些設計模式反而看似消失不見了。其實是融入了語言裡面,不易察覺。我看見過這麼一句話:“設計模式是程式語言固有缺陷的產物”。有一個討論在這裡:Why is IoC / DI not common in Python?
Dependency Injection 常常簡稱為:DI。它是實現控制反轉(Inversion of Control – IoC)的一個模式。有一本依賴注入詳解的書在這裡:Dependency Injection 。它的本質目的是解耦,保持軟體元件之間的鬆散耦合,為設計開發帶來靈活性。
這裡借用一套PHP程式碼的演化過程,解釋依賴注入模式的出現過程。程式碼來自Phalcon框架文件。個人感覺,從演化出發,最能達成理解的目標,就如同數學推理一樣讓人信服,自然而然。想當年,我研究Windows時代的COM技術體系,看到有一本書也是這麼做的 – Dan Box的《COM本質論》第1-2章,闡述了從Dll到COM元件的設計過程。
假設我們要開發一套元件,這個元件幹啥目前並不重要,不過它需要連線資料庫。最簡單的實現,當然是把資料庫的配置資訊寫在元件裡面。
class SomeComponent
{
/**
* The instantiation of the connection is hardcoded inside
* the component, therefore it's difficult replace it externally
* or change its behavior
*/
public function someDbTask()
{
$connection = new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
// ...
}
}
$some = new SomeComponent();
$some->someDbTask();
但是這麼幹問題很大,屬於“程式碼的壞味道”。因為資料庫配置寫死了,完全沒有靈活性可言。這也給後面的測試/部署/安全帶來了隱患。為了解決這個問題,我們試試把配置資訊拿出去,從外面傳進來。
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(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
好一點。不過如果我們在很多地方都要用這個元件,那麼意味著每次用的時候,都要建立這麼一個連線配置物件,不僅冗餘,而且難以變更和管理。我們把這個連線配置物件單獨放在一個地方管理,DRY原則。
class Registry
{
/**
* Returns the connection
*/
public static function getConnection()
{
return new Connection(
[
"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();
可行。不過有個問題,連線物件每次使用都是重複建立,浪費資源。再改一下,改成共享式,近似於單件模式。
class Registry
{
protected static $_connection;
/**
* Creates a connection
*/
protected static function _createConnection()
{
return new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
}
/**
* Creates a connection only once and returns it
*/
public static function getSharedConnection()
{
if (self::$_connection === null) {
self::$_connection = self::_createConnection();
}
return self::$_connection;
}
/**
* Always returns a new connection
*/
public static function getNewConnection()
{
return self::_createConnection();
}
}
class SomeComponent
{
protected $_connection;
/**
* Sets the connection externally
*/
public function setConnection($connection)
{
$this->_connection = $connection;
}
/**
* This method always needs the shared connection
*/
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
/**
* This method always needs a new connection
*/
public function someOtherDbTask($connection)
{
}
}
$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()
);
這就是“依賴注入”模式了,它解決了元件的依賴項和元件之間的過度耦合問題。不過還有個麻煩:如果這個元件依賴項很多怎麼辦?每次都要建立並設定一大堆依賴項。
// 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);
每次使用這個元件,都要建立一堆附加的依賴項。如果以後我們修改元件依賴,那麼必須挨個改掉。程式碼的壞味道又來了。再改。
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);
}
}
估計好多人走到這一步就會停下腳步了。程式碼用個工廠模式不就行了嘛。可是你對比下開頭的程式碼,元件和它的依賴項的耦合不就又來了麼?現在,問題又回到開頭了。
一個更好的辦法是使用依賴注入容器。它就如同一個全域性的登錄檔,像橋一樣獲取依賴項,並解耦。
use Phalcon\Di;
use Phalcon\DiInterface;
class SomeComponent
{
protected $_di;
public function __construct(DiInterface $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 every time
$connection = $this->_di->getShared("db");
// This method also requires an input filtering service
$filter = $this->_di->get("filter");
}
}
$di = new Di();
// Register a "db" service in the container
$di->set(
"db",
function () {
return new Connection(
[
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo",
]
);
}
);
// Register a "filter" service in the container
$di->set(
"filter",
function () {
return new Filter();
}
);
// Register a "session" service in the container
$di->set(
"session",
function () {
return new Session();
}
);
// Pass the service container as unique parameter
$some = new SomeComponent($di);
$some->someDbTask();”
問題解決。獲取依賴項只要通過DI容器介面操作,不需要的部分甚至都不會建立,節約了資源。
在Java的Spring框架裡面,依賴注入和控制反轉設計思想是近似的,道理相同但是實現不同。因為程式語言各有各的設計特點可以利用。
Spring框架的依賴注入容器介面是:ApplicationContext.
ApplicationContext context
= new ClassPathXmlApplicationContext("applicationContext.xml");
但是Spring使用DI,有好幾種方法,比如註解式,利用了語言的功能。
具體解釋參考這篇文章,不翻譯了。
Intro to Inversion of Control and Dependency Injection with Spring
相關文章
- Laravel Dependency Injection (依賴注入) 概念詳解Laravel依賴注入
- .Net DI(Dependency Injection)依賴注入機制依賴注入
- SAP Spartacus 中的依賴注入 Dependency Injection 介紹依賴注入
- .net core 原始碼分析(9) 依賴注入(DI)-Dependency Injection原始碼依賴注入
- 依賴注入?依賴注入是如何實現解耦的?依賴注入解耦
- SpringDI四種依賴注入方式詳解Spring依賴注入
- 詳解 Laravel 中的依賴注入和 IoCLaravel依賴注入
- 一、.Net Core 依賴注入詳解及Autofac使用依賴注入
- spring 詳細講解(ioc,依賴注入,aop)Spring依賴注入
- JavaScript中依賴注入詳細解析JavaScript依賴注入
- 使用ReflectionTestUtils解決依賴注入依賴注入
- angular依賴注入Angular依賴注入
- XUnit 依賴注入依賴注入
- Struts 依賴注入依賴注入
- 依賴倒置(DIP)與依賴注入(DI)依賴注入
- ABAP模擬Java Spring依賴注入(Dependency injection)的一個嘗試JavaSpring依賴注入
- 使用google wire解決依賴注入Go依賴注入
- 詳解.NET依賴注入中物件的建立與“銷燬”依賴注入物件
- 【半小時大話.net依賴注入】(下)詳解AutoFac+實戰Mvc、Api以及.NET Core的依賴注入依賴注入MVCAPI
- ASP.NET Core中的依賴注入(2):依賴注入(DI)ASP.NET依賴注入
- 「轉」Laravel 依賴注入原理(詳細註釋)Laravel依賴注入
- [譯] 依賴注入?? 哈??依賴注入
- Angular 依賴注入原理Angular依賴注入
- .Net Core — 依賴注入依賴注入
- 理解 Angular 依賴注入Angular依賴注入
- Spring依賴注入Spring依賴注入
- Spring依賴注入---Spring依賴注入
- 依賴注入系列教程依賴注入
- 我看依賴注入依賴注入
- webapi - 使用依賴注入WebAPI依賴注入
- 依賴注入是否值得?依賴注入
- 聊聊依賴注入註解@Resource和@Autowired依賴注入
- Swift中依賴注入的解耦策略Swift依賴注入解耦
- 反射、註解與依賴注入總結反射依賴注入
- Spring依賴注入的兩種方式(根據例項詳解)Spring依賴注入
- Asp .Net Core 依賴注入依賴注入
- Spring IOC——依賴注入Spring依賴注入
- 入門系列-依賴注入依賴注入