## 淺入淺出 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}]"}
    2241 views