设计模式漫谈:从依赖注入看模块解藕
依赖
初学面向对象时,经常会举的一个例子是购物,从这个过程里很容易抽象出Customer类:
1 |
|
而Customer的buy行为需要传入一个Product类对象,代表这个行为需要考虑Product的内部定义:这种需要关系,就是依赖
依赖倒置原则
面向对象设计模式七大设计原则中有一条依赖倒置原则,在模块间依赖关系的设计上可以为我们提供一些指导:
细节应该依赖于抽象,而抽象不应该依赖于细节。面向接口编程,而不是面向实现编程
这句话和面向对象设计模式中的很多理念一样,总结得过于精炼,对初学者不太友好,本鸟在这举例解释一下:
在上文提到的购物的场景中,支付通常要通过具体的支付方式,因此实际上还需要依赖一个支付工具类,供pay方法使用:
1 |
|
这个时候Customer相对于支付工具类属于高层模块,当这里的支付工具类是某种具体的支付方式比如Alipay Wallet,那么这种依赖就是耦合度较高的——因为它依赖了一种具体的支付方式,如果Alipay的某些特性发生了改变,Customer类的实现就有可能不得不需要更改。
这样的设计就违背了细节依赖于抽象的原则,因此建议将支付工具的共性抽象出来,形成一个抽象类PayInstrument或者接口Payable,Customer依赖此抽象类,pay方法的具体实现从PayInstrument出发,避免具体支付方式特性的影响,这样的即便日后当前使用的某种具体的支付工具发生了更改,pay方法的实现也无需改动,满足了依赖倒置原则。
1 |
|
控制反转
虽然修改后的代码满足了依赖倒置原则,但仍然存在一点问题,如果需要更改支付方式,需要更改的Customer的构造函数,并不是一个足够好的抽象封装,对于这种问题,提出控制反转的设计理念,将高层模块(Customer)对底层模块(PayInstrument)的依赖控制权(实例化过程)移交出去,其中一种方式就是随构造函数传入依赖:
1 |
|
而使用传入具体支付工具实例化Customer的地方(某个外部类),称之为IoC容器
依赖注入
依赖注入是控制反转的一种实现方式,将依赖的底层模块实例传入到高层模块对象中,除了上文提到的构造函数注入方式,还有两种依赖注入方式:
1 |
|
依赖注入与控制反转的关系
1. 控制反转是一种在软件工程中解耦合的思想,调用类只依赖接口,而不依赖具体的实现类,减少了耦合。控制权交给了容器,在运行的时候才由容器决定将具体的实现动态的“注入”到调用类的对象中。
2. 依赖注入是一种设计模式,可以作为控制反转的一种实现方式。依赖注入就是将实例变量传入到一个对象中去(Dependency injection means giving an object its instance variables)。
3. 通过IoC框架,类A依赖类B的强耦合关系可以在运行时通过容器建立,也就是说把创建B实例的工作移交给容器,类A只管使用就可以。
(周末抽时间搞一下markdown支持,后边模块解藕未完待续)