架構演化學習思考(4) --- IOC的學習認識

畅知發表於2024-08-05

架構演化學習思考(4)

IOC的學習認識

IOC相關概念認識

什麼是IOC?

IOC全稱為 Inversion Of Control ,即控制反轉。它是一種控制思想,可以解釋為類和類之間的依賴關係不再由程式碼直接控制,而是透過容器來控制和配置實現。

控制反轉?那麼什麼是正傳? 反轉有啥好處?IOC到底是啥?

好,那就開始逐步認識和了解吧~

既然是一種思想,那就從它的常見實現方式DI來入手。

DI

DI,即 Dependence Injection,依賴注入,它是IOC的一種實現。

依賴這個概念我們在第一篇文章中解釋過,它是一種物件/引用的持有關係。

最簡單的單項依賴:

A對B產生依賴關係。

public class A
{
    public B b = new B();
}

那麼注入又是什麼?

注入是建立依賴關係的過程。

public class A
{
    //持有B的空引用
    public B b =null;
}


public class B {}

void Main()
{
    var a = new A();
    var b = new B();

    //建立AB之間的依賴關係
    //即注入
    a.b = b;
}

也就是說,A和B建立依賴的過程便是注入。因為注入操作。使的A中b引用不再為空,而是直接拿到B物件的引用。

到此我們認識了”依賴注入“的操作,就是幫助建立物件與物件之間的依賴關係,讓A物件完成持有B物件引用的操作。

IOCContainer的使用

DI的具體實現離不開DI容器,DI容器有時候也稱為IOC容器,透過此容器來完成依賴注入的工作。來看一下IOCContainer的使用。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using QFramework

namespace IOCContainerExample
{

    public class A
    {
        public void Say()
        {
            Debug.Log("我是A" + this.GetHashCode());
        }
    }

    public class IOCExample: MonoBehaviour
    {
        //新增註入標記
        [Inject]
        public A a {get;set;}

        void Start()
        {
            //建立容器例項
            var container = new QFrameworkContainer();

            //註冊需要注入的型別
            container.Register<A>();

            //進行依賴注入
            //會自動查詢 Inject Atrribute的物件
            container.Inject(this);

            //注入之後可以使用了
            a.Say();
        }
    }
    
}

使用IOCContainer來進行依賴注入,先給容器註冊相關的型別,然後標記要獲取物件的空引用,之後呼叫容器的Inject方法完成依賴注入。

這個這個容器可以理解為一個”租房中介“,想要出租房屋的房東們向中介進行”Register“註冊,當呼叫Inject()方法時候,中介會根據租客( [Inject]標記的空引用)的租房型別來匹配合適的房源,完成租客找房源的目的,幫助租客完成對房子的依賴。

這就是IOCContainer的簡單使用。

image

一般情況下,DIContainer會提供如下的API:

  • 註冊型別:Register<TSource,TTarget>
  • 注入:Inject(object obj)
  • 解析:Resolve()

註冊和注入我們在上面內容中使用瞭解了,而解析(Resolve)是什麼意思呢?

Resolve實際上會根據型別返回例項。

那會產生疑問,返回的例項是每次都新建的還是同一個例項呢?

在下面內容會逐步解析。

我們繼續來聊IOCContainer的職責,正如上文所比喻的那樣,其職責就是管理依賴和注入依賴。透過類型別來管理依賴,給空引用賦值完成依賴注入。也就是說租房中介幫助想要找房源的租客和登記註冊的房源之間建立關係,具體表現就是建立依賴。

這個中介手裡掌握著租客和房源之間的對應關係,也可以對此依賴關係履行管理職責。所以一般的IOCContainer會用一個Dictionary<Type,object>來作為核心資料結構。

即根據Type可以得到Type的例項,依賴在程式碼中就是這樣一個東西。而這個依賴不是指Type和object之間的相互依賴,而是說其結構本身就是一個依賴。

這裡比較繞,也就是說這裡的資料結構中儲存的鍵值對元素本身就是依賴,是Inject(object obj) 中的obj的依賴。

