PHP程序員如何理解IoC/DI(轉)

php - Dependency Injection依賴注入 和 自動加載 各自的優缺點
ioc/di和自動加載時兩回事。

ioc/di 讓代碼由建立對象改成注入對象,是一種編程思想,而自動加載,只是省略reqire文件而已。

ioc/di我認爲有如下好處:

第一,把對象的建立從業務代碼裏抽出來。

第二,統一一個對象的建立方式,避免處處使用本身的方式建立對象。

第三,使用了建造者模式,將某些對象複雜的建造過程封裝起來。

ioc/di我認爲有如下壞處:

第一:濫用使得代碼沒法被跟蹤到,我一個類的一個方法,在項目哪一個地方用到了,ioc/di提供了一種途徑,也是如今不少框架使用的途徑,讓這種代碼已經沒法跟蹤到了。

第二:硬編碼,我傳一個字符串獲取一個對象,若是之後這種對應關係不在或發生變化了怎麼辦。

第三:代碼混亂,你會發現ioc/di不止能夠返回一個對象,一個函數,甚至能夠執行一個命令,而後錘子釘子錘子釘子。。。。。。

 

 

思想

思想是解決問題的根本
思想必須轉換成習慣
構建一套完整的思想體系是開發能力成熟的標誌
——《簡單之美》(前言)php

.html

「成功的軟件項目就是那些提交產物達到或超出客戶的預期的項目,並且開發過程符合時間和費用上的要求,結果在面對變化和調整時有彈性。」
——《面向對象分析與設計》(第3版)P.236程序員

術語介紹

——引用《Spring 2.0 技術手冊》林信良算法

非侵入性 No intrusive

  • 框架的目標之一是非侵入性(No intrusive)數據庫

  • 組件能夠直接拿到另外一個應用或框架之中使用編程

  • 增長組件的可重用性(Reusability)segmentfault

容器(Container)

  • 管理對象的生成、資源取得、銷燬等生命週期設計模式

  • 創建對象與對象之間的依賴關係服務器

  • 啓動容器後,全部對象直接取用,不用編寫任何一行代碼來產生對象,或是創建對象之間的依賴關係。session

IoC

  • 控制反轉 Inversion of Control

  • 依賴關係的轉移

  • 依賴抽象而非實踐

DI

  • 依賴注入 Dependency Injection

  • 沒必要本身在代碼中維護對象的依賴

  • 容器自動根據配置,將依賴注入指定對象

AOP

  • Aspect-oriented programming

  • 面向方面編程

  • 無需修改任何一行程序代碼,將功能加入至原先的應用程序中,也能夠在不修改任何程序的狀況下移除。

分層

表現層:提供服務,顯示信息。
領域層:邏輯,系統中真正的核心。
數據源層:與數據庫、消息系統、事務管理器及其它軟件包通訊。
——《企業應用架構模式》P.14

代碼演示IoC

假設應用程序有儲存需求,若直接在高層的應用程序中調用低層模塊API,致使應用程序對低層模塊產生依賴。

/**
 * 高層
 */
class Business
{
    private $writer;

    public function __construct()
    {
        $this->writer = new FloppyWriter();
    }

    public function save()
    {
        $this->writer->saveToFloppy();
    }
}

/**
 * 低層,軟盤存儲
 */
class FloppyWriter
{
    public function saveToFloppy()
    {
        echo __METHOD__;
    }
}

$biz = new Business();
$biz->save(); // FloppyWriter::saveToFloppy

假設程序要移植到另外一個平臺,而該平臺使用USB磁盤做爲存儲介質,則這個程序沒法直接重用,必須加以修改才行。本例因爲低層變化致使高層也跟着變化,很差的設計。

正如前方提到的

控制反轉 Inversion of Control
依賴關係的轉移
依賴抽象而非實踐

程序不該該依賴於具體的實現,而是要依賴抽像的接口。請看代碼演示

/**
 * 接口
 */
interface IDeviceWriter
{
    public function saveToDevice();
}

/**
 * 高層
 */
class Business
{
    /**
     * @var IDeviceWriter
     */
    private $writer;

