概述
高层模块不应依赖低层模块,二者都应该依赖其抽象。而抽象不应依赖细节,细节应该依赖抽象。依赖倒置原则的中心思想其实就是面向接口编程 。
相对于细节的多变性,抽象的东西会稳定的多,所以以抽象为基础搭建的架构自然也会比以细节为基础搭建的架构稳定的多 。
使用接口或抽象类的目的是为了更好的制定规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
相信有读过spring framework源码的同学应该对这一点深以为是,比如其核心接口之一的BeanFactory接口之下就继承了AutowireCapableBeanFactory、HierarchicalBeanFactory、ListableBeanFactory接口,而这些接口又被多个抽象类有选择实现,正是对依赖倒置原则的应用才使得spring framework 框架具有了极高的健壮性和扩展性。
三寸反骨
我们不妨从相反的角度出发,写个反例看看会有什么问题,今日反骨颇重,就是不想遵循依赖倒置原则。
比如我们现在需要编写一个简单的Person类,让他可以接受邮件消息即可。
反例
java
public class DependecyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
}
}
class Email{
public String getInfo(){
return "电子邮件信息: Hello,Email";
}
}
class Person {
public void receive(Email email){
System.out.println(email.getInfo());
}
}
编程的过程十分愉快,如此简单的需求甚至不需要聪明的我们多思考一秒。运行效果也完全满足预期。
但是,来了一个不好不坏的消息:"能力需要扩展,客户要求,除了收到email的能力外,微信消息也想收到!"
"反骨仔想了想,不要紧,我新增类,同时Person类也要增加相应的方法就好了呀"!
于是他加班1小时完成了这个需求,自信满满的走了。
但是,第二天又来了个不好不坏的消息:"客户对产品很满意,同时要求除了想收到email、微信消息之外,还想扩展几十个软件的消息,清单包含:"QQ、微博、墨迹天气、钉钉..."
反骨仔爆炸了,因为这个需求如果继续按他的思路去实现,类也爆炸了。实现的方法更是爆炸到难以维护。
所以说一定要设计先行,一个优秀的设计可能会占据相当长的开发时间,但是会为后期的扩展和维护提供强有力的保障!
优化设计
我们把时间推回两天前,接到需求的我们首先便进行了深度剖析并达成了共识:"依赖倒置原则必须遵守!"于是,有了如下代码:
java
public class DependecyInversion {
public static void main(String[] args) {
Person person = new Person();
person.receive(new Email());
person.receive(new WeChat());
}
}
interface IReceive{
public String getInfo();
}
class Email implements IReceive{
public String getInfo(){
return "电子邮件信息: Hello,Email";
}
}
class WeChat implements IReceive{
public String getInfo(){
return "微信信息: Hello,WeChat";
}
}
class Person {
public void receive(IReceive receiver){
System.out.println(receiver.getInfo());
}
}
当客户说我要继续扩展几十个消息入口时,我们会发现,我们对于person类不需要做任何改动了,不过是需要遵照IReceive接口规范去实现新扩展的业务细节就可以了。善莫大焉。
依赖关系传递三板斧
常见的依赖关系传递有三种方式:
接口传递
java
public class DependencyPass {
public static void main(String[] args) {
ChangHong changHongTv = new ChangHong();
OpenAndClose openAndClose = new OpenAndClose();
openAndClose.open(changHongTv);
}
}
class ChangHong implements ITV,ITV2,ITV3{
@Override
public void play() {
System.out.println("长虹电视机打开了。");
}
}
interface IOpenAndClose{
public void open(ITV tv);
}
interface ITV{
public void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose{
@Override
public void open(ITV tv) {
tv.play();
}
}
构造方法传递
java
public class DependencyPass {
public static void main(String[] args) {
ChangHong changHongTv = new ChangHong();
OpenAndClose2 openAndClose2 = new OpenAndClose2(changHongTv);
openAndClose2.open();
}
}
class ChangHong implements ITV,ITV2,ITV3{
@Override
public void play() {
System.out.println("长虹电视机打开了。");
}
}
interface IOpenAndClose2{
public void open();
}
interface ITV2{
public void play();
}
class OpenAndClose2 implements IOpenAndClose2{
public ITV2 tv;
public OpenAndClose2(ITV2 tv){
this.tv = tv;
}
@Override
public void open() {
this.tv.play();
}
}
setter方法传递
java
public class DependencyPass {
public static void main(String[] args) {
ChangHong changHongTv = new ChangHong();
OpenAndClose3 openAndClose3 = new OpenAndClose3();
openAndClose3.setTv(changHongTv);
openAndClose3.open();
}
}
class ChangHong implements ITV,ITV2,ITV3{
@Override
public void play() {
System.out.println("长虹电视机打开了。");
}
}
interface IOpenAndClose3{
public void open();
public void setTv(ITV3 itv3);
}
interface ITV3{
public void play();
}
class OpenAndClose3 implements IOpenAndClose3{
private ITV3 itv3;
@Override
public void setTv(ITV3 itv3) {
this.itv3 = itv3;
}
@Override
public void open() {
this.itv3.play();
}
}
结
- 底层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好;
- 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间就存在一个缓冲层,利于程序扩展和优化;
- 继承时遵循里氏替换原则;
- 什么是里氏替换原则?下次讲!
关注我,共同进步,每周至少一更。------Wayne