> 依賴倒置和控制反轉是一種編程思想,而依賴注入就是通過服務容器實現這種面向接口或者是面向抽象編程的思想
## 概念理解
### 依賴倒置原則
依賴倒置是一種軟件設計思想,在傳統軟件中,上層代碼依賴于下層代碼,當下層代碼有所改動時,上層代碼也要相應進行改動,因此維護成本較高。而依賴倒置原則的思想是,**上層不應該依賴下層,應依賴接口**。意為上層代碼定義接口,下層代碼實現該接口,從而使得下層依賴于上層接口,降低耦合度,提高系統彈性
### 控制反轉
當調用者需要被調用者的協助時,在傳統的程序設計過程中,通常由調用者來創建被調用者的實例,但在這里,創建被調用者實例的工作不再由調用者來完成,而是將被調用者的創建移到調用者的外部,從而反轉被調用者的創建,消除了調用者對被調用者創建的控制,因此稱為控制反轉。
要實現控制反轉,通常的解決方案是將創建被調用者實例的工作交由 IoC 容器來完成,然后在調用者中注入被調用者(通過構造器/方法注入實現),這樣我們就實現了調用者與被調用者的解耦,該過程被稱為依賴注入。
依賴注入不是目的,它是一系列工具和手段,最終的目的是幫助我們開發出松散耦合(loose coupled)、可維護、可測試的代碼和程序。這條原則的做法是大家熟知的**面向接口,或者說是面向抽象編程**。
> 通俗的說,在調用一個對象的方法,首先要實例化對象之后。 而所謂的注入,就是一種工廠模式的升華。由一個更高級的工廠(容器),來完成對象實例化,實現調用者與被調用者的解耦
## 解決什么問題
### 實現調用者與被調用者的解耦
>[info] 所謂的上層代碼依賴于**接口**,就是業務邏輯的實現是跳過了具體對象的抽象行為。比如我們要對用戶發消息,可以通過郵件發送,也可以通過短信發送。上層代碼不用關注其用什么發送,只發送即可(適配器模式)
```PHP
interface Mail
{
public function send();
}
class Email implements Mail
{
public function send()
{
echo '發送郵件' . PHP_EOL;
}
}
class SmsMail implements Mail
{
public function send()
{
echo '發送短信' . PHP_EOL;
}
}
// 注冊容器
class Register
{
private $_mailObj;
// 構造函數里面已經約束了必須是實現了Mail接口的類的實例
public function __construct(Mail $mailObj)
{
$this->_mailObj = $mailObj;
}
public function doRegister()
{
// 一定會有send方法
$this->_mailObj->send();//發送信息
}
}
$emailObj = new Email();
$smsObj = new SmsMail();
$reg = new Register($emailObj);
$reg->doRegister();//使用email發送
$reg = new Register($smsObj);
$reg->doRegister($smsObj);//使用短信發送
```
使用構造函數注入的方法,使得它只依賴于發送短信的接口,只要實現其接口中的'send'方法,不管你什么方式發送都可以。上面通過構造函數注入對象的方式,就是最簡單的依賴注入;當然"注入"不僅可以通過構造函數注入,也可以通過屬性注入,上面你可以通過一個"setter"來動態為"mailObj"這個屬性賦值。
### 通過php反射機制實現自動注入
真實的dependency injection container會提供更多的特性,如
>[info] 1. 自動綁定(Autowiring)或 自動解析(Automatic Resolution)
> 2. 注釋解析器(Annotations)
> 3. 延遲注入(Lazy injection)
```php
<?php
class C
{
public function doSomething()
{
echo __METHOD__ , '我是周伯通C|';
}
}
class B
{
private $c;
public function __construct(C $c)
{
$this->c = $c;
}
public function doSomething()
{
$this->c->doSomething();
echo __METHOD__ , '我是周伯通B|';
}
}
class A
{
private $b;
public function __construct(B $b)
{
$this->b = $b;
}
public function doSomething()
{
$this->b->doSomething();
echo __METHOD__ , '我是周伯通A|';;
}
}
class Container
{
private $s = [];
public function __set($k , $c)
{
$this->s[$k] = $c;
}
public function __get($k)
{
return $this->build($this->s[$k]);
}
/**
* 自動綁定(Autowiring)自動解析(Automatic Resolution)
* @param string $className
* @return object
* @throws Exception
*/
public function build($className)
{
// 如果是匿名函數(Anonymous functions),也叫閉包函數(closures)
if ($className instanceof Closure) {
// 執行閉包函數,并將結果
return $className($this);
}
if(!class_exists($className)){
throw new Exception("{$className} class is not exists");
}
/** @var ReflectionClass $reflector */
$reflector = new ReflectionClass($className);
// 檢查類是否可實例化, 排除抽象類abstract和對象接口interface
if (!$reflector->isInstantiable()) {
throw new Exception("Can't instantiate this.");
}
/** @var ReflectionMethod $constructor 獲取類的構造函數 */
$constructor = $reflector->getConstructor();
// 若無構造函數,直接實例化并返回, (注意! 此處退出遞歸1)
if (is_null($constructor)) {
return new $className;
}
// 取構造函數參數,通過 ReflectionParameter 數組返回參數列表
$parameters = $constructor->getParameters();
// 遞歸解析構造函數的參數
$dependencies = $this->getDependencies($parameters);
// 創建一個類的新實例,給出的參數將傳遞到類的構造函數。
return $reflector->newInstanceArgs($dependencies);
}
/**
* @param array $parameters
* @return array
* @throws Exception
*/
public function getDependencies($parameters)
{
$dependencies = [];
/** @var ReflectionParameter $parameter */
foreach ($parameters as $parameter) {
/** @var ReflectionClass $dependency */
$dependency = $parameter->getClass();
if (is_null($dependency)) {
// 是變量,有默認值則設置默認值 (注意,此處退出遞歸2)
$dependencies[] = $this->resolveNonClass($parameter);
} else {
// 是一個類,遞歸解析
$dependencies[] = $this->build($dependency->name);
}
}
return $dependencies;
}
/**
* @param ReflectionParameter $parameter
* @return mixed
* @throws Exception
*/
public function resolveNonClass($parameter)
{
// 有默認值則返回默認值
if ($parameter->isDefaultValueAvailable()) {
return $parameter->getDefaultValue();
}
throw new Exception('I have no idea what to do here.');
}
}
/*// example1
$container = new Container();
$container->b = 'B';
$container->a = function ($container){
return new A($container->b);
};
// 從容器中取得A
$model = $container->a;
// output: C::doSomething我是周伯通C|B::doSomething我是周伯通B|A::doSomething我是周伯通A|
// 實現依賴自動注入
$model->doSomething();*/
// example2
$di = new Container();
$di->php7 = 'A'; // 自動注入classA
/** @var A $php7 */
$foo = $di->php7;
$foo->doSomething(); //C::doSomething我是周伯通C|B::doSomething我是周伯通B|A::doSomething我是周伯通A|
```
參考:
https://www.cnblogs.com/painsOnline/p/5138806.html
https://www.cnblogs.com/phpper/p/7781810.html
- 現代化PHP特性
- php7常用特性整理
- 反射機制Reflection
- 依賴注入與服務容器
- 抽象類與接口
- 類多繼承的替代方案Traits
- 類的延遲綁定(后期綁定)
- 生成器語法
- 匿名函數和閉包
- 匿名類
- 理解php的output buffer
- 斷言ASSERT
- 魔術方法小結
- Zend Opcache字節碼緩存
- 內置的http服務器
- SPL標準庫
- 【SPL標準庫專題(1)】SPL簡介
- 【SPL標準庫專題(2)】Iterator
- 【SPL標準庫專題(3)】Classes
- 【SPL標準庫專題(4)】Exceptions
- 【SPL標準庫專題(5)】Datastructures:SplDoublyLinkedList
- 【SPL標準庫專題(6)】Datastructures:SplStack & SplQueue
- 【SPL標準庫專題(7)】Datastructures:SplPriorityQueue
- 【SPL標準庫專題(8)】Datastructures:SplHeap & SplMaxHeap & SplMinHeap
- 【SPL標準庫專題(9)】Datastructures:SplFixedArray
- 【SPL標準庫專題(10)】Datastructures:SplObjectStorage
- PHPcomposer使用手札[ing]
- PHP中的多態
- 通過命名空間實現自動加載的框架雛形
- 日期與金額
- PHPstorm使用攻略
- 筆記本