iT邦幫忙

6

相依性注入(Dependency Injection)的一點點心得

  • 分享至 

  • xImage
  •  

這份文章參考自

大陸網站:
http://computer.jges.mlc.edu.tw/index.php/zend-framework-2/76-di-dependency-injection
http://hi.baidu.com/thinkinginlamp/item/41c9bff024f03dcf531c26e6

外國人簡報
http://www.slideshare.net/fabpot/dependency-injection-with-php-and-php-53
<- 收穫很大

石頭大的文章
http://blog.roodo.com/rocksaying/archives/13454601.html
<- 最先爬到,但最慢看懂

===分隔線===

要了解DI,首先我們先假設一個情境

我們有一個class 叫作 User

那想當然我們使用的時候會產生一個實例 $user = new User() ;

但是這個user物件裡面牽涉很廣,可能有資料庫的連線,可能會有SESSION機制的存取

都可能會把總總要用到的物件,存進user這個類的特性。

class User
{
    protected $storage ;

    function __construct()
    {
        $this->storage = new SessionStorage() ;
    }
    
    function setLanguage($language)
    {
        $this->storage->set('language', language) ;
    }
}

這樣會有一個問題,就是耦合性太高

想像如果你有一百支程式有用到SessionStorage這個類

而你新寫了一個SessionStorage2,那也許,你會必須改一百支程式。

另外一個說法是,太早耦合。

而相依性注入的強大優點就是低耦合,可以解決這樣的問題。

class User
{
    protected $storage ;

    function __construct($storage)
    {
        $this->storage = $storage ;
    }

}

$storage = new SessionStorage() ;
$user = new User($storage) ;

我們來了解一下為甚麼一開始那一段程式還有甚麼問題?

class User
{
    protected $storage ;

    function __construct()
    {
        $this->storage = new SessionStorage('SESSION_ID') ; <- 要改了~
    }
    
    function setLanguage($language)
    {
        $this->storage->set('language', language) ;
    }
}

也許SessionStorage需要改一個參數,喔不,你又要改一百支程式了。

恩...我們會怎麼解決呢?

define('SESSION_NAME', 'SESSION_ID') ;
$user = new User() ;

弄一個全域變數???

$user = new User('SESSION_ID') ;

恩,為了一個特性(物件)的一個特性....你覺得好嗎?
不好的地方是在SESSION_ID跟User不該有那麼直接的聯貫

再說,如果你想要改變SESSION的儲存的方式呢?可能放到檔案、可能放到MYSQL、可能放到記憶體
你會不會又,被迫寫下面的CODE

$user = new User('SESSION_ID', 'mysql') ;

<h3>所以這樣真的不太好!</h3>

所以孩子,放棄吧<del datetime="2013-11-14T06:50:43+00:00">去賣雞排吧</del>

<h3>以後就是這樣的生活了嗎?!</h3>

$storage = new MySQLSessionStorage('SESSION_ID') ;  
//我假設MySQLSessionStorage繼承了SessionStorage,並override了一些方法
$user = new User($storage) ;

把你要的成員傳進去就對了,而這,就是相依性注入。就是那麼簡單。

<h3>讓我們更精進一點</h3>

class User
{
    protected $storage ;
    
    //你可以限制它來自哪種型別,放class應該也是合邏輯的
    function __construct(SessionStorageInterface $storage)
    {
        $this->storage = $storage ;
    }
}

interface SessionStorageInterface
{
    function get($key) ;
    function set($key, $value) ;
}

class MySQLSessionStorage implements SessionStorageInterface
{
    protected $data = array() ;
    
    static function get($key)
    {
        return self::$data[$key];
    }
    
    static function set($key, $val)
    {
        self::$data[$key] = $value ;
    }    
}

讓我們回到原點一下,我們解決了甚麼問題?

我們使用相依性注入,減少對User class的耦合

