以「依賴注入」的精神對抗老闆反覆無常的修改需求

1.2k 詞

一、前言

現代軟體開發中,依賴注入(Dependency Injection)逐漸成為顯學,微軟甚至在 .NET Core中大量地仰賴依賴注入。在傳統開發過程中,相信一定有很多人遇到在程式碼中建立一個實例並使用它,結果今天老闆叫你把這些實例通通換成另外一個,這下子慘了,我們得一個一個去修。

為了解決這樣的問題,實踐「鬆耦合」與「代碼重用性」,依賴注入這個概念被提出來了。「依賴」是指接收方所需的對象。「注入」是指將「依賴」傳遞給接收方的過程。

詳細關於「依賴注入」的介紹,可以參考以下大神們的分享:

而我個人認為「依賴注入」的精華就在於「建立鬆動的關係」,那麼依循這個概念,我們其實也不用寫得非常複雜,也能建立有著依賴注入精神的程式碼。

二、實作

我們這邊用C#做舉例,在這例子中,我們有兩個Repo類別、兩個Service類別,一般來說如果我們想要在程式碼中使用這四個類別,我們會這樣寫:

1
2
3
4
5
6

ARepo aRepo = new ARepo();
BRepo aRepo = new BRepo();
AService aService = new AService();
BService aService = new BService();

這是我們平常依賴實例的寫法,比較複雜一點的話可能會用到多型,但都是依賴著某個實例。

接著我們就會在程式碼中使用這幾個類別中的Method做我們想要做的事情,而當這幾個類別常常被實例在不同地方時,我們會發現怎麼好像到處都在new,不過不影響程式運行所以就不管了。

結果有一天,老闆要我們把ARepo換成ARepoV2,完蛋了有好多地方得改,又怕交給IDE一次修改會改到不該改的地方。

那麼能不能有個小容器,能讓我們儲存這幾個類別的關係,然後讓我們需要更替類別時,只要更換容器裡面的關係就可以了?

這時候就得請出我們的老面孔 介面(Interface) 了。首先我們得先為原本的類別建立介面,分別為IARepo、IBRepo、IAService、IBService。

接著寫一個簡單的小容器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
namespace ProjectA.Tools
{

/// <summary>
/// </summary>
public sealed class MyContainerSingleton
{
private static MyContainerSingleton uniqueInstance;

//Repo
/// <summary>
/// A的Repo介面
/// </summary>
public Type IARepo;
/// <summary>
/// B的Repo介面
/// </summary>
public Type IBRepo;

//Service
/// <summary>
/// A的Service介面
/// </summary>
public Type IAService;
/// <summary>
/// B的Service介面
/// </summary>
public Type IBService;


private MyContainerSingleton()
{
//Repo
this.IARepo = typeof(ARepo);
this.IBRepo = typeof(BRepo);

//Service
this.IAService = typeof(AService);
this.IBService = typeof(BService);
}

/// <summary>
/// 提供單例
/// </summary>
public static MyContainerSingleton GetInstance()
{
if (uniqueInstance == null)
{
uniqueInstance = new MyContainerSingleton();
}
return uniqueInstance;
}
}
}

我們透過MyContainerSingleton建立了介面與類別的關係,並且提供單例,不讓程式碼實例一堆重複的東西。接著我們就在原先使用這幾個類別的程式碼中,改寫成下列的樣子:

1
2
3
4
5
6
7
8
9
10
11

private IARepo _IARepo;
private IBRepo _IBRepo;
private IAService _IAService;
private IBService _IBService;

//並將這幾個寫在建構式中
this._IARepo= (IARepo) Activator.CreateInstance(MyContainerSingleton.GetInstance().IARepo);
this._IBRepo= (IBRepo) Activator.CreateInstance(MyContainerSingleton.GetInstance().IBRepo);
this._IAService= (IAService) Activator.CreateInstance(MyContainerSingleton.GetInstance().IAService);
this._IBService= (IBService) Activator.CreateInstance(MyContainerSingleton.GetInstance().IBService);

這樣一來我們就能讓程式碼透過建構式,動態實例應該要實例的東西,如果哪天老闆要我們把IARepo的ARepo換成ARepoV2,我們也只需要去修改MyContainerSingleton中的對應關係就好:

1
this.IARepo = typeof(ARepoV2);

當然啦,現在有許多程式語言都支持依賴注入,其本身也都有強大又方便的語法,而且支援各種不同的依賴注入,所以如果真的需要,就用它們的就好了!

畢竟我們實作的例子,有幾個限制:

  • 這個是Thread-unsafe的單例寫法
  • 用了C#中的反射,如果使用的程式語言不支援反射,就寫不起來
  • 寫起來不像 .NET Core的依賴注入那樣優雅精美

所以還是要好好評估專案的需求,選擇適當的工具喔!

三、結語

寫這一篇其實有個背景,當時我使用 .NET Framework開發專案,要知道Framework不像Core這麼全面支援依賴注入,那時候我就找了幾個工具但通通用不好,只好索性寫一個簡單的小容器。畢竟殺雞焉用牛刀,我不需要那些多餘的功能,而在當今程式輕量化開發的主流下,能解決問題當然是越簡單越好!

畢竟老闆又沒多給我錢,我不用拚了老命寫一個依賴注入的工具給他對吧XD