在 Angular 中 Dependency Injection 是个非常大的特点,Dependency Injection 是一种设计模式,主要是用于将相关的程式由外部注入进 component 中
而不是在 component 中创建以实现解耦
的目的,可以有效地减低维护的複杂度。
What is Coupling(藕合)
Coupling(藕合) 又可以称为 Dependency(依赖),简单来说就是程式与程式之间互相具有依赖性,在一个专案中如果彼此的依赖性越高则代表越难维护
。
一般在开发专案时会将功能相关的部分结合起来作为一个 Class 以提供给其他地方使用,当某个地方需要使用到这个 Class 实则需要再对应的地方使用关键字 new
产生一个新的物件,这样才可以使用到里面的 method
或 property
,来举个例子某一个国小的教室 (ClassRoom) 需要一个老师,老师的功能有 Teaching
, Test
, QA
这三种功能。
class MathTeacher { constructor() { } Teaching() { console.log('Teach Math'); } Test() { console.log('Test 1 + 1 = 2'); } QA() { console.log('QA Time'); }}
而现在的情境是在这个教室中有一个数学老师在执行这三种功能并执行它
class ClassRoom { mathTeach = new MathTeacher(); ClassTeaching() { this.mathTeach.Teaching(); } ClassTest() { this.mathTeach.Test(); } ClassQA() { this.mathTeach.QA(); }}room = new ClassRoom();room.ClassTeaching();room.ClassTest();room.ClassQA();
虽然这种写法可以完成目标但如果今天需要将数学老师变为英文老师的话,那就需要将 mathTeach = new MathTeacher();
这边更改为 englishTeacher = new EnglishTeacher();
,这种因为一个程式变动而导致另一个程式也需要随之修改的行为就称为高藕合(Coupling)
,如果专案很大的话这种高耦合会让专案的维护与开发变得越来越困难。
Dependency Injection (依赖注入)
为了减轻每个程式中互相的依赖,这时就诞生了 Dependency Injection 这种设计模式,他主要的目的是通过将有关连的程式利用注入的方式由外部注入至需要的程式,而不是像过去一样在内部创建,而达成 DI 有两种方法
使用建构子 (Contructor)
首先跟刚刚一样,新增一个Teacher 的 class (老师类别),将老师的技能 (Methods) 写入,可以随需求更改教学的内容。
class Teacher { constructor() { } Teaching() { console.log('Teach Math'); } Test() { console.log('Test 1 + 1 = 2'); } QA() { console.log('QA Time'); }}
接着对 ClassRoom Class 中修改 constructor 让需要授课的老师 class 由外部注入进来
class ClassRoom { constructor(private teacher: Teacher) {} // (1) ClassTeaching() { this.teacher.Teaching(); } ClassTest() { this.teacher.Test(); } ClassQA() { this.teacher.QA(); }}
(1): 利用 constructor 将原先依赖的程式注入进来接着使用这个新的 Room Class
const teach = new Teacher(); // (1)const room = new ClassRoom(teach); // (2)room.ClassTeaching();room.ClassTest();room.ClassQA();
(1): 在外部创建需要的 Class(2): 将需要的 Class 作为参数传入 ClassRoom Class 中(注入)使用 Setter
除了在 constructor 中宣告注入的 Class 之外,还可以利用 setter
做到相同的功能。
class ClassRoom { private _teacher!: Teacher constructor() {} set setTeacher(teacher: Teacher) { // (1) this._teacher = teacher; } ClassTeaching() { this._teacher.Teaching(); } ClassTest() { this._teacher.Test(); } ClassQA() { this._teacher.QA(); }}
(1): 创建一个 setter
用于将外部依赖的 Class 注入进来const teach = new Teacher(); // (1)const room = new ClassRoom(); // (2)room.setTeacher = teach; // (3)room.ClassTeaching();room.ClassTest();room.ClassQA();
(1): 在外部创建需要的 Class(2): 创建需要的 Class(3): 利用 ClassRoom
中的 setter
将外部依赖的 Class (Teacher) 注入当使用了 Dependency Injection 这种设计模式后,你想要从数学老师变更为英文老师的话,只需要在 Teacher
Class 中更改教学方式就好,其他都不用变就可以达到目的,可以打幅度的减低维护的成本
class Teacher { constructor() {} Teaching() { console.log("Teach English"); } Test() { console.log("Test A, B, C"); } QA() { console.log("QA English Time"); }}
Dependency Inversion Principle (依赖反转)
Dependency Inversion Principle:依赖反转,又称为依赖反向或依赖倒转
当专案越来越大的时候,就需要比 Dependency Injection 更有弹性的设计模式的出现,接着我们把上面的例子改一下,现在我们从一间教室变成多间,一位老师也变成多位老师且每个老师都用相同的方法但不同的内容教课,可能想一想就会觉得非常的複杂,不过没关係我们可以把他整理一下
有一个地方专门培训老师,所以教导出来的老师教学模式都一样 ( 每个 Class 中有着相同的 method )虽然教学模式一样,但因为每个老师有不同的专业,所以教学的内容都不一样将每位老师分配到不同的教室中每间教室的老师都用着相同的教学模式有一个地方专门培训老师,所以教导出来的老师教学模式都一样 ( 每个 Class 中有着相同的 method )
要每一个 Class 都有着相同的格式的最好方法就是建立一个 interface 去规定需要哪些教学方式( method )
interface NormalSchool { Teaching: () => void; // 教学 Test: () => void; // 考试 QA: () => void; // 提问}
虽然教学模式一样,但因为每个老师有不同的专业,所以教学的内容都不一样
可以建立多个不同老师 ( Class ),透过 implements
将 interface
让每个老师 ( Class ) 有相同的教学模式 ( methods )。
class MathTeacher implements NormalSchool { Teaching() { console.log('Teach Math'); } Test() { console.log('Test 1 + 1 = 2'); } QA() { console.log('Math QA Time!') }}
英文老师class EnglishTeacher implements NormalSchool { Teaching() { console.log('Teach English'); } Test() { console.log('Test A, B, C, D'); } QA() { console.log('English QA Time!') }}
Javascript 老师class JavascriptTeacher implements NormalSchool { Teaching() { console.log('Teach Javascript'); } Test() { console.log('Test program'); } QA() { console.log('program QA Time!') }}
将每位老师分配到不同的教室中
修改 ClassRoom 我们把外部注入的对象这定为只接受去过师範大学的老师才可以进入这个教室
class ClassRoom { constructor(private _teacher: NormalSchool) {} // 外部注入的对象必须是 NormalSchool classTeaching() { this._teacher.Teaching(); } classTest() { this._teacher.Test(); } classQA() { this._teacher.QA(); }}
将每位老师放入教室中并使用着相同的教育方式但教授的是不同的科目
3间教室指定老师使用师範大学的教学方式,去教自己专业科目的内容。
const mathTeacher = new MathTeacher(); // Create math teacherconst englishTeacher = new EnglishTeacher(); // Create english teacherconst javascriptTeacher = new JavascriptTeacher(); // Create javascript teacherconst room1 = new ClassRoom(mathTeacher); // 将数学老师放入教室 1const room2 = new ClassRoom(englishTeacher); // 将英文老师放入教室 2const room3 = new ClassRoom(javascriptTeacher); // 将 Javascript 老师放入教室 3room1.classTeaching();room2.classTest();room3.classQA();
上面的结果可以发现每间教室可以接受不同的老师,只要是从师範大学中出来的 ( implements NormalSchool ) 就可已放入,并且每个不同的老师都有着相同的教学方式 ( methods ) 但教授的内容却是不同的,之后如果某个教室需要更换老师或是需要新增一个新的老师,其他部分就不需要更改也不会受到影响,这就是 Dependency Inversion Principle
。
结论
本章介绍了什么是 Dependency Injection 的概念与在 Typescript 中如何实现,在我刚接触 Angular 的时候对于 Dependency Injection 来说可以说是一头雾水,只知道只要在 Constructor 里面宣告就可以用了,但根本不知道这么做的目的是什么,直到我去看了 Jun 大大对于 Dependency Injection 的一些概念与举例所以才比较明白是什么,不过 Jun 大大的文章是用 Java 写的,所以本章算是把 Jun 大大的文转换为 Typescript 做个纪录,大家可以去看 Jun 大大原本的文章或是其他文章,都写得非常好!