即,object 類中有期待注入的null型別的索引對這種配對資訊產生依賴。比喻過去就是“租客名單”(object)對中介手中的(租客房源配對資訊)有依賴,因為租客在配對資訊中等級(object)才可以順利的和自已想要的房源進行配對。

不知道大家這裡還能順利理解嘛!這就是IOCContainer中對依賴的管理方式。

沒關係,我們下面會看具體的例項。

我們在看案例之前先簡單聊一聊IOCContainer的強大之處。

IOCContainer的強大之處

用過單例的人都會有這樣的認識:用單例一時爽,一直用單例一直爽!

因為單例作為隨用隨獲取,與其它模組的互動變得非常容易。

專案規模小的時候這樣確實很方便,不用單獨處理各種依賴關係。只要大家按照約定的層級關係來訪問代理模組既可。

但是如果專案功能繁多,模組層級也有多個,約定的內容也難以繼續保證,且單從技術限制角度來說,單例訪問是沒有限制的,像是對所有的層級模組都開放使用。這樣確實難以體現出模組之間的層級關係,顯然不利於整個專案的架構設計。

那麼如何合理使用單例,避免破壞層級呢?

解決方式很簡單,就是最頂層的模組都用單例,然後一些底層模組,作為頂層模組的成員變數,從而達到邏輯層無法直接訪問底層模組,而是必須透過頂層模組間接地使用底層模組的服務

img

這樣就解決了單例結構無法表達層級問題,但是同時也失去了單例帶來的種種好處

,但是同樣失去了單例的種種好處,易擴充套件。

因為現在單例結果對底層的模組產生了依賴關係,當擴充功能模組時候要考慮對底層模組的依賴關係。

那麼IOCContainer的強大之處體現出來,幫助管理依賴關係。

依賴管理

我們回過頭來再看依賴管理相關內容,依賴注入的時機和位置是一個需要關注的問題。

public class ModuleA
{
    public ModuleB moduleB;
}

public class ModuleB
{
    
}

來看一個待注入的依賴,依賴注入我們可以再ModuleA內部進行:

public class ModuleA
{
    public ModuleB moduleB = new ModuleB();
}

但是如果MoudleB是公用的呢?在內部建立顯然就不太合適了,因為這裡是一個模組,不是簡單的一個物件。

那就在外部建立物件:

void main()
{
    var moduleA = new ModuleA;
    mouduleA.moudleB = new MoudleB();
}

那在外部注入的依賴在模組內部使用時候就得需要知道依賴到底注入沒有?

在哪裡注入的?我可不可以直接用?

public class ModuleA
{
    public ModuleB moduleB;
    /*
     ..。其它程式碼邏輯
    */
    void someFunc()
    {
        //需要使用moduleB
        //需要知道moduleB到底有沒有值?在哪裡獲取到的?
        moduleB.XXX  
    }
}

所以這就不得不考慮依賴的建立過程了。

而使用單例,那就沒有這個問題。

public class ModuleA
{
   void someFunc()
   {
       //直接使用單例
       moduleB.Instance.DoSomething();
   }
}

現在該IOCContainer登場了。

是的,IOCContainer的職責就是注入依賴、管理依賴。

使用IOCContainer管理依賴

public class ModuleA
{
    [Inject]
    public ModuleB moduleB;

    void something()
    {
        //放心使用 不用考慮是否為空
        moduleB.DoSomeThing();
    }
}

在啟動程式的時候,統一註冊依賴:

public static QFrameworkContainer Container {get; set;}

void Main()
{
    Container = new Container();

    Container.Register<MoudleB>();
}

在MoudleA的建構函式中注入依賴:

public class ModuleA
{
    [Inject]
    public ModuleB moduleB;

    //建構函式
    public MoudleA()
    {
        //注入依賴
        Global.ContainerInject(this);
    }

    void something()
    {
        //放心使用 不用考慮是否為空
        moduleB.DoSomeThing();
    }
}

這樣使用IOCContainer對各種依賴進行管理其模組內容變得更佳清晰:

img

放心使用依賴內容,依賴管理和注入交給IOCContainer管理即可。

相關文章