PHP程序員如何理解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. 可重用性

    • 經過重用開發軟件

    • 開發軟件時考慮重用

相關文章
相關標籤/搜索