於是我們可以針對storage進行改變而不用修改User的任何一行

而相依性注入不一定要使用__construct

我們來看看還有哪些方式?

$storage = new SessionStorage() ;

//constructor
$user = new User($storage) ;

//setter injection
$user = new User() ;
$user->setStorage($storage) ;

//property injection
$user = new User() ;
$user->storage = $storage ;

這~大概就是依賴注入的一些IDEA了。

但是,我們的確還是可以繼續往前延伸...

為了不影響User 類別,我們使用了依賴性注入

於是我們有了下面的程式碼

$storage = new SessionStorage('SESSION_ID') ;
$user = new User($storage) ;

//而且在user的constructor裡頭,你還是要$this-storage = $storage ;

但我們不希望我們每次要用到User的時候

就要上面寫一堆,除了SESSION,我們可以還要用到DB連線、LOG記錄等等的

( 然後在建構子裡面還要設定,如果很多的話,那不是產生一個User都要帶一堆參數? )

最好,還是一行可以搞定吧!!

$user = ( some magic can return user object )

為了達到這個目的,我們需要一個工具,我們叫它 - Container

Container在設計的時候要注意下面兩點

1." 裡面要被注入的類別,是不可以得知自己被container控制的狀況。
保持這樣的概念,可以確保屆時要在container進行置換的時候不會有問題。"

2." container可依照接收到的參數(是一個類),幫那個類的實例掛載property "

基本邏輯是這樣,Container是一個類,你告訴他你要產生User這個類的實例

然後他會幫你連User的特性都設定好(storage、dbh那些...)

最後把實例丟給變數$user接收。

class Container
{
    protected $vlaues = array() ;
    
    function __set($id, $value)
    {
        $this->values[$id] = $value ;
    }
    
    function __get($id)
    {
        if (is_callable($this->values[$id]))
        {
            return $this->values[$id]($this) ;
        }
        else
        {
            return $this->values[$id] ;
        }
    }
}

$container = new Container() ;
$contianer->session_name = 'SESSION_ID' ;
// $this->values['session_name'] = 'SESSION_ID' ;

$contianer->storage_class = 'SessionStorage' ;
// $this->values['storage_class'] = 'SessionStorage' ;

$container->user = function($c)  //假設叫作fn1
{
    return new User($c->storage) ;
} ;
// $this->values['user'] = closure fn ;

$container->storage = function($c)  //假設叫作fn2
{
    return new $c->storage_class($c->session_name) ;
}
// $this->values['storage'] = closure fn ;

$user = $container->user ;
//會觸發fn1,然後會去觸發參數$c->storage
//於是又觸發fn2,然後觸發$c->storage_class,去設定storage_class這個property

/*
這裡有點太精妙了建議看投影片從36頁開始看...
*/

不過這樣一來,我們就達到我們的目的了

不過這寫法實在太神了。

反而在設定的時候不是很好懂。所以,應該要寫一個版本

可能container類寫的複雜一點,但是設定的時候可以呆瓜一點。

總之,如果還想繼續了解的話,可以去看石頭大大那邊,也是不錯的實作。


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
總裁
iT邦好手 1 級 ‧ 2013-11-15 10:30:44

同學,請用BBCODE,請參考樣式使用說明

感謝兄台
我都是習慣在自己的地方寫一寫然後貼過來
有時候有點MISS,我會看你貼的東西

0
fillano
iT邦超人 1 級 ‧ 2013-11-15 14:43:29

3Q了

一言難盡啊...那麼基本的觀念居然那麼晚才知道

感謝你的回文。
哈哈哈哈

fillano iT邦超人 1 級 ‧ 2013-11-16 04:03:57 檢舉
0
slime
iT邦大師 1 級 ‧ 2013-11-15 23:59:16

最近在看一本"悅知文化"的"軟體構築美學", 裏面有整理一些相關的建議, 可以參考看看.

我要留言

立即登入留言