PHP 依賴註入和控制反轉再談(二)
今天有個朋友看到yii2中介紹的依賴註入一頭霧水,之前我寫過類似的文章發給他看了,可能還沒深入理解吧,這裏我再通俗點描述下依賴註入的原理吧,盡可能滴說通俗易懂一點吧:
先還是扯下概念性滴問題(概念問題我個人的原則總是先簡單瞟一眼概念,通過實例來對概念加深理解了)
要想理解 PHP 依賴註入 和 控制反轉 兩個概念,我們還是必須搞清楚下面的兩個問題:
- DI —— Dependency Injection 依賴註入
- IoC —— Inversion of Control 控制反轉
什麽是依賴註入
沒有工作就沒有錢,沒有錢我就活不下去,那麽,工作和錢就是我的依賴,有錢我才能勉強滴好好的活下去,再說白了就是:
東西不是我自己的,都是我所需要的。一切需要外部提供的,都是需要進行依賴註入的。
依賴註入舉例
<?php class Work { protected $money=5000.0;//底薪5000 public function __construct(Money $money) { $this->money = $money; echo ‘i need money ‘; } } class Money { } $work = new Work();//輸出發現
Fatal error: Uncaught TypeError: Argument 1 passed to Work::__construct() must be an instance of Money, none given, called in D:\web\test\do.php on line 15 and defined in D:\web\test\do.php:5 Stack trace: #0 D:\web\test\do.php(15): Work->__construct() #1 {main} thrown in D:\web\test\do.phpon line 5
從上述代碼我們可以看到Work強依賴Money
必須在構造時註入Money
的實例才行。我們改成如下:
<?php class Work { protected $money=5000.0;//底薪 public function __construct(Money $money) { $this->money = $money; echo ‘i need money ‘; } } class Money { } //$work = new Work(); // 所以,工作必須要給他錢才行 $money = new Money();$work = new Work($money); //輸出 i need money
那麽為什麽要有依賴註入
這個概念,依賴註入
到底解決了什麽問題?我們將上述代碼修正一下我們初學時都寫過的代碼:
<?php class Work { protected $money=5000.0;//底薪5000 public function __construct() { $this->money = new Money(); } }
這種方式與前面的方式有什麽不同呢?比如某天我去了百度了底薪6000,我們會發現每次Work
重生一次money.比如某天Work升職加薪到6k啦
,怎麽辦?重生自己...把Money
丟掉...把 6k帶進去...
<?php class BaiduMoney { } class Work { protected $money=5000.0; public function __construct() { // $this->money = new Money(); $this->money = new BaiduMoney(); } }
某天 Work想去BAT
....work
好煩...老換工作是不是感覺不太好?每次幹不久,待的不長人卻要這麽的折磨自己...Work
說,我要變的強大一點。我不想被改來改去的!好吧,我們讓Work
強大一點:
<?php interface Money { } class baiduMoney implements Money { } class Alibaba implements Money { } class Work { protected $money=5000.0; public function __construct(Money $money) { $this->money = $money; } } $baidu= new baiduMoney(); $alibaba = new Alibaba(); $boy = new Work($baidu); $boy = new Work($alibaba);
終於可以去BAT體驗不同的人生了......
依賴註入方式
1、構造器 註入
<?php class Book { private $db_conn; public function __construct($db_conn) { $this->db_conn = $db_conn; } }
2、setter 註入
<?php class Book { private $db; private $file; function setdb($db) { $this->db = $db; } function setfile($file) { $this->file = $file; } } class file { } class db { } class test { $book = new Book(); $book->setdb(new db()); $book->setfile(new file()); }
小結:
因為大多數應用程序都是由兩個或者更多的類通過彼此合作來實現業務邏輯,這使得每個對象都需要獲取與其合作的對象(也就是它所依賴的對象)的引用。如果這個獲取過程要靠自身實現,那麽將導致代碼高度耦合並且難以維護和調試。
所以才有了依賴註入的概念,依賴註入解決了以下問題:
- 依賴之間的解耦
- 單元測試,方便Mock
上面介紹的兩種方法代碼很清晰,但是當我們需要註入很多個依賴時,意味著又要增加很多行,會比較難以管理。
比較好的解決辦法是 建立一個class作為所有依賴關系的container,在這個class中可以存放、創建、獲取、查找需要的依賴關系。我們還是先來了解一下IOC的概念(又要玩概念了,不急,看代碼說明一切額)
控制反轉 (Inversion Of Control, IOC)
控制反轉 是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。其中最常見的方式叫做 依賴註入(Dependency Injection, DI), 還有一種叫"依賴查找"(Dependency Lookup)。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用傳遞給它。也可以說,依賴被註入到對象中。
<?php class Ioc { protected $db_conn; public static function make_book() { $new_book = new Book(); $new_book->set_db(self::$db_conn); //... //... //其他的依賴註入 return $new_book; } }
此時,如果獲取一個book實例,只需要執行$newone = Ioc::makebook();
以上是container的一個具體實例,最好還是不要把具體的某個依賴註入寫成方法,采用registry註冊,get獲取比較好
<?php /** * 控制反轉類 */ class Ioc { /** * @var array 註冊的依賴數組 */ protected static $registry = array(); /** * 添加一個 resolve (匿名函數)到 registry 數組中 * * @param string $name 依賴標識 * @param Closure $resolve 一個匿名函數,用來創建實例 * @return void */ public static function register($name, Closure $resolve) { static::$registry[$name] = $resolve; } /** * 返回一個實例 * * @param string $name 依賴的標識 * @return mixed * @throws \Exception */ public static function resolve($name) { if (static::registered($name)) { $name = static::$registry[$name]; return $name(); } throw new \Exception("Nothing registered with that name"); } /** * 查詢某個依賴實例是否存在 * * @param string $name * @return bool */ public static function registered($name) { return array_key_exists($name, static::$registry); } }
現在就可以通過如下方式來註冊和註入一個
<?php Ioc::register("book", function () { $book = new Book(); $book->setdb(‘db‘); $book->setfile(‘file‘); return $book; }); // 註入依賴 $book = Ioc::resolve(‘book‘);
問題匯總
1、參與者都有誰?
答:一般有三方參與者,一個是某個對象;一個是IoC/DI的容器;另一個是某個對象的外部資源。又要名詞解釋一下,某個對象指的就是任意的、普通的Java對象; IoC/DI的容器簡單點說就是指用來實現IoC/DI功能的一個框架程序;對象的外部資源指的就是對象需要的,但是是從對象外部獲取的,都統稱資源,比如:對象需要的其它對象、或者是對象需要的文件資源等等。
2、依賴:誰依賴於誰?為什麽會有依賴?
答:某個對象依賴於IoC/DI的容器。依賴是不可避免的,在一個項目中,各個類之間有各種各樣的關系,不可能全部完全獨立,這就形成了依賴。傳統的開發是使用其他類時直接調用,這會形成強耦合,這是要避免的。依賴註入借用容器轉移了被依賴對象實現解耦。
3、註入:誰註入於誰?到底註入什麽?
答:通過容器向對象註入其所需要的外部資源
4、控制反轉:誰控制誰?控制什麽?為什麽叫反轉?
答:IoC/DI的容器控制對象,主要是控制對象實例的創建。反轉是相對於正向而言的,那麽什麽算是正向的呢?考慮一下常規情況下的應用程序,如果要在A裏面使用C,你會怎麽做呢?當然是直接去創建C的對象,也就是說,是在A類中主動去獲取所需要的外部資源C,這種情況被稱為正向的。那麽什麽是反向呢?就是A類不再主動去獲取C,而是被動等待,等待IoC/DI的容器獲取一個C的實例,然後反向的註入到A類中。
5、依賴註入和控制反轉是同一概念嗎?
答:從上面可以看出:依賴註入是從應用程序的角度在描述,可以把依賴註入描述完整點:應用程序依賴容器創建並註入它所需要的外部資源;而控制反轉是從容器的角度在描述,描述完整點:容器控制應用程序,由容器反向的向應用程序註入應用程序所需要的外部資源。
PHP 依賴註入和控制反轉再談(二)