依賴注入(DI)和控制反轉(IOC)基本是一個意思,由於提及來誰都離不開誰。 簡單來講,類A依賴類B,但A不控制B的建立和銷燬,僅使用B,那麼B的控制權則交給A以外處理,這叫控制反轉(IOC)。 因爲A依賴於B,所以在A中必然要使用B的instance,咱們能夠經過A的構造函數將B的實例注入,好比:javascript
class B { }
class A {
constructor(b: B) {
console.log(b);
}
}
const b = new B();
// 將B的實例注入到a中
const a = new A(b);
複製代碼
這個過程叫依賴注入(DI)。 那麼什麼是IOC Container(容器)? 在剛剛的例子中,將B的實例注入到A的構造函數中的這個過程是咱們手動操做的,比較麻煩,特別是當類的關係變多變複雜時,這種方式顯得很難維護。 所以IOC容器就是爲了解決這樣的問題,IOC容器負責管理對象的生命週期、依賴關係等,實現對象的依賴查找以及依賴注入。 好比Java的Spring以及前端@Angular框架的依賴注入器(DI)就是屬於IOC容器。前端
接下來我將經過代碼的形式對比使用依賴注入相比非依賴注入的好處體如今哪。java
咱們先來看一段傳統的實現代碼(非DI) car.ts框架
// 引擎
export class Engine {
public cylinders = '引擎發動機1';
}
// 輪胎
export class Tires {
public make = '品牌';
}
export class Car {
public engine: Engine;
public tires: Tires;
public description = 'No DI';
constructor() {
this.engine = new Engine();
this.tires = new Tires();
}
// Method using the engine and tires
drive() {
return `${this.description} car with ` +
`${this.engine.cylinders} cylinders and ${this.tires.make} tires.`;
}
}
複製代碼
在以上代碼中,Car類沒有經過第三方容器而是親自建立了一個引擎(engine)和一些輪胎(tires),這樣的代碼耦合度比較高,這樣會存在如下問題:函數
問題1:若是有一天對引擎進行升級,代碼以下:性能
// 引擎
export class Engine {
public cylinders = '';
constructor(_cylinders:string) {
this.cylinders = _cylinders;
}
}
複製代碼
在建立引擎的時候須要傳入一個參數,那麼這時候就須要修改Car類裏的new Engine(parameter),這樣就致使Car類被破壞了,這裏請思考一個問題:要怎麼作才能使引擎升級的時候不須要修改Car類呢?(答案:DI)單元測試
問題2:若是想在Car上使用不一樣品牌的輪胎,代碼以下:測試
// 輪胎
export class Tires {
public make = '品牌';
}
export class Tires1 extends Tires {
public make = '品牌1';
}
export class Tires2 extends Tires {
public make = '品牌2';
}
export class Car {
//。。。。。。其餘代碼省略。。。。。。。
public tires: Tires;
constructor() {
this.tires = new Tires1();
}
}
複製代碼
此時又得從新修改Car的代碼,這裏請思考一個問題:要怎麼作才能使Car更換其餘不一樣品牌的輪胎的時候不須要修改Car類呢?(答案:DI)ui
問題3:如何實現數據共享,好比說車聯網,創建了一個Service數據中心,不一樣的Car經過Service實現數據通訊以及數據共享,若是是經過在Car裏new Service的方式,是沒法實現數據共享和通訊的,由於不一樣Car裏的Service不是同一個實例。this
這裏請思考一個問題:如何實現不一樣Car的數據通訊和共享呢?
問題4:測試比較難,根本沒法測試。 在示例代碼中,Car類依賴於Engine類和Tires類,而Engine和Tires又可能各自依賴於其餘的類,而其餘的類又可能有各自更多的依賴,在這樣層層的依賴關係中,因爲不能控制Car背後的隱藏依賴,要進行測試是比較難的,或者應該說,這樣的代碼是根本沒法進行測試的。 好比說想同時測試不一樣品牌的輪子的car的性能,由於car裏頭的new已經寫死了,所以沒法作到。 好比說想同時測試不一樣參數的引擎的car的性能,由於car裏頭的new已經寫死了,所以沒法作到。 除非是每次只測試一種狀況,下面拿測試不一樣品牌的輪子來舉例: 先測試品牌1的輪子: car.ts
export class Tires {
public make = '品牌';
}
export class Tires1 extends Tires {
public make = '品牌1';
}
export class Tires2 extends Tires {
public make = '品牌2';
}
export class Car {
public tires: Tires;
public description = 'No DI';
constructor() {
// new 一個品牌1的輪子
this.tires = new Tires1();
}
// Method using the engine and tires
drive() {
return `${this.description} car with ` + ` ${this.tires.make} tires.`;
}
}
複製代碼
測試程序car.spec.ts
import { Car } from './car.ts';
describe('Car類單元測試', function () {
it('測試品牌1輪子的Car的駕駛性能', function () {
const car = new Car();
car.drive().should.equal('No DI car with 品牌1 tires.');
})
})
複製代碼
以上代碼對輪子品牌1進行測試,輸出輪子品牌1的car的駕駛性能。 接着對輪子品牌2進行測試:修改Car類,將this.tires = new Tires1();修改成 this.tires = new Tires2();此時輸出輪子品牌2的car的駕駛性能。
這樣的測試效率是很低的,由於每次只能手動的測試一種狀況,若是再加上引擎的測試,那多種混合狀況就更多了,根本就不能作到自動測試,所謂的自動測試,是一次性將全部的狀況都寫到一個單元測試裏,一次運行,全部狀況都會被測試到,當測試經過了,那麼就說明代碼達到了預期。
針對以上問題,咱們來看看使用DI的好處。
接下來將演示使用DI來解決以上的4個問題。 先看使用DI實現的car.ts代碼: car.ts
export class Engine {
public cylinders = '引擎發動機1';
}
export class Tires {
public make = '品牌';
}
export class Tires1 extends Tires {
public make = '品牌1';
}
export class Tires2 extends Tires {
public make = '品牌2';
}
export class Car {
public description = 'DI';
// 經過構造函數注入Engine和Tires
constructor(public engine: Engine, public tires: Tires) {}
// Method using the engine and tires
drive() {
return `${this.description} car with ` +
`${this.engine.cylinders} cylinders and ${this.tires.make} tires.`;
}
}
複製代碼
在以上代碼中,經過往構造函數中傳入engine和tires來建立Car,Car類再也不親自建立engine和tires,而是消費它們,此時最大的好處就是engine和tires與Car解除了強耦的關係。在new Car的時候,能夠傳入任何類型的Engine和Tires,即 let car = new Car(new Engine(),new Tires());
解決問題1:若是有一天對引擎進行升級,代碼以下:
export class Engine {
public cylinders = '';
constructor(_cylinders:string) {
this.cylinders = _cylinders;
}
}
複製代碼
在建立引擎的時候須要傳入一個參數,這時候不須要修改Car類,只須要修改主程序便可:
主程序代碼:
main(){
const car = new Car(new Engine('引擎啓動機2'), new Tires1());
car.drive();
}
複製代碼
解決問題2:若是想在Car上使用不一樣品牌的輪胎,代碼以下:
export class Tires {
public make = '品牌';
}
export class Tire1 extends Tires {
public make = '品牌1';
}
export class Tire2 extends Tires {
public make = '品牌2';
}
export class Car {
//。。。。。。其餘代碼省略。。。。。。。
constructor(public engine: Engine, public tires: Tires) {}
}
複製代碼
此時不須要修改Car類,只須要修改主程序便可: 主程序代碼:
main(){
// 使用品牌2的輪胎
const car = new Car(new Engine('引擎啓動機2'), new Tires2());
car.drive();
}
複製代碼
解決問題3:如何實現數據共享,好比說車聯網,創建一個Service數據中心(就像angular的Service層,能夠給多個component共享),不一樣的Car經過Service實現數據通訊以及數據共享。 代碼以下: Service.ts
export class Service {
public data = '';
// 向Service存數據
setData(_data: string) {
this.data = _data;
}
// 從Service中取數據
getData() {
return this.data;
}
}
複製代碼
car.ts
export class Car {
constructor(public service: Service) { }
// 向Service存數據
setDataToService(_data: string) {
this.service.setData(_data);
}
// 從Service中取數據
getDataFromService() {
return this.service.getData();
}
}
複製代碼
此時主程序以下: 主程序代碼:
main(){
// 建立一個共享服務中心Service
const shareService = new Service();
const car1 = new Car(shareService);
const car2 = new Car(shareService);
// car1向服務中心存數據
car1.setDataToService('this data is from car1.');
// car2從服務中心取數據
car2.getDataFromService();
}
複製代碼
解決問題4:測試用例 在示例代碼中,Car類依賴於Engine類和Tires類,而Engine和Tires又可能各自依賴於其餘的類,而其餘的類又可能有各自的依賴,在這樣層層的依賴關係中,使用DI的代碼測試是比較簡單的。 測試程序以下: 測試程序 car.spec.ts
import { Car,Engine,Tires1, Tires2} from './car.ts';
// 測試程序入口
describe('Car類單元測試', function () {
const engine1 = new Engine('引擎發動機1');
const engine2 = new Engine('引擎發動機2');
const tires1 = new Tires1();
const tires2 = new Tires2();
it('測試引擎1 輪胎品牌1', function () {
const car = new Car(engine1, tires1);
car.drive().should.equal('DI car with 引擎發動機1 cylinders and 品牌1 tires.');
});
it('測試引擎1 輪胎品牌2', function () {
const car = new Car(engine1, tires2);
car.drive().should.equal('DI car with 引擎發動機1 cylinders and 品牌2 tires.');
});
it('測試引擎2 輪胎品牌1', function () {
const car = new Car(engine2, tires1);
car.drive().should.equal('DI car with 引擎發動機2 cylinders and 品牌1 tires.');
});
it('測試引擎2 輪胎品牌2', function () {
const car = new Car(engine2, tires2);
car.drive().should.equal('DI car with 引擎發動機2 cylinders and 品牌2 tires.');
});
})
複製代碼
此時以爲很棒有木有,自動測試的思想就是這樣的,將全部的狀況的代碼都配置好,一次運行,全部的狀況均可以測試到。
至此,若是看懂以上的話,DI的思想以及爲何要用DI就應該能夠理解了。