一文秒懂什麼是依賴注入, Ioc容器

jsoner發表於2021-08-27

什麼是依賴

打個比方,就好比每個人都需要穿衣服才能進行正常的社會活動,這就是說是個人都需要依賴衣服才能正常社會活動。
相應的,就是一個類需要另一個類才能完成工作,這就是依賴

code demo

class Person{
      protected $clothes;
      public function __construct() 
      {
          $this->clothes = new Clothes();
      }
}

看上面的程式碼就知道 人依賴了衣服,

什麼是依賴注入呢,我們改造下上面的程式碼

class Person {
        protected $clothes;
        public function __construct(Clothes $clothes) {
            $this->clothes = $clothes;
        }
    }

    $person = new Person(new Clothes());

這裡的Person 依賴注入了clothes類

理解了依賴注入,我們就可以接著去理解IOC

IOC

IOC 是什麼呢,看明白了依賴注入(DI)後就很容易理解了
通過DI我們可以看到,一個類所需要的依賴類是由我們主動例項化後傳入類中的。
控制反轉的意思就是說將依賴類的控制權交出去,由主動變被動。

laravel 程式碼

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AuthController extends Controller
{

    public function login(Request $request)
    {
        //這就是IOC,我們不需要主動傳入類了一切由laravel去實現
    }
}

看到這你可能有疑問了,這是怎麼實現的呢?
這就是靠服務容器了,請往下接著看。

服務容器

看了很多文章,我一致認為服務容器就是一種設計模式。

它的目的就是解耦依賴。

它有點類似於我前面說的《享元模式》。區別在於服務容器解決了所有依賴的實現。

這裡我們再從頭至尾的看一遍,怎麼一步步演化出服務容器。

依然是人的例子,我們知道人依賴衣服鞋子褲子,可是衣服鞋子也有很多種品牌呀。

先看一個最原始的程式碼例子:

 class Person {
        protected $clothes;

        public function __construct($type = null) {

            switch($type) {
                case 'anta':
                    $this->clothes = new Anta();
                case 'adidas':
                    $this->clothes = new Adidas();
                default:
                    $this->clothes = new 360();
            }
        }
    }

或許你一眼就看出了問題在哪。

如果我們又要增加一種衣服,那我們又得對這個類進行修改。這樣下去,這個類會變得龐大且耦合程度過高。

那麼我們可以怎麼修改呢?

工廠模式

這樣我們可以避免直接的修改 Person 類。


    class Factory {

        public static function getInstance($type){
            switch($type) {
                case 'anta':
                    $this->clothes = new Anta();
                    break;
                case 'adidas':
                    $this->clothes = new Adidas();
                    break;
                default:
                    $this->clothes = new 360();
                    break;
            }
        }
    }

    class Person {
        protected $clothes;
        public function __construct($type == null) {
            $this->clothes = Factory::getInstance($type);
        }
    }

這樣使用簡單工廠模式後,我們後續的修改可以不用對 Person 類進行操作而只要修改工廠類就行了。這就相當於對 Person 類進行了解耦。

Person 類雖不在依賴那些鍵盤類了,但是卻變為依賴工廠類了。

後續新增新型別的鍵盤就必須對工廠類進行修改。

所以這個工廠類還不能很好的滿足要求,我們知道電腦對鍵盤的介面都是一致的,鍵盤必須實現這一介面才能被電腦識別,那我們對 Person 和 Clothes 類進行修改。

DI(依賴注入)

interface Clothes {
        public function type();
    }

    class AntaClothes implements Clothes {
        public function type(){
            echo '安踏';
        }
    }

    class AdidasClothes implements Clothes {
        public function type(){
            echo 'adidas';
        }
    }

    class Person {
        protected $clothes;

        public function __construct (Clothes $clothes) {
            $this->clothes = $clothes;
        }
    }

    $person = new Person(new AntaClothes());

可是這樣也有問題,如果我們後續有人使用的衣服不滿意要進行替換呢? 我們又回到原點了,必須去修改傳入的衣服類。

能不能做成可配置的呢?

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);
    }
}

這就是一個簡單的 IOC 服務容器。
這個怎麼解決我們上述的問題呢?

 $container = new Container;

 $container->bind('Clothes', function($container){
     return new AntaClothes;
 });

 $container->bind('Person',function($container,$module){
     return new Person($container->make($module));
 });

 $person = $container->make('Person',['Clothes']);

這裡生產出來的 Person 類就是一個使用了安踏衣服的類了。

如果我們要換衣服怎麼辦呢?

 $container->bind('Clothes', function($container){
     return new AdidasClothes;
 });

 $container->bind('Person',function($container,$module){
     return new Person($container->make($module));
 });

 $person = $container->make('Person', ['Clothes']);

只要對 bind 繫結的 Clothes 類的實現進行修改,我們就可以很容換掉衣服了。這就是一個服務容器。

對服務容器進行一個理解:

容器就是一個裝東西的,好比碗。而服務就是這個碗要裝的飯呀,菜呀,等等東西。當我們需要飯時,我們就能從這個碗裡拿到。如果你想在飯里加點菜(也就是飯依賴注入了菜),我們從碗裡直接拿飯就可以了,而這些依賴都由容器解決了(這也就是控制反轉)。

我們需要做的就是對提供的服務進行維護。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章