【modernPHP專題(3)】依賴注入與服務容器

依賴倒置和控制反轉是一種編程思想,而依賴注入就是經過服務容器實現這種面向接口或者是面向抽象編程的思想

概念理解

依賴倒置原則

依賴倒置是一種軟件設計思想,在傳統軟件中,上層代碼依賴於下層代碼,當下層代碼有所改動時,上層代碼也要相應進行改動,所以維護成本較高。而依賴倒置原則的思想是,上層不該該依賴下層,應依賴接口。意爲上層代碼定義接口,下層代碼實現該接口,從而使得下層依賴於上層接口,下降耦合度,提升系統彈性php

控制反轉

當調用者須要被調用者的協助時,在傳統的程序設計過程當中,一般由調用者來建立被調用者的實例,但在這裏,建立被調用者實例的工做再也不由調用者來完成,而是將被調用者的建立移到調用者的外部,從而反轉被調用者的建立,消除了調用者對被調用者建立的控制,所以稱爲控制反轉。html

要實現控制反轉,一般的解決方案是將建立被調用者實例的工做交由 IoC 容器來完成,而後在調用者中注入被調用者(經過構造器/方法注入實現),這樣咱們就實現了調用者與被調用者的解耦,該過程被稱爲依賴注入。編程

依賴注入不是目的,它是一系列工具和手段,最終的目的是幫助咱們開發出鬆散耦合(loose coupled)、可維護、可測試的代碼和程序。這條原則的作法是你們熟知的面向接口,或者說是面向抽象編程數組

通俗的說,在調用一個對象的方法,首先要實例化對象以後。 而所謂的注入,就是一種工廠模式的昇華。由一個更高級的工廠(容器),來完成對象實例化,實現調用者與被調用者的解耦

解決什麼問題

實現調用者與被調用者的解耦

[info] 所謂的上層代碼依賴於 接口,就是業務邏輯的實現是跳過了具體對象的抽象行爲。好比咱們要對用戶發消息,能夠經過郵件發送,也能夠經過短信發送。上層代碼不用關注其用什麼發送,只發送便可(適配器模式)
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"這個屬性賦值。php7

經過php反射機制實現自動注入

真實的dependency injection container會提供更多的特性,如閉包

  1. 自動綁定(Autowiring)或 自動解析(Automatic Resolution)
  2. 註釋解析器(Annotations)
  3. 延遲注入(Lazy injection)
<?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|

參考:ide

https://www.cnblogs.com/pains...
https://www.cnblogs.com/phppe...函數

相關文章
相關標籤/搜索