相依性注入起手式 - 「何謂相依」

現今許多前端或後端的框架,

預設都是採用相依性注入的方式進行開發,

對我這種原先較少使用DI框架的開發者而言,

上手適應的確得花一番功夫。

記得某次參加twMVC週四固定聚會時,

我向Bill叔請益有關相依性注入的看法時,他秒回道:

不就是相依,然後再注入而已。

這看似簡單的幾句話,

卻讓當下只會用DI框架的我,

開始反思何謂相依、何謂注入。

此篇我想從「何謂相依」出發,

分別地來談談「相依」及「注入」,

順手記錄自己理解的過程。

 

何謂相依

首先,何謂相依?

在教育部的國語辭典查詢的結果顯示:彼此倚賴

舉個MVC中的ControllerService的關係而言,

我有個MemberController(以下簡稱Controller),用來控制會員新刪修查的後端流程,

並且有個MemberService(以下簡稱Service),用來處理新刪修查所需的執行細節。

以這個例子而言,

究竟是Controller相依於Service

還是Service相依於Controller呢?

還是它們兩個的關係是彼此倚賴的?

 

身為一個專業的工程師,

我相信看程式碼遠比咀嚼文字來的快!

所以我們先來一小段程式碼:

MemberController.cs

public class MemberController : Controller
{
    private readonly MemberService _memberService;

    public MemberController()
    {
        _memberService = new MemberService();
    }

    public MemberController(MemberService memberService)
    {
        _memberService = memberService;
    }

    [HttpGet]
    public ActionResult Create()
    {
        return View();
    }

    [HttpPost]
    public ActionResult Create(Member member)
    {
        _memberService.CreateMember(member);
        return View();
    }
}

MemberService.cs

public class MemberService
{
    public void CreateMember(Member member)
    {
        //The real detail for creating member ...
    }
}

 

這段程式對各位來說應該再簡單不過,

而我們回到教育部國語辭典的定義:彼此倚賴

倚賴與相依是同義的,

為避免讀者搞混,以下我換稱為相依

ControllerService的關係是否為彼此相依呢?

看起來應該是,但又好像不是。

 

提到闡述相依,

讓我想起Mariah Carey - Without You中的一段歌詞:

 I can't live If living is without you.

也就是「失去你,我也沒辦法獨活」的情節。

有看過蠟筆小新的中生代都知道,

阿吉跟小美是眾所皆知的男女朋友,

如果失去小美,阿吉活不下去,

我們可以稱「阿吉相依於小美」。

這是單方面的相依。

 

在上述的假設之下,

如果失去阿吉,小美也無法獨活,

我們可以稱「阿吉與小美彼此相依」。

 

回到正題,

ControllerService究竟是誰相依於誰?

其實舉個簡單的例子就可以明白了。

如果Service不實作,Controller是否能夠如預期的完成新增會員的任務?

當然不行!

所以我們可以說:Controller相依於Service

 

換個角度,如果Controller在新增完會員後拋出錯誤,

是否會影響Service的運作?

[HttpPost]
public ActionResult Create(Member member)
{
    _memberService.CreateMember(member);
    var a = 0;
    var b = 5 / a;
    return View();
}

當然不會,Service還是能夠照常運作,

CreateMember依舊會如期執行並完成任務,

除以零所拋出的錯誤不過是導致後續的動作中斷而已。

所以,Service並不相依於Controller

 

搞懂相依之後,別急著注入。

讓我們回到現實常態,

一個Controller用到的通常應該不只一個Service而已,

也就是說一個Controller通常相依於多個Service

相依的對象一多之後,

進行程式碼修改時就容易引起變化,

一不小心就會發生改A壞B的副作用。

 

 

提取介面

接著我們幫相依的對象(Service)提取介面,

並透過介面來隔離實作細節,

同時提供替代的可能性。

當然,這會造成開發者必須提取相當多的介面。

也是許多人不太習慣的地方。

但在IDE打天下的年代,

提取介面應該要是幾個按鍵就能完成的,

而不是逐字地敲打。

 

以上述為例,

我們試著替Service抽取一個介面,

同時讓Controller相依於抽出來的介面。

public class MemberController : Controller
{
    private readonly IMemberService _memberService;

    public MemberController()
    {
        _memberService = new MemberService();
    }

    public MemberController(IMemberService memberService)
    {
        _memberService = memberService;
    }

}

 

抽完介面,Controller就沒有相依了嗎?

的確,MemberController現在已經不相依於MemberService了,

但它仍然相依於MemberService的實作介面(IMemberService)。

提取介面僅能隔離相依宿主(Controller)與實作者(Service),

並不能完全阻擋變化的發生,

但如若透過介面注入容器,

將控制權反轉,

能夠集中管理所有的實作對象,

並在必要時進行實作對象的抽換,

將修改的範圍限縮到最小,

提取介面另一項好處 – 利於單元測試。

透過介面可以讓我們在感測物件行為時,

隔離不必要的行為變化。

總總利弊相較之下,

在適當的情境提取介面,

我想能帶來的效益是值得的。

 

結語

內容如有不洽之處,

歡迎指證討論。

2020新春第一發,

也祝所有朋友新年快樂!

 

===2020/1/28 21:53===

感謝善意人士提醒,

讓我發現自己對ISP理解是錯誤的,

實際上文內提及的範圍僅是提取介面而已(已修正文章)。

==================