## 淺入淺出 Dependency Injection
By Wenchin, 2021.05.06
<!-- slide: https://hackmd.io/p/hqkqFKzsRiyJD8jQnifSFw -->
---
## Who am I?
- Wenchin
- Batch #10
- Backend developer :hatching_chick:
---
## Outline
1. Why: 為什麼要介面、為什麼要 DI
2. How: 如何實作 DI、DI 設計模式 (pure DI 架構、~~使用 DI 容器~~)
3. What: DI 定義、跟 IoC 的關係、跟 DIP 的關係、優缺點
<!-- 相信很多人對 DI(Dependency Injection, 依賴注入)的觀念有很多疑惑。從最根本的本質(到底它是個技術?工具?設計模式?還是信仰?)、到目的(注入一堆東西是為了單元測試好寫嗎?)、到引入的手段(是不是要用 DI 容器才能 DI?有純的 DI 方法嗎?)DI 都很容易讓人寫著寫著就黑人問號了。
因為我也很問號,所以決定讀讀同事借我的「依賴注入原理、實作與設計模式」這本經典,並跟大家討論我從中學到、可能實務較常用到的東西,目的是學好 DI 架構、一起寫出更舒服的程式碼。 -->
---
## Disclaimer
* DI 架構本身要解決的問題不限 OO 領域,但以下會聊到的解決方案都屬於 OOP 語言的領域
* DI 對函數式程式設計或動態程式語言無法發揮全力
* 雖然如此,DI 架構與很多軟體設計中的實務原則、設計模式也有相關
---
# Why use DI?
---
[![](https://i.imgur.com/fa1CGmW.png)](https://stackoverflow.com/questions/1638919/how-to-explain-dependency-injection-to-a-5-year-old)
---
叫女友名字還是叫寶貝?:heart:
---
| 緊耦合 | 鬆耦合 |
| -------- | -------- |
| ![](https://i.imgur.com/IyoRcZ4.jpg) | [![](https://i.imgur.com/OqY6IQg.png)](https://unsplash.com/photos/7alo7OJVNVw) |
<!-- In a cheap hotel room, you might find a hair dryer wired directly into the wall outlet. This is equivalent to using the common practice of writing tightly coupled code. -->
<!-- Through the use of sockets and plugs, a hair dryer can be loosely coupled to a wall outlet.
Using a socket and a plug, you can replace the original hair dryer from with a computer. This corresponds to the Liskov Substitution Principle. -->
---
插頭怎麼實現 SOLID 原則?
---
### Liskov Substitution Principle
對介面的實作要能替換,而且不須破壞客戶端介面或實作。
---
### Open Close Principle
只要個人電腦的插頭遵循插座要求的規格設計它們的插頭,就可以在不用修改插座設計(既有系統 code base)的情況下使用這個它。
---
鬆耦合架構可以根據需求做到...
| 裝飾者模式 | 合成模式 | 轉接器模式 |
| -------- | -------- | -------- |
| ![](https://i.imgur.com/8a0CX3g.png) | ![](https://i.imgur.com/1MU7DDz.png) | ![](https://i.imgur.com/be8uNv7.png) |
---
The goal of dependency injection is to separate the usage an object from it’s creation. This removes a class’s direct dependency on another class. Dependent classes can be changed out without the depending class knowing or caring.
---
Program to an interface, not an implementation.
---
```csharp=
IMessageWriter writer = new IMessageWriter();
```
---
![](https://i.imgur.com/VLASBte.png)
---
因為介面不包含實作,無法這樣直接成立一個介面的物件,需要透過別的管道建立。
DI 可以解決這個問題!
---
# How do I use DI?
---
就是學會用 DI 容器對吧?
---
DI containers 是種函式庫,讓你可以在構成一個應用程式的時候更容易的實作 DI、組織類別,但絕對不是必要的選擇。
先學實作 pure DI 再按照需求使用 DI containers。
---
## :computer: Hello DI
---
在 `Salutation` 類別注入 `IMessageWriter` 介面,讓它的實作印出 "Hello DI!"
---
`Salutation` 依賴介面 `IMessageWriter`,不會知道實作是哪位
![](https://i.imgur.com/oEVvsEp.png)
---
## DI 設計模式
---
### 1. 建構子注入
確保類別所需要的不穩定依賴對象即使不直接依賴、還是可以被滿足
```csharp
public class Driver
{
private ICar _car;
public Driver(ICar car)
{
_car = car;
}
public void Drive()
{
_car.Run();
}
}
```
---
### 2. 方法注入
在同一類別、不同方法上注入不同的依賴對象
```csharp
public class Driver
{
public void Drive(ICar car)
{
car.Run();
}
public void BookRestaurant(IBookingService service)
{
service.BookRestaurant();
}
}
```
---
### 3. 屬性注入
當已經有適合的內建預設可用,但還是希望提供呼叫方一份彈性
```csharp
public class Driver
{
public ICar Car { get; set; }
public void Drive()
{
Car.Run();
}
}
```
<!-- - 好處: 不會讓建構子過度龐大、可選擇用哪些 dependency
- 缺點: 使用方可能會少放入 dependency -->
---
# What's DI?
---
<!-- > 所謂的 DI 架構,指的是一整套設計模式與實務原則。這是一種程式設計面上的思維訓練,而不是特指某種技術。DI 架構的最後目標,是在物件導向程式的領域中,打造出具備高可維護性的軟體。
> *Dependency Injection* is a set of software design principles and patterns that enables you to develop loosely coupled code. -->
是個手段,不是目標。
---
DI → 鬆耦合 → 好維護 → 程式效率高 → :moneybag: → :smile:
---
- 依賴 = 耦合 → 兩個物件是否認識彼此
- 注入 = 這個認識的關係怎麼建立的 (認識的途徑)
以愛情為例:兩人的關係就是依賴,注入方式可能有路上搭訕注入、交友網站注入、相親注入...等
---
## 請問你跟 IoC 的關係是...?
---
### Inversion of Control
讓通用的程式碼來控制應用特定的程式碼,相依於抽象而不倚賴實作
<!-- 把產生執行個體的行為交給 Main(): 就會到處 new 執行個體=> 寫個方法 CreateConnection 之類,把產生執行個體的權力給他移轉控制權 -->
<!-- 例:不該讓 client 知道 `Benz`, `BMW` 類別 (internal)、而該知道抽象的 `ICar` 就好 -->
實現 IoC 的做法:DI, 工廠模式 (拉個 `CarFactory` 類別把控制產生執行個體如 `Benz`, `BMW` 類別的權力移轉給工廠)...
IoC 的範疇包含 DI,但不僅限於 DI。
---
## 請問你跟依賴反轉原則 (DIP) 的關係是...?
---
### Dependency Inversion Principle
* High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g., interfaces).
* Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions.
---
Dependency Inversion Principle
|
IoC
|
DI
---
## Advantages of DI
---
### 1. Late Binding
替換資料庫:SQL Server 要換成 PostgreSQL
---
### 2. Extensibility
需求:希望印訊息的物件要經過確認
:computer: 用我們定義的 `IMessageWriter` 來寫一個新的 `SecureMessageWriter` class! (裝飾者模式)
---
### 3. Parallel development
多人開發:把你的模組 DI 到我的應用
---
### 4. Maintainability
SRP:幫 class 區分職責
---
### 5. Testablility
測試替身取代原本物件注入 system under test
:computer: 用 `TestSpyMessageWriter` 注入 `Salutation` class 寫 Hello DI 的單元測試!
Test Spy 型的實體類別:驗證 sut 對其他元件的間接輸出呼叫行為
---
### Interception
當我們把依賴性關係的控制權交給第三方 → 可在物件交給使用方的類別之前先攔截、做更動!
---
:computer: 剛剛寫的 `SecureMessageWriter` 就是對 `ConsoleMessageWriter` 加以攔截
![](https://i.imgur.com/b2nb2E8.png)
---
## Disadvantages
---
![](https://i.imgur.com/sMOXRnD.png)
---
- 系統架構的複雜度增加,追扣較難 <!-- - 由於程式不再和實作的 class 相關,因此開發者追程式碼時會比較困難,實作的 class 是什麼沒辦法直接看出來的。這需要花時間來熟悉整體系統的運作才會上手。 -->
- 所需寫的程式碼變多,開發速度變長 <!-- - 為了這個架構,得要多寫一些程式;這會直接的影響到開發的速度。在簡單的程式裡套用 DI,可能造成寫了許多程式結果都和商業邏輯沒有任何關係的狀況。 -->
- 往往某種程度上耦合了你用的 DI 框架,或你決定實現 DI 的方式
---
## Source
Book [依賴注入:原理、實作與設計模式 by Steven van Deursen, Mark Seemann](https://www.books.com.tw/products/0010865153?gclid=CjwKCAjwhMmEBhBwEiwAXwFoEehAlibs1ZQTWNxYc5Nys2kDo54ghf90DxcFMO7bgV_inEgT511gLhoCFPoQAvD_BwE)
Course [輕鬆學會物件導向(使用C#) by Bill Chung](https://skilltree.my/events/2021/4/10/oop-batch-19)
Podcast [Dependency Injection by Complete Developer Podcast](https://open.spotify.com/episode/27EhYYkhLjyMwHFD4VJk0X?si=GjyBygdvR4es77cMaeKq5Q&dl_branch=1) ([episode notes](https://completedeveloperpodcast.com/episode-185/))
---
## Thank you!
You can find today's code & notes on my
- [Repo](https://github.com/wenchin77/di-is-difficult)
- [Notion](https://www.notion.so/wenchin/210506-DI-49a774f012a74d06b303cde125df4937)
---
![](https://i.imgur.com/KXvrFms.png)
Source: https://simonsinek.com/
{"metaMigratedAt":"2023-06-15T23:08:16.314Z","metaMigratedFrom":"YAML","title":"淺入淺出 DI","breaks":true,"description":"View the slide with \"Slide Mode\".","contributors":"[{\"id\":\"f0ac5174-556c-41e8-85cc-0f3528d83f8a\",\"add\":9473,\"del\":5387}]"}