1. 程式人生 > >服務容器

服務容器

觸發 ram new 而是 分析 case function mod else

1.依賴

  我們定義兩個類:class Supperman 和 class Power,現在我們要使用Supperman ,而Supperman 依賴了Power

class Supperman {     

  private $power;  
   
  public function __construct(){          
  $this->power = new Power;        
  } 
}

  一旦Power發生了變化,Supperman 不得不修改,這種就叫耦合程度太高,所以面臨的問題是解耦,就需要用到控制反轉.

2.依賴註入

  只要不是由內部生產(比如初始化、構造函數 __construct 中通過工廠方法、自行手動 new 的),而是由外部以參數或其他形式註入的,都屬於 依賴註入(DI)

  方式1、用一個方法把依賴類引入進來。俗稱setter方法。

class Supperman{
public $newPower;
public function setNew($Power){ $this->newPower=$Power; }
public function show(){ $this->newPower->use(); } }

  方式2、用構造器引入依賴類。

class Supperman{
public $newPower;
public function __construct($Power){ $this->newPower=$Power; }
public function show(){ $this->newPower->use(); } }

  方式3、直接設置屬性

class Supperman{
public $newPower=new Power();
public function show(){   $this->newPower->use(); } }

3.下面我們具體來分析

IoC 容器誕生的故事——石器時代(原始模式)

  #超人類

class Superman { 

  protected $power;

  public function __construct() {
  $this->power = new Power(999, 100);
  }
}

  #超能力制造類

class Power { 

  protected $ability;//能力值
  protected $range; //能力範圍

  public function __construct($ability, $range) {
  $this->ability = $ability; $this->range = $range;
  }
}

  “超人”和“超能力”之間不可避免的產生了一個依賴。

  其實超人是有多種超能力:我們將power細分為flight,force,shot

  我們創建了如下類:

class Flight { 

  protected $speed;
  protected $holdtime;

  public function __construct($speed, $holdtime) {
  }
}

class Force {

  protected $force;
  public function __construct($force) {
  }
}

class Shot {

  protected $atk;
  protected $range;
  protected $limit;

  public function __construct($atk, $range, $limit) {
  }
}

  現在我們要實例化一個超人就需要把各種能力都傳給超人,這些都要超人自己做

class Superman {

    protected $power;

    public function __construct() {
        $this->power = new Fight(9, 100); 
        // $this->power = new Force(45); 
        // $this->power = new Shot(99, 50, 2);
    } 
}

IoC 容器誕生的故事——青銅時代(工廠模式)

  上面要實例化很多次,我們現在引入一個工廠

class SuperModuleFactory { 
    public function makeModule($moduleName, $options) { 
      switch ($moduleName) { 
          case ‘Fight‘: return new Fight($options[0], $options[1]); 
          case ‘Force‘: return new Force($options[0]); 
          case ‘Shot‘: return new Shot($options[0], $options[1], $options[2]); 
    } } }

  有了這個工廠,直接調用工廠裏面的方法,超人就不用自己去實例化超能力了

class Superman { 

    protected $power; 

    public function __construct() { 
        // 初始化工廠 $factory = new SuperModuleFactory;
        // 通過工廠提供的方法制造需要的模塊 
        $this->power = $factory->makeModule(‘Fight‘, [9, 100]); 
        // $this->power = $factory->makeModule(‘Force‘, [45]);
        // $this->power = $factory->makeModule(‘Shot‘, [99, 50, 2]); 
    }
}                

  不過這樣和以前好像差不多,只是沒有那麽多new關鍵字,我們稍微改造下超人,就可以很方便的使用超人了

class Superman { 
protected $power;
public function __construct(array $modules) {   // 初始化工廠
     $factory = new SuperModuleFactory;    // 通過工廠提供的方法制造需要的模塊
     foreach ($modules as $moduleName => $moduleOptions) {     $this->power[] = $factory->makeModule($moduleName, $moduleOptions);     }   } } // 創建超人
$superman = new Superman([ ‘Fight‘ => [9, 100], ‘Shot‘ => [99, 50, 2] ]);

  現在我們可以在超人類外面自由控制生產出何種能力的超人了,我們再也不用關心超人類中需要提前寫入需要何種能力了(以前超人類中需要自己new 超能力或者$factory->make超能力),這就是控制反轉

  我們不應該手動在 “超人” 類中固化了他的 “超能力” 初始化的行為,而轉由外部負責,由外部創造超能力模組、裝置或者芯片等(我們後面統一稱為 “模組”),植入超人體內的某一個接口,這個接口是一個既定的,只要這個 “模組” 滿足這個接口的裝置都可以被超人所利用,可以提升、增加超人的某一種能力。這種由外部負責其依賴需求的行為,我們可以稱其為 “控制反轉(IoC)”。

IoC 容器誕生的故事——鐵器時代(依賴註入)

  到此為止我們發現,我們可以很靈活的制造超人,超人對超能力的依賴都轉嫁到工廠中了,但是如果我們的超能力有幾百種或者更多,工廠就會很臃腫,就像下面

class SuperModuleFactory { 
    public function makeModule($moduleName, $options) {
        switch ($moduleName) { 
            case ‘Fight‘: return new Fight($options[0], $options[1]); 
            case ‘Force‘: return new Force($options[0]); 
            case ‘Shot‘: return new Shot($options[0], $options[1], $options[2]);
            // case ‘more‘: ....... 
            // case ‘and more‘: ....... 
            // case ‘and more‘: ....... 
            // case ‘oh no! its too many!‘: ....... 
        } 
    } 
}

  下一步就是我們今天的主要配角 —— DI (依賴註入)

  為了約束超能力,我們制定了超能力的規範,接口SuperModuleInterface,