    /**
     * @param IDeviceWriter $writer
     */
    public function setWriter($writer)
    {
        $this->writer = $writer;
    }

    public function save()
    {
        $this->writer->saveToDevice();
    }
}

/**
 * 低層,軟盤存儲
 */
class FloppyWriter implements IDeviceWriter
{

    public function saveToDevice()
    {
        echo __METHOD__;
    }
}

/**
 * 低層,USB盤存儲
 */
class UsbDiskWriter implements IDeviceWriter
{

    public function saveToDevice()
    {
        echo __METHOD__;
    }
}

$biz = new Business();
$biz->setWriter(new UsbDiskWriter());
$biz->save(); // UsbDiskWriter::saveToDevice

$biz->setWriter(new FloppyWriter());
$biz->save(); // FloppyWriter::saveToDevice

控制權從實際的FloppyWriter轉移到了抽象的IDeviceWriter接口上,讓Business依賴於IDeviceWriter接口,且FloppyWriter、UsbDiskWriter也依賴於IDeviceWriter接口。

這就是IoC,面對變化,高層不用修改一行代碼,再也不依賴低層,而是依賴注入,這就引出了DI。

比較實用的注入方式有三種:

  • Setter injection 使用setter方法

  • Constructor injection 使用構造函數

  • Property Injection 直接設置屬性

事實上無論有多少種方法,都是IoC思想的實現而已,上面的代碼演示的是Setter方式的注入。

依賴注入容器 Dependency Injection Container

  • 管理應用程序中的『全局』對象(包括實例化、處理依賴關係)。

  • 能夠延時加載對象(僅用到時才建立對象)。

  • 促進編寫可重用、可測試和鬆耦合的代碼。

理解了IoC和DI以後,就引起了另外一個問題,引用Phalcon文檔描述以下:

若是這個組件有不少依賴, 咱們須要建立多個參數的setter方法​​來傳遞依賴關係,或者創建一個多個參數的構造函數來傳遞它們,另外在使用組件前還要每次都建立依賴,這讓咱們的代碼像這樣不易維護

//建立依賴實例或從註冊表中查找
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();

//把實例做爲參數傳遞給構造函數
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);

// ... 或者使用setter

$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);

假設咱們必須在應用的不一樣地方使用和建立這些對象。若是當你永遠不須要任何依賴實例時,你須要去刪掉構造函數的參數,或者去刪掉注入的setter。爲了解決這樣的問題,咱們再次回到全局註冊表建立組件。無論怎麼樣,在建立對象以前,它增長了一個新的抽象層:

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()
    {

        // 得到數據庫鏈接實例
        // 老是返回一個新的鏈接
        $connection = $this->_di->get('db');

    }

    public function someOtherDbTask()
    {

        // 得到共享鏈接實例
        // 每次請求都返回相同的鏈接實例
        $connection = $this->_di->getShared('db');

        // 這個方法也須要一個輸入過濾的依賴服務
        $filter = $this->_di->get('filter');

    }

}

$di = new Phalcon\DI();

//在容器中註冊一個db服務
$di->set('db', function() {
    return new Connection(array(
        "host" => "localhost",
        "username" => "root",
        "password" => "secret",
        "dbname" => "invo"
    ));
});

//在容器中註冊一個filter服務
$di->set('filter', function() {
    return new Filter();
});

//在容器中註冊一個session服務
$di->set('session', function() {
    return new Session();
});

//把傳遞服務的容器做爲惟一參數傳遞給組件
$some = new SomeComponent($di);

$some->someTask();

這個組件如今能夠很簡單的獲取到它所須要的服務,服務採用延遲加載的方式,只有在須要使用的時候才初始化,這也節省了服務器資源。這個組件如今是高度解耦。例如,咱們能夠替換掉建立鏈接的方式,它們的行爲或它們的任何其餘方面,也不會影響該組件。

參考文章

補充

不少代碼背後,都是某種哲學思想的體現。

如下引用《面向模式的軟件架構》卷1模式系統第六章模式與軟件架構

