发布于 

设计模式漫谈:从依赖注入看模块解藕

依赖

初学面向对象时,经常会举的一个例子是购物,从这个过程里很容易抽象出Customer类:

1
2
3
4
5
6
7
8
9

class Customer{

// 下订单
public void order(Product product)();

// 支付
public void pay();
}

而Customer的buy行为需要传入一个Product类对象,代表这个行为需要考虑Product的内部定义:这种需要关系,就是依赖

依赖倒置原则

面向对象设计模式七大设计原则中有一条依赖倒置原则,在模块间依赖关系的设计上可以为我们提供一些指导:

细节应该依赖于抽象,而抽象不应该依赖于细节。面向接口编程,而不是面向实现编程

这句话和面向对象设计模式中的很多理念一样,总结得过于精炼,对初学者不太友好,本鸟在这举例解释一下:

在上文提到的购物的场景中,支付通常要通过具体的支付方式,因此实际上还需要依赖一个支付工具类,供pay方法使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

class Customer{

//具体的支付方式
private Alipay wallet;

public Customer(){
wallet = new Alipay();
}

// 下订单
public void order(Product product)();

// 支付
public void pay(){
//调用支付工具进行支付动作
}
}

这个时候Customer相对于支付工具类属于高层模块,当这里的支付工具类是某种具体的支付方式比如Alipay Wallet,那么这种依赖就是耦合度较高的——因为它依赖了一种具体的支付方式,如果Alipay的某些特性发生了改变,Customer类的实现就有可能不得不需要更改。

这样的设计就违背了细节依赖于抽象的原则,因此建议将支付工具的共性抽象出来,形成一个抽象类PayInstrument或者接口Payable,Customer依赖此抽象类,pay方法的具体实现从PayInstrument出发,避免具体支付方式特性的影响,这样的即便日后当前使用的某种具体的支付工具发生了更改,pay方法的实现也无需改动,满足了依赖倒置原则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

class Customer{

private PayInstrument payInstrument;//具体的支付方式

public Customer(){
// payInstrument = new WeChatPay();
payInstrument = new Alipay();
}

// 下订单
public void order(Product product)();

// 支付
public void pay(){
//调用支付工具进行支付动作
}
}

控制反转

虽然修改后的代码满足了依赖倒置原则,但仍然存在一点问题,如果需要更改支付方式,需要更改的Customer的构造函数,并不是一个足够好的抽象封装,对于这种问题,提出控制反转的设计理念,将高层模块(Customer)对底层模块(PayInstrument)的依赖控制权(实例化过程)移交出去,其中一种方式就是随构造函数传入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

class Customer{

private PayInstrument payInstrument;//具体的支付方式

public Customer(PayInstrument payInstrument){
this.payInstrument = payInstrument;
}

// 下订单
public void order(Product product)();

// 支付
public void pay(){
//调用支付工具进行支付动作
}
}

而使用传入具体支付工具实例化Customer的地方(某个外部类),称之为IoC容器

依赖注入

依赖注入是控制反转的一种实现方式,将依赖的底层模块实例传入到高层模块对象中,除了上文提到的构造函数注入方式,还有两种依赖注入方式:

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

class Customer implements DependencySetable {

private PayInstrument payInstrument;//具体的支付方式

//接口方式注入
@Override
public void set(PayInstrument payInstrument){
this.payInstrument = payInstrument;
}

//构造函数注入
public Customer(PayInstrument payInstrument){
this.payInstrument = payInstrument;
}

//setter方式注入
public void setPayInstrument(PayInstrument payInstrument){
this.payInstrument = payInstrument;
}

// 下订单
public void order(Product product)();

// 支付
public void pay(){
//调用支付工具进行支付动作
}
}

依赖注入与控制反转的关系

1. 控制反转是一种在软件工程中解耦合的思想,调用类只依赖接口,而不依赖具体的实现类,减少了耦合。控制权交给了容器,在运行的时候才由容器决定将具体的实现动态的“注入”到调用类的对象中。

2. 依赖注入是一种设计模式,可以作为控制反转的一种实现方式。依赖注入就是将实例变量传入到一个对象中去(Dependency injection means giving an object its instance variables)。

3. 通过IoC框架,类A依赖类B的强耦合关系可以在运行时通过容器建立,也就是说把创建B实例的工作移交给容器,类A只管使用就可以。

(周末抽时间搞一下markdown支持,后边模块解藕未完待续)


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

本站由 @tsparrot 创建,使用 Stellar 作为主题。