interface SuperModuleInterface { 
  public function activate(array $target);
}

  各種類型的超能力繼承超能力接口

//超能力X 
class XPower implements SuperModuleInterface {
  public function activate(array $target) {
    // 這只是個例子。。具體自行腦補
}
}
//超能力炸彈
class UltraBomb implements SuperModuleInterface {
  public function activate(array $target) {
    // 這只是個例子。。具體自行腦補
  }
}

  我們再改造一下超人類,所有超能想要超能力,必須遵循超能力制造接口的規範

class Superman { 
  protected $module;
  public function __construct(SuperModuleInterface $module) {     $this->module = $module   } }

  我們現在要制造一個超人

// 超能力模組 
$superModule = new XPower; 
// 初始化一個超人,並註入一個超能力模組依賴 
$superMan = new Superman($superModule);
 

IoC 容器誕生的故事——科技時代(IoC容器)

  以前我們制造超人還要手動,先實例化超能力,再用它實例化超人,既然是科技時代,這顯然是不方便的

  首先我們制造一個容器,容器中有兩個方法,一個是方法是向容器中綁定各種腳本,另一個是利用容器生產超人的方法

class Container { 
  protected $binds; protected $instances;
  //綁定生產腳本 
  public function bind($abstract, $concrete) {     if ($concrete instanceof Closure) {     $this->binds[$abstract] = $concrete;
    } else {
    $this->instances[$abstract] = $concrete;
    }
  }   //生產出超人
  public function make($abstract, $parameters = []) {   if (isset($this->instances[$abstract])) {     return $this->instances[$abstract];
  }
  array_unshift($parameters, $this);   
  return call_user_func_array($this->binds[$abstract], $parameters);
  }
}

  看看我們應該怎樣使用這個容器

// 創建一個容器(後面稱作超級工廠) 
$container = new Container; // 向該 超級工廠 添加 超人 的生產腳本
$container->bind(‘superman‘, function($container, $moduleName) {   return new Superman($container->make($moduleName));
}
); // 向該 超級工廠 添加 超能力模組 的生產腳本
$container->bind(‘xpower‘, function($container) {
  return new XPower;
}); // 同上
$container->bind(‘ultrabomb‘, function($container) {
  return new UltraBomb;
});
// ****************** 華麗麗的分割線 ********************** // 開始啟動生產
$superman_1 = $container->make(‘superman‘, ‘xpower‘);
$superman_2 = $container->make(‘superman‘, ‘ultrabomb‘);
$superman_3 = $container->make(‘superman‘, ‘xpower‘);
// ...隨意添加

  通過最初的 綁定(bind) 操作,我們向 超級工廠 註冊了一些生產腳本,這些生產腳本在生產指令下達之時便會執行。發現沒有?我們徹底的解除了 超人 與 超能力模組 的依賴關系,更重要的是,容器類也絲毫沒有和他們產生任何依賴!我們通過註冊、綁定的方式向容器中添加一段可以被執行的回調(可以是匿名函數、非匿名函數、類的方法)作為生產一個類的實例的 腳本 ,只有在真正的 生產(make) 操作被調用執行時,才會觸發。

  這樣一種方式,使得我們更容易在創建一個實例的同時解決其依賴關系,並且更加靈活。當有新的需求,只需另外綁定一個“生產腳本”即可。

在laravel框架中如何綁定和解析

  幾乎所有的服務容器綁定都會註冊至服務提供者(下一篇總結中會介紹)中

第一,通過綁定服務ID的方式定義的服務,只能通過服務ID來獲取

singleton 方法綁定一個只會被解析一次的類或接口至容器中,且後面的調用都會從容器中返回相同的

// 綁定方式:定義服務

//實例:
$this->app->singleton(‘service_id‘, function () { 
    return new Service(); 
}) 
// 獲取服務,以下方式等價 
$this->app->make(‘service_id‘); $this->app[‘service_id‘];

第二,你想通過接口的類型提示(Type Hint)來自動解析並註入服務

需要像下邊這樣:bind 方法註冊一個綁定,傳遞我們希望註冊的類或接口名稱,並連同返回該類實例的閉包:

// 綁定方式:綁定接口到實例

$this->app->bind(‘Namespace\To\Your\Interface\Pay‘, ‘Namespace\To\Your\Class\Weixin‘);

//解析方式:服務容器(Service Container)

//如果你需要註入服務的對象是通過服務容器(Service Container)來解析的,才可以使用類型提示(Type Hint)來進行自動解析並註入服務。

第三,上下文綁定

像上邊這樣,通過綁定接口到實例,只能自動解析為一個實例,也就是你綁定的實例;如果你想要的結果是,不同的類,構造器通過類型提示(Type Hint)相同的接口,註入不同的實例,可以像下邊這樣(上下文綁定,Context Binding):

$this->app->when(‘Namespace\To\Your\Class\A‘) ->needs(‘Namespace\To\Your\Interface\Pay‘) ->give(‘Namespace\To\Your\Class\Weixin‘); 

$this->app->when(‘Namespace\To\Your\Class\B‘) ->needs(‘Namespace\To\Your\Interface\Pay‘) ->give(‘Namespace\To\Your\Class\Ali‘);

通過上下文綁定,A的實例會註入Weixin的實例,而B的實例會註入Ali的實例。像下邊:

class A { 
    public functions __construct(Pay $pay) { 
        var_dump($pay instanceof Weixin); // True 
    } 
}
class B {
    public functions __construct(Pay $pay) {
        var_dump($pay instanceof Ali); // True
    }
}

服務容器