羊毛 | 寫 Code 雜談

願如同童話裡的山羊般咀嚼知識

0%

來理解 Dependency Inversion Principle 吧!

透過簡單的情境和 typescript 的 code 去理解 Dependency Inversion Principle 的有趣之處

[正文]

0x00 定義?

直接來看 Dependency Inversion Principle (DIP) 的定義

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend on details. Details should depend on abstractions.

看完之後就先放到一旁不管,等到我們理解後再回來看看兩條定義。

0x01 問題

我們先來弄明白兩件事情:

  1. 什麼是 high/low level modules?
  2. 要怎麼知道哪個東西 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(); }
}
}

咱們就可以說:

  1. Programmer depend on Computer
    • 沒有 Computer,就沒辦法寫 code!
  2. Programmer is high-level modules
  3. 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 定義!

我們現在在看一次那兩點定義,你會發現你看懂了,還變成你熟悉的語言嘍

  1. 高層次的模組(Programmer)不應該依賴於低層次的模組(Computer),兩者都應該依賴於抽象介面(Programable)。
  2. 抽象介面(Programable)不應該依賴於具體實現(Computer)。而具體實現(Computer)則應該依賴於抽象介面(Programable)。

一開始 Programmer 依賴 Computer,在修改 Computer 的時後所有用到 Computer 的地方都需要顧慮會不會需要修改,要將 Computer 換掉還要修改 Programmer。經過一番修改之後,Programmer 改成依賴 Programable,而 Computer 也依賴(實做) Programable,這樣我們就可以自由的新增各種 Programable 的工具或修改工具,而不影響到 Programmer 這個 high level 的 module。