设计模式学习,设计模式原则,以《设计模式之禅》为基础进行记录,今天记录两条原则:依赖倒置原则和接口隔离原则。
依赖倒置原则:
Dependence Inversion Principle(DIP):
High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
翻译过来,包含三层含义:
1、高层模块不应该依赖低层模块,两者都应该依赖其抽象;
2、抽象不应该依赖细节;
3、细节应该依赖抽象;
那什么是抽象?什么又是细节呢?在Java语言中,抽象就是指接口或抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,也就是可以加上一个关键字new产生一个对象。依赖倒置原则在Java语言中的表现就是:
1、模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
2、接口或抽象类不依赖于实现类;
3、实现类依赖接口或抽象类。
更加精简的定义就是“面向接口编程”——OOD(Object-OrientedDesign,面向对象设计)的精髓之一。
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
看如下类图:
根据类图可以很容易实现一个场景,就是让司机A开奔驰车。
DriverzhangSan=new Driver();
Benzbenz=new Benz();
//张三开奔驰车
zhangSan.drive(benz);
那么如果司机需要靠宝马怎么办?
我们没有办法让张三开动起来,为什么?张三没有开动宝马车的方法呀!
一个拿有C驾照的司机竟然只能开奔驰车而不能开宝马车,这也太不合理了!
在现实世界都不允许存在这种情况,何况程序还是对现实世界的抽象,我们的设计出现了问题:司机类和奔驰车类之间是紧耦合的关系,其导致的结果就是系统的可维护性大大降低,可读性降低,两个相似的类需要阅读两个文件,你乐意吗?还有稳定性,什么是稳定性?固化的、健壮的才是稳定的,这里只是增加了一个车类就需要修改司机类,这不是稳定性,这是易变的。
引入DIP的设计:
建立两个接口:IDriver和ICar,分别定义了司机和汽车的各个职能,司机就是驾驶汽车,必须实现drive()方法。
在IDriver中,通过传入ICar接口实现了抽象之间的依赖关系,Driver实现类也传入了ICar接口,至于到底是哪个型号的Car,需要在高层模块中声明。
IDriverzhangSan=new Driver();
ICarbenz=new Benz();
//张三开奔驰车
zhangSan.drive(benz);
在新增加低层模块时,只修改了业务场景类,也就是高层模块,对其他低层模块如Driver类不需要做任何修改,业务就可以运行,把“变更”引起的风险扩散降低到最小。
依赖的传递性:依赖是可以传递的,A对象依赖B对象,B又依赖C,C又依赖D……生生不息,依赖不止,记住一点:只要做到抽象依赖,即使是多层的依赖传递也无所畏惧!
对象的依赖关系有三种方式来传递,如下所示。
1.构造函数传递依赖对象。
在类中通过构造函数声明依赖对象,按照依赖注入的说法,这种方式叫做构造函数注入:
public interface Idriver{
//是司机就应该会驾驶汽车
public void drive();
}
public class Driver implements IDriver{
private ICar car;
//构造函数注入
public Driver(ICar_car){
this.car=_car;
}
//司机的主要职责就是驾驶汽车
public void drive(){
this.car.run();
}
}
2.Setter方法传递依赖对象
在抽象中设置Setter方法声明依赖关系,依照依赖注入的说法,这是Setter依赖注入:
public interface IDriver{
//车辆型号
public void setCar(ICar car);
//是司机就应该会驾驶汽车
publicvoid drive();
}
3.接口声明依赖对象
在接口的方法中声明依赖对象,即之前举例的类图。
实践总结:
依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,我们怎么在项目中使用这个规则呢?只要遵循以下的几个规则就可以:
1、每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
2、变量的表面类型尽量是接口或者是抽象类
3、任何类都不应该从具体类派生
4、尽量不要覆写基类的方法
5、结合里氏替换原则使用
依赖倒置原则是6个设计原则中最难以实现的原则,它是实现开闭原则的重要途径,依赖倒置原则没有实现,就别想实现对扩展开放,对修改关闭。在项目中,大家只要记住是“面向接口编程”就基本上抓住了依赖倒置原则的核心。
接口隔离原则
Interface Segregation Principle(ISP):
1、Clients should not be forced to depend upon interfaces that they don't use.
(客户端不应该依赖它不需要的接口)
2、The dependency of one class to another one should depend on the smallest possible interface.
(类间的依赖关系应该建立在最小的接口上)
我们可以把这两个定义概括为一句话:建立单一接口,不要建立臃肿庞大的接口。再通俗一点讲:接口尽量细化,同时接口中的方法尽量少。
与单一职责的区别:
接口隔离原则与单一职责的审视角度是不相同的,单一职责要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。
如下类图:
定义了一个IPettyGirl接口,声明所有的美女都应该有goodLooking、niceFigure和great-Temperament,然后又定义了一个抽象类AbstractSearcher,其作用就是搜索美女并显示其信息,只要美女都按照这个规范定义,Searcher(星探)就轻松多了。
我们想想这个程序有没有问题,思考一下IPettyGirl这个接口,这个接口是否做到了最优化设计?
随着时代的发展我们的审美观也在变化,当你发现有一个女孩,脸蛋不怎么样,身材也一般般,但是气质非常好,我相信大部分人都会把这样的女孩叫美女,审美素质提升了,就产生了气质型美女,但是我们的接口却定义了美女必须是三者都具备,按照这个标准,气质型美女就不能算美女,那怎么办?可能你要说了,我重新扩展一个美女类,只实现greatTemperament方法,其他两个方法置空,什么都不写,不就可以了吗?聪明,但是行不通!为什么呢?星探AbstractSearcher依赖的是IPettyGirl接口,它有三个方法,你只实现了两个方法,星探的方法是不是要修改?我们上面的程序打印出来的信息少了两条,还让星探怎么去辨别是不是美女呢?
分析到这里,我们发现接口IPettyGirl的设计是有缺陷的,过于庞大了,容纳了一些可变的因素,根据接口隔离原则,星探AbstractSearcher应该依赖于具有部分特质的女孩子,而我们却把这些特质都封装了起来,放到了一个接口中,封装过度了!问题找到了,我们重新设计一下类图:
把原IPettyGirl接口拆分为两个接口,一种是外形美的美女IGoodBodyGirl,这类美女的特点就是脸蛋和身材极棒,超一流,但是没有审美素质,比如随地吐痰,文化程度比较低;另外一种是气质美的美女IGreatTemperamentGirl,谈吐和修养都非常高。我们把一个比较臃肿的接口拆分成了两个专门的接口,灵活性提高了,可维护性也增加了,不管以后是要外形美的美女还是气质美的美女都可以轻松地通过PettyGirl定义。
通过这样的重构以后,不管以后是要气质美女还是要外形美女,都可以保持接口的稳定。当然,你可能要说了,以后可能审美观点再发生改变,只有脸蛋好看就是美女,那这个IGoodBody接口还是要修改的呀,确实是,但是设计是有限度的,不能无限地考虑未来的变更情况,否则就会陷入设计的泥潭中而不能自拔。
以上把一个臃肿的接口变更为两个独立的接口所依赖的原则就是接口隔离原则,让星探AbstractSearcher依赖两个专用的接口比依赖一个综合的接口要灵活。接口是我们设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩散,提高系统的灵活性和可维护性。
接口隔离原则是对接口进行规范约束,其包含以下4层含义:
1、接口要尽量小
这是接口隔离原则的核心定义,不出现臃肿的接口(FatInterface),但是“小”是有限度的,首先就是不能违反单一职责原则。
2、接口要高内聚
什么是高内聚?高内聚就是提高接口、类、模块的处理能力,减少对外的交互。具体到接口隔离原则就是,要求在接口中尽量少公布public方法,接口是对外的承诺,承诺越少对系统的开发越有利,变更的风险也就越少,同时也有利于降低成本。
3、定制服务
一个系统或系统内的模块之间必然会有耦合,有耦合就要有相互访问的接口,我们设计时就需要为各个访问者(即客户端)定制服务,什么是定制服务?定制服务就是单独为一个个体提供优良的服务。采用定制服务就必然有一个要求:只提供访问者需要的方法。
4、接口设计是有限度的
接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低。
接口隔离原则是对接口的定义,同时也是对类的定义,接口和类尽量使用原子接口或原子类来组装。但是,这个原子该怎么划分是设计模式中的一大难题,在实践中可以根据以下几个规则来衡量:
1、一个接口只服务于一个子模块或业务逻辑;
2、通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量让接口中的方法简单紧凑,而不是方法大杂烩;
3、已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理;
4、了解环境,拒绝盲从。每个项目或产品都有特定的环境因素,别看到大师是这样做的你就照抄。千万别,环境不同,接口拆分的标准就不同。深入了解业务逻辑,最好的接口设计就出自你的手中!
原文链接:https://www.f2er.com/javaschema/284776.html