用trait實現簡單的依賴注入

tim1020發表於2019-02-16

這裡先假設一個場景:

有一個工廠(Factory),需要工人(Worker) 使用某個機器(Machine)來生產某種產品

即有這樣的依賴關係: Factory –> Worker –> Machine

不使用注入

程式碼大概是這樣子:

class Machine{
    function run(){
        echo `機器開動`;
    }
}
class Worker {
    function work(){
        echo "工人開動機器 -> ";
        $machine = new Machine();
        $machine -> run();
    }
}
class Factory{
    function produce(){
        echo "工廠叫工人開工 -> ";
        $worker = new Worker();
        $worker -> work();
    }
}
$factory = new Factory();
$factory -> produce();

可以看出來,這裡所依賴的物件都由類自己在內部例項化,是一種強耦合,不利於測試和維護。比如我現在要改成另一種工人來生產,那就要改工廠內部,這是不合理的。

手工注入

所謂的注入,就是將類所依賴的物件的例項化操作放在類外面,同時使用Interface來作出約束:

Interface Machine{
    public function run();
}
Interface Worker{
    public function work();
}
class Machine1 implements Machine{
    function run(){
        echo `機器 1 開動`;
    }
}
class Machine2 implements Machine{
    function run(){
        echo `機器 2 開動`;
    }
}

class Worker1 implements Worker{
    private $machine;
    public function __construct(Machine $machine){
        $this -> machine = $machine;
    }
    function work(){
        echo "工人 1 開動機器 -> ";
        $this -> machine -> run();
    }
}
class Worker2 implements Worker{
    private $machine;
    public function __construct(Machine $machine){
        $this -> machine = $machine;
    }
    function work(){
        echo "工人 2 開動機器 -> ";
        $this -> machine -> run();
    }
}

class Factory{
    private $worker;
    public function __construct(Worker $worker){
        $this -> worker = $worker;
    }
    function produce(){
        echo "工廠叫工人開工 -> ";
        $this -> worker -> work();
    }
}

$machine = new Machine1();
$worker = new Worker2($machine);
$factory = new Factory($worker);

$factory -> produce();

可以看出來,這樣的好處是解耦。比如:可以由工人1開動機器2來生產,也可以由工人2來開動機器1來生產,以後也可以隨時用新的工人(只要他會work)、Worker也可以隨時換其它的機器(只要它會run)來生產。這種轉換都不需要修改工廠或工人的程式碼。

那麼問題來了,現在只是把例項化從裡面移到了外面,但如果依賴的東西多了,也是很麻煩的,這就需要一個自動注入的機制,也就是平時經常聽到的注入容器,常見的容器都是用到反射來實現,而且要在建構函式中宣告注入的型別,相對還是比較麻煩。

在本篇,我嘗試用另一種方式實現。

trait自動注入

trait可以簡單理解為可以複用的方法,下面來看看怎麼用trait來實現自動注入。

思路就是用trait來實現魔術方法__get,通過該方法來自動生成依賴的物件,先看完整程式碼

trait DI{
    private $map = [`Worker` => `Worker1`,`Machine`=>`Machine2`];

    function __get($k){
        if(preg_match(`/^[A-Z]/`, $k)) {
            $obj =  new $this -> map[$k];
            if($obj instanceof $k){
                return $obj;
            }else{
                exit("不符約定");
            }
        }
    }
}

Interface Machine{
    public function run();
}
Interface Worker{
    public function work();
}
class Machine1 implements Machine{
    function run(){
        echo `機器 1 開動`;
    }
}
class Machine2 implements Machine{
    function run(){
        echo `機器 2 開動`;
    }
}
class Worker1 implements Worker{
    use DI;
    function work(){
        echo "工人 1 開動機器 -> ";
        $this -> Machine -> run();
    }
}
class Worker2 implements Worker{
    use DI;
    function work(){
        echo "工人 2 開動機器 -> ";
        $this -> Machine -> run();
    }
}
class Factory{
    use DI;
    function produce(){
        echo "工廠叫工人開工 -> ";
        $this -> Worker -> work();
    }
}

$factory = new Factory();
$factory -> produce();
  • trait中的map用來演示實現類和介面的繫結關係,以便進行型別約束,實際應用時可以用配置或其它方式實現.
  • 類中要使用依賴注入時,需宣告use DI, 同時,注入的物件為首字母大寫(你也可以用其它規則,相應的修改trait中的判斷)

當然了,這只是一個很粗糙的演示,只實現了基本的自動注入,它還有很多問題,比如原來的類如果也有__get方法時,就會產生覆蓋。

有興趣的可以嘗試完善一下看能不能在專案中實際使用。

相關文章