一 什麼是依賴注入?

此文是本人翻譯的來自國外某網站一篇文章 What is Dependency Injection?,第一次翻譯,各位見諒php

這篇文章是一系列關於依賴注入和PHP輕量級容器實現文章中的一部分: Part 1: What is Dependency Injection? Part 2: Do you need a Dependency Injection Container? Part 3: Introduction to the Symfony Service Container Part 4: Symfony Service Container: Using a Builder to create Services Part 5: Symfony Service Container: Using XML or YAML to describe Services Part 6: The Need for Speedhtml

今天,我一開始不會講容器,我但願先經過一些具體的實例來介紹一下依賴注入的理念以及其所嘗試解決的問題和它能給開發者帶來的好處。若是你已經瞭解依賴注入,你能夠跳過這篇文章去看下一篇。 依賴注入多是我知道的最簡單的設計模式之一,極可能你已經使用過,可是同時也是最難解釋的,緣由多是大多數介紹依賴注入的文章用的例子都比較無聊。我想了一個比較適合PHP領域的例子,由於PHP主要用在web開發,因此讓咱們來看一個簡單的web實例。 爲了解決http協議無狀態的問題,web應用須要一種在web請求之間記錄用戶信息的方法,簡單的經過cookie或者session都能解決:web

$_SESSION['language'] = 'fr';
複製代碼

上面的代碼把用戶的語言存在了session變量裏面。這樣,對於同一個用戶的請求,其所使用的語言就會被存儲在$_SESSION數組裏面,咱們能夠這樣獲取:數據庫

$user_language = $_SESSION['language'];
複製代碼

因爲依賴注入只在面向對象的世界裏有意義,咱們僞裝咱們有一個叫SessionStorage的類封裝了處理session的方法:設計模式

class SessionStorage
{
  function __construct($cookieName = 'PHP_SESS_ID')
  {
    session_name($cookieName);
    session_start();
  }
  function set($key, $value)
  {
    $_SESSION[$key] = $value;
  }
  function get($key)
  {
    return $_SESSION[$key];
  }
  // ...
}
複製代碼

...和一個提供高級接口的易用的User類數組

class User
{
  protected $storage;

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

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

  function getLanguage()
  {
    return $this->storage->get('language');
  }

  // ...
}
複製代碼

這些類足夠簡單,使用User類也很是容易:緩存

$user = new User();
$user->setLanguage('fr');
$user_language = $user->getLanguage();
複製代碼

到目前爲止,一切都很好...除非你想要更多的靈活性。萬一你想要改變session裏面的cookie名字呢?你可能會使用下面這些方法:bash

  1. 在SessionStorage構造器裏面硬編碼名字
class User
    {
      function __construct()
      {
        $this->storage = new SessionStorage('SESSION_ID');
      }

      // ...
    }
複製代碼
  1. 在User類外面定義一個常量
define('STORAGE_SESSION_NAME', 'SESSION_ID');
 class User
    {
      function __construct()
      {
        $this->storage = new SessionStorage(STORAGE_SESSION_NAME);
      }

      // ...
    }
複製代碼
  1. 在User類構造器裏面傳遞一個名字做爲參數
class User
    {
      function __construct($sessionName)
      {
        $this->storage = new SessionStorage($sessionName);
      }

      // ...
    }

    $user = new User('SESSION_ID');
複製代碼
  1. 在User類構造器裏面傳遞一個數組選項
class User
    {
      function __construct($storageOptions)
      {
        $this->storage = new SessionStorage($storageOptions['session_name']);
      }

      // ...
    }

    $user = new User(array('session_name' => 'SESSION_ID'));
複製代碼

以上的全部選擇都很爛,硬編碼名字沒有真正解決問題由於你之後可能隨時會改變注意,你還得更改User類。使用常量也是一個壞注意,由於你又依賴了一個常量。經過傳遞一個數組參數多是一個好的解決方案,可是依然不太好,它把User構造器和一個和它自己不相關的東西耦合了。 並且還有一個問題無法容易搞定:我怎麼換掉SessionStorage類?比方說,用一個mock對象去測試,或者你想把session保存在數據庫或內存裏面。在目前的代碼裏面除非你更改User類,不然沒法實現。cookie

###依賴注入 不要在User類裏面建立SessionStorage對象,咱們在類外面建立SessionStorage對象,而後經過構造函數把其做爲參數傳進來:session

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

  // ...
}
複製代碼

這就是依賴注入,就是這些!

$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
複製代碼

如今,配置一個session存儲對象很是簡單了,替換它也很容易,不用改變User類也能夠實現其餘功能。  Pico Container website 這樣形容依賴注入:「依賴注入就是經過構造器、方法、屬性獲取所須要的元素」 依賴注入不單單侷限於此:

  • 構造器注入:
class User
    {
      function __construct($storage)
      {
        $this->storage = $storage;
      }

      // ...
    }
複製代碼
  • Setter注入:
class User
    {
      function setSessionStorage($storage)
      {
        $this->storage = $storage;
      }

      // ...
    }
複製代碼
  • 屬性注入:
class User
    {
      public $sessionStorage;
    }

    $user->sessionStorage = $storage;
複製代碼

通常來講,構造器注入最適合必要依賴,就像例子裏面那樣,Setter注入比較適合可選依賴,好比說緩存對象。當今,不少現代PHP框架大量使用依賴注入提供一系列既解耦又有凝聚力的組件:

// symfony: A constructor injection example
$dispatcher = new sfEventDispatcher();
$storage = new sfMySQLSessionStorage(array('database' => 'session', 'db_table' => 'session'));
$user = new sfUser($dispatcher, $storage, array('default_culture' => 'en'));
// Zend Framework: A setter injection example
$transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
  'auth'     => 'login',
  'username' => 'foo',
  'password' => 'bar',
  'ssl'      => 'ssl',
  'port'     => 465,
));
$mailer = new Zend_Mail();
$mailer->setDefaultTransport($transport);
複製代碼

若是你想了解更多關於依賴注入的東西,我強烈建議你讀一讀Martin Fowler introduction 或者 Jeff More presentation。你也能夠看看我去年關於依賴注入的演講,這裏講了更多細節

好了,就說這麼多了,我但願你如今對依賴注入有更好的理解,本系列的下一章我會講關於依賴注入容器

相關文章
相關標籤/搜索