PHP中的服務容器與依賴注入的思想

舊夢發癲發表於2019-02-16

依賴注入

當A類需要依賴於B類,也就是說需要在A類中例項化B類的物件來使用時候,如果B類中的功能發生改變,也會導致A類中使用B類的地方也要跟著修改,導致A類與B類高耦合。這個時候解決方式是,A類應該去依賴B類的介面,把具體的類的例項化交給外部。

就拿我們業務中常用的通知模組來說。

<?php

/**
 * 定義了一個訊息類
 * Class Message 
 */
class  Message{

  public function seed()
  {
      return `seed email`;

  }
}
/*
 * 訂單產生的時候 需要傳送訊息
 */
class Order{

    protected $messager = ``;

    function __construct()
    {
        $this->messager = new Message();

    }

    public function seed_msg()
    {

        return $this->messager->seed();

    }
}
$Order = new Order();
$Order->seed_msg();

上面的程式碼是我們傳統的寫法。首先由個訊息傳送的類。然後在我們需要傳送訊息的地方,呼叫傳送訊息的介面。有一天你需要新增一個傳送簡訊的介面以滿足不同的需求。那麼你會發現你要再Message類裡面做修改。同樣也要再Order類裡面做修改。這樣就顯得很麻煩。這個時候就有了依賴注入的思路。下面把程式碼做一個調整

<?php

/**
 * 為了約束我們先定義一個訊息介面
 * Interface Message
 */
interface  Message{

  public function seed();
}

/**
 * 有一個傳送郵件的類
 * Class SeedEmail
 */
class SeedEmail implements Message
{

    public function seed()
    {

        return  `seed email`;

        // TODO: Implement seed() method.
    }

}

/** 
 *新增一個傳送簡訊的類
 * Class SeedSMS
 */
class SeedSMS implements Message
{
    public function seed()
    {
        return `seed sms`;
        // TODO: Implement seed() method.
    }


}
/*
 * 訂單產生的時候 需要傳送訊息
 */
class Order{

    protected $messager = ``;

    function __construct(Message $message)
    {
        $this->messager = $message;

    }
    public function seed_msg()
    {
        return $this->messager->seed();
    }
}
//我們需要傳送郵件的時候
$message = new SeedEmail();
//將郵件傳送物件作為引數傳遞給Order
$Order = new Order($message);
$Order->seed_msg();


//我們需要傳送簡訊的時候
$message = new SeedSMS();
$Order = new Order($message);
$Order->seed_msg();

這樣我們就實現了依賴注入的思路,是不是很方便擴充套件了。

服務容器

我理解的服務容器就是一個自動產生類的工廠。

<?php
/**
 * 為了約束我們先定義一個訊息介面
 * Interface Message
 */
interface  Message{

    public function seed();
}

/**
 * 有一個傳送郵件的類
 * Class SeedEmail
 */
class SeedEmail implements Message
{

    public function seed()
    {

        return  `seed email`;

        // TODO: Implement seed() method.
    }

}

/**
 *新增一個傳送簡訊的類
 * Class SeedSMS
 */
class SeedSMS implements Message
{
    public function seed()
    {
        return `seed sms`;
        // TODO: Implement seed() method.
    }

}


/**
 * 這是一個簡單的服務容器
 * Class Container
 */
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);
    }
}

//建立一個訊息工廠
$message = new  Container();
//將傳送簡訊註冊繫結到工廠裡面
$message->bind(`SMS`,function (){
     return   new  SeedSMS();
});
//將傳送郵件註冊繫結到工廠
$message->bind(`EMAIL`,function (){
   return new  SeedEmail();
});
//需要傳送簡訊的時候
$SMS  = $message->make(`SMS`);
$SMS->seed();

container是一個簡單的服務容器裡面有bind,make兩個方法
bind是向容器中繫結服務物件。make則是從容器中取出物件。

bind

bind方法中需要傳入一個 concrete 我們可以傳入一個例項物件或者是一個閉包函式。
可以看到我這全使用的是閉包函式,其實也可以這樣寫

$sms = new  SeedSMS();
$message->bind(`SMS`,$sms);

後面這種寫法與閉包相比的區別就是我們需要先例項化物件才能往容易中繫結服務。而閉包則是我們使用這個服務的時候才去例項化物件。可以看出閉包是有很多的優勢的。

make

make方法就從容器中出去方法。裡面首先判斷了instances變數中是否有當前以及存在的服務物件,如果有直接返回。如果沒有那麼會通過 call_user_func_array返回一個物件。call_user_func_array的使用可以檢視
PHP 中 call_user_func 的使用

原文地址

相關文章