What is Dependency Injection?

在 Angular 中 Dependency Injection 是个非常大的特点,Dependency Injection 是一种设计模式,主要是用於将相关的程序由外部注入进 component 中而不是在 component 中创建以实现解耦的目的,可以有效地减低维护的复杂度。

img

What is Coupling(藕合)

Coupling(藕合) 又可以称为 Dependency(依赖),简单来说就是程序与程序之间互相具有依赖性,在一个专案中如果彼此的依赖性越高则代表越难维护

一般在开发专案时会将功能相关的部分结合起来作为一个 Class 以提供给其他地方使用,当某个地方需要使用到这个 Class 实则需要再对应的地方使用关键字 new 产生一个新的物件,这样才可以使用到里面的 methodproperty,来举个例子某一个国小的教室 (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();

https://ithelp.ithome.com.tw/upload/images/20220216/20124767IBMhMMKCLW.png

虽然这种写法可以完成目标但如果今天需要将数学老师变为英文老师的话,那就需要将 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 中(注入)

https://ithelp.ithome.com.tw/upload/images/20220216/20124767IBMhMMKCLW.png

使用 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");
  }
}

https://ithelp.ithome.com.tw/upload/images/20220216/20124767hWndZPGwti.png

Dependency Inversion Principle (依赖反转)

Dependency Inversion Principle:依赖反转,又称为依赖反向或依赖倒转

当专案越来越大的时候,就需要比 Dependency Injection 更有弹性的设计模式的出现,接着我们把上面的例子改一下,现在我们从一间教室变成多间,一位老师也变成多位老师且每个老师都用相同的方法但不同的内容教课,可能想一想就会觉得非常的复杂,不过没关系我们可以把他整理一下

  1. 有一个地方专门培训老师,所以教导出来的老师教学模式都一样 ( 每个 Class 中有着相同的 method )
  2. 虽然教学模式一样,但因为每个老师有不同的专业,所以教学的内容都不一样
  3. 将每位老师分配到不同的教室中
  4. 每间教室的老师都用着相同的教学模式

有一个地方专门培训老师,所以教导出来的老师教学模式都一样 ( 每个 Class 中有着相同的 method )

要每一个 Class 都有着相同的格式的最好方法就是建立一个 interface 去规定需要哪些教学方式( method )

interface NormalSchool {
    Teaching: () => void;  // 教学
    Test: () => void;      // 考试
    QA: () => void;        // 提问
}

虽然教学模式一样,但因为每个老师有不同的专业,所以教学的内容都不一样

可以建立多个不同老师 ( Class ),透过 implementsinterface 让每个老师 ( Class ) 有相同的教学模式 ( methods )。

  1. 数学老师
class MathTeacher implements NormalSchool {
    Teaching() {
        console.log('Teach Math');
    }

    Test() {
        console.log('Test 1 + 1 = 2');
    }

    QA() {
        console.log('Math QA Time!')
    }
}
  1. 英文老师
class EnglishTeacher implements NormalSchool {
    Teaching() {
        console.log('Teach English');
    }

    Test() {
        console.log('Test A, B, C, D');
    }

    QA() {
        console.log('English QA Time!')
    }
}
  1. 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 teacher
const englishTeacher = new EnglishTeacher();           // Create english teacher
const javascriptTeacher = new JavascriptTeacher();     // Create javascript teacher

const room1 = new ClassRoom(mathTeacher);              // 将数学老师放入教室 1
const room2 = new ClassRoom(englishTeacher);           // 将英文老师放入教室 2
const room3 = new ClassRoom(javascriptTeacher);        // 将 Javascript 老师放入教室 3

room1.classTeaching();
room2.classTest();
room3.classQA();

https://ithelp.ithome.com.tw/upload/images/20220216/20124767OuZTlaSPi3.png

上面的结果可以发现每间教室可以接受不同的老师,只要是从师范大学中出来的 ( implements NormalSchool ) 就可已放入,并且每个不同的老师都有着相同的教学方式 ( methods ) 但教授的内容却是不同的,之後如果某个教室需要更换老师或是需要新增一个新的老师,其他部分就不需要更改也不会受到影响,这就是 Dependency Inversion Principle

结论

本章介绍了什麽是 Dependency Injection 的概念与在 Typescript 中如何实现,在我刚接触 Angular 的时候对於 Dependency Injection 来说可以说是一头雾水,只知道只要在 Constructor 里面宣告就可以用了,但根本不知道这麽做的目的是什麽,直到我去看了 Jun 大大对於 Dependency Injection 的一些概念与举例所以才比较明白是什麽,不过 Jun 大大的文章是用 Java 写的,所以本章算是把 Jun 大大的文转换为 Typescript 做个纪录,大家可以去看 Jun 大大原本的文章或是其他文章,都写得非常好!

Reference


<<:  JS [撞墙] document.querySelector("").checked

>>:  【笔记】git新手教学

[Day 27]TensorFlow运算方法

我们今天来聊聊TensorFlow运算的几个较为常用的方法,不像其他语言的加减乘除,TensorFl...

高层架构介绍

本系列文章同步发布於笔者网站 我们在前几篇文章介绍了 NIST 对云端的定义,从今天开始文章将会进入...

【Side Project】 点菜单功能实作 - 前台资料传到後台

这篇我们接着做: 取得网页上栏位资料 资料送往後台 资料写回资料库 取得栏位资料 在送资料到後台之前...

Day 27 RSpec 的 Mock & Stub

该文章同步发布於:我的部落格 在我一开始学习写 Rails 测试时,会有很常见的问题,就是到底什麽...

Day09 X Resource Hint & Non-Blocking Script Tag

经过昨天的内容,读者们应该对於网页的渲染流程有大致的理解了。 我们再小小复习一下,大致上网页的渲染...