透過簡單的情境和 typescript 的 code 去理解 Dependency Inversion Principle 的有趣之處
[正文]
0x00 定義?
直接來看 Dependency Inversion Principle (DIP) 的定義
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
看完之後就先放到一旁不管,等到我們理解後再回來看看兩條定義。
0x01 問題
我們先來弄明白兩件事情:
- 什麼是 high/low level modules?
- 要怎麼知道哪個東西 depend on 哪個東西?
答案非常簡單,就是看誰使用誰就好,例如以下的 code 可以從第 11 行看出 Programmer 會使用 Computer 的 program method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Computer { program() { console.log("Computer: Code...Code...Code..."); } }
class Programmer { private computer = new Computer();
code(): void { this.computer.program(); } }
class Company { main() { const me = new Programmer();
while(true) { me.code(); } } }
|
咱們就可以說:
- Programmer depend on Computer
- Programmer is high-level modules
- Computer is low-level modules
不過剛剛那段 Code 有個問題,就是因為電腦是程式設計師帶來的,如果今天電腦壞掉的話就要連程式設計師一起換掉,這相當不合理,所以公司決定配發電腦給程式設計師用!這樣電腦壞掉的話,就換一個給程式設計師就好
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Programmer { private computer: Computer;
constructor(computer: Computer) { this.setComputer(computer); }
setComputer(computer: Computer) { this.computer = computer; }
code(): void { this.computer.program(); } }
class Company { main() { const me = new Programmer(new Computer());
while(true) { me.code(); } } }
|
Dependency Injection
像這樣把 Dependency 建立的責任交給上一層使用自己的物件來建立然後在傳給自己,就叫做 Dependency Injection
例如上一段 Code 的 25 行,Programmer 將它的 Dependency(Computer) 交給 Company 來建立然後傳給自已
如果今天公司請了另一個程式設計師,因為他會需要用筆電來工作,程式碼就要新增 LaptopProgrammer:
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 55 56 57 58 59 60 61 62 63
| ...
class Laptop { program() { console.log("Laptop: Code...Code...Code..."); } }
class LaptopProgrammer { private laptop: Laptop;
constructor(laptop: Laptop) { this.setLaptop(laptop); }
setLaptop(laptop: Laptop) { this.laptop = laptop; }
code(): void { this.laptop.program(); } }
class Company { main() { const me = new Programmer(new Computer()); const colleague = new LaptopProgrammer(new Laptop());
while(true) { me.code(); colleague.code(); } } }
|
這樣每多一種可以 program
的工具,就要新增一個相似的 class,相當不合理,所以我們可以抽象化 Computer 和 Laptop
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
| interface Programable { program(): void; }
class Computer implements Programable { program() { console.log("Computer: Code...Code...Code..."); } }
class Laptop implements Programable { program() { console.log("Laptop: Code...Code...Code..."); } }
class Programmer { private programableTool: Programable;
constructor(programableTool: Programable) { this.setProgramableTool(programableTool); }
setProgramableTool(programableTool: Programable) { this.programableTool = programableTool; }
code(): void { this.programableTool.program(); } }
class Company { main() { const me = new Programmer(new Computer()); const colleague = new Programmer(new Laptop());
while(true) { me.code(); colleague.code(); } } }
|
這樣之後只要工具是 Programable,程式設計師就可以用來寫 code,公司也都能新增給程式設計師! 例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| class SmartPhone implements Programable { program() { console.log("SmartPhone: Code...Code...Code..."); } }
class Company { main() { const me = new Programmer(new SmartPhone());
while(true) { me.code(); } } }
|
0x02 定義!
我們現在在看一次那兩點定義,你會發現你看懂了,還變成你熟悉的語言嘍
- 高層次的模組(Programmer)不應該依賴於低層次的模組(Computer),兩者都應該依賴於抽象介面(Programable)。
- 抽象介面(Programable)不應該依賴於具體實現(Computer)。而具體實現(Computer)則應該依賴於抽象介面(Programable)。
一開始 Programmer 依賴 Computer,在修改 Computer 的時後所有用到 Computer 的地方都需要顧慮會不會需要修改,要將 Computer 換掉還要修改 Programmer。經過一番修改之後,Programmer 改成依賴 Programable,而 Computer 也依賴(實做) Programable,這樣我們就可以自由的新增各種 Programable 的工具或修改工具,而不影響到 Programmer 這個 high level 的 module。