軟件架構支持技術(開發軟件時要遵循的基本原則)

  1. 抽象

  2. 封裝

  3. 信息隱藏

  4. 分離關注點

  5. 耦合與內聚

  6. 充分、完整、簡單

  7. 策略與實現分離

    • 策略組件負責上下文相關決策,解讀信息的語義和含義,將衆多不一樣結果合併或選擇參數值

    • 實現組件負責執行定義完整的算法,不須要做出與上下文相關的決策。上下文和解釋是外部的,一般由傳遞給組件的參數提供。

  8. 接口與實現分離

    • 接口部分定義了組件提供的功能以及如何使用該組件。組件的客戶端能夠訪問該接口。

    • 實現部分包含實現組件提供的功能的實際代碼,還可能包含僅供組件內部使用的函數和數據結構。組件的客戶端不能訪問其實現部分。

  9. 單個引用點

    • 軟件系統中的任何元素都應只聲明和定義一次,避免不一致性問題。
      10. 分而治之

軟件架構的非功能特性

  1. 可修改性

    • 可維護性

    • 可擴展性

    • 重組

    • 可移植性

  2. 互操做性

    • 與其它系統或環境交互

  3. 效率

  4. 可靠性

    • 容錯:發生錯誤時確保行爲正確並自行修復

    • 健壯性:對應用程序進行保護,抵禦錯誤的使用方式和無效輸入,確保發生意外錯誤時處於指定狀態。

  5. 可測試性

  6. 可重用性

    • 經過重用開發軟件

    • 開發軟件時考慮重用

  • php
  • 架構模式
  • 設計模式

  • http://segmentfault.com/a/1190000002411255



    一個IOC的簡單實例:http://www.jb51.net/article/56101.htm
    <?php
    
     class Container
     {
         protected $setings = array();
    
         public function set($abstract, $concrete = null)
         {
             if ($concrete === null) {
                 $concrete = $abstract;
             }
    
             $this->setings[$abstract] = $concrete;
         }
    
         public function get($abstract, $parameters = array())
         {
             if (!isset($this->setings[$abstract])) {
                 return null;
             }
    
             return $this->build($this->setings[$abstract], $parameters);
         }
    
         public function build($concrete, $parameters)
         {
             if ($concrete instanceof Closure) {
                 echo 'Closure';
                 return $concrete($this, $parameters);
             }
             $reflector = new ReflectionClass($concrete);
    
    
             if (!$reflector->isInstantiable()) {
                 throw new Exception("Class {$concrete} is not instantiable");
             }
    
             $constructor = $reflector->getConstructor();
    
             if (is_null($constructor)) {
                 return $reflector->newInstance();
             }
    
             $parameters = $constructor->getParameters();
             $dependencies = $this->getDependencies($parameters);
    
             return $reflector->newInstanceArgs($dependencies);
         }
    
         public function getDependencies($parameters)
         {
             $dependencies = array();
             foreach ($parameters as $parameter) {
                 $dependency = $parameter->getClass();
                 if ($dependency === null) {
                     if ($parameter->isDefaultValueAvailable()) {
                         $dependencies[] = $parameter->getDefaultValue();
                     } else {
                         throw new Exception("Can not be resolve class dependency {$parameter->name}");
                     }
                 } else {
                     $dependencies[] = $this->get($dependency->name);
                 }
             }
    
             return $dependencies;
         }
     }
    
    
    interface MyInterface{}
    class Foo implements MyInterface{}
    class Bar implements MyInterface{}
    class Baz
    {
        public function __construct(MyInterface $foo)
        {
            $this->foo = $foo;
        }
    }
    
    $container = new Container();
    
    $container->set('Baz', 'Baz');
    //$this->settings['Baz']='Baz';
    
    $container->set('MyInterface', 'Foo');
    //$this->settings['MyInterface'] = 'Foo';
    $baz = $container->get('Baz');
    //
    print_r($baz);
    $container->set('MyInterface', 'Bar');
    $baz = $container->get('Baz');
    print_r($baz);
    ?>
相關文章
相關標籤/搜索