Java设计模式的设计原则
单一职责原则
单一职责原则的英文名称是Single Responsibility Principle,简称SRP。
1. 定义
单一职责原则的定义:应该有且仅有一个原因引起类的变更。
单一职责原则要求类的设计、接口的设计、方法的设计都是满足单一职责原则,就是要保证类、接口、方法只做一件事或者是同一类型、同一职责的操作。
2. 优点
- 类的复杂度降低,实现什么职责都有清晰明确的定义
- 可读性提高,复杂性降低,自然就可以提高可读性
- 可维护性提高,可读性提高,自然可维护性也提高
- 变更引起的风险降低。
3. 设计
在方法的设计时,要保证我们设计的方法只做一件事情。
在接口设计时,也要在设计的时候做到单一职责,这样可以降低维护的成本。
在设计接口的实现类时,需要考虑实际的应用场景。如果把单一职责强加到实现类上,会引起类的数量的剧增,从而极大的增加系统的复杂度。
里氏替换原则
里氏替换原则的英文名称是Liskov Substitution Principle, 简称LSP。
里氏替换原则为了最大的发挥继承的优势同时又降低继承的弊端。
1. 定义
1.1 继承
继承的优点
- 代码共享,减少创建类的工作量
- 提高代码的重用性
- 子类可以形似父类,但是又区别于父类
- 提高代码的可拓展性
- 提高产品和项目的开放性
继承的缺点
- 继承是可入侵的。只要是继承就必须让子类拥有父类的全部属性和方法
- 减低代码的灵活性。子类必须拥有父类的属性和方法,丰富了子类的同时也给子类增加了父类的限制
- 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改。
1.2 里氏替换原则的定义
里氏替换原则的定义:所有引用基类的地方必须能透明地使用其子类的对象。
通俗来讲,就是只要父类能出现的地方子类就可以出现,但是子类能出现的地方,父类未必能够出现。
2. 规则约束
2.1 子类必须完全实现父类的方法
我们在设计时,通常会定义一个接口或是抽象类,然后通过编码实现,然后调用类则直接传入接口或者抽象类作为参数,这样就是一个符合里氏替换原则的设计。
同样,在类中调用其他类时务必使用父类或接口,如果不能使用父类或接口,则说明类的设计时不符合里氏替换原则的。
如果子类不能完整地实现父类的方法,或者实现父类的某些方法在子类中发生的变异,那么建议断开继承关系,采用依赖、聚集、组合等关系替代。
2.2 子类可以有自己的个性
由于里氏替换原则规定,父类出现的地方子类就可以出现,但是子类出现的地方父类未必可以出现。因此,子类完全可以拥有自己的属性和方法,以丰富自身的功能。
2.3 覆盖或实现父类的方法时,输入参数可以被放大
子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或更宽松。
2.4 覆写或实现父类的方法时,输出结果可以被缩小
依赖倒置原则
依赖倒置原则的英文名称是:Dependence Inversion Principle, DIP
1. 定义
依赖倒置原则:
1. 高层的模块不应该依赖于底层模块,两者都应该依赖其抽象
2. 抽象不应该依赖细节
3. 细节应该依赖抽象
依赖倒置原则体现在Java语言中的表现是:
+ 模块间的依赖通过抽象产生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
+ 接口或抽象类不依赖于实现类;
+ 实现类依赖接口或抽象类。
依赖倒置原则也就是“面向接口编程”, 即OOD(Object-Oriented Design, 面向对象设计)的精髓之一。
2. 实例
我们先通过一个例子来说明为什么要用依赖倒置原则。
故事背景:小明最近刚刚拿了驾照,准备开车上道。一个新手司机要去驾驶一辆经典款大众甲壳虫。
我们先看一个UML图如何实现司机驾驶经典甲壳虫的。
但是,小明好不容易拿到了驾照,家里的车库里又不只是有甲壳虫这么一款车,他家里还有奔驰、宝马、奥迪等等,作为一个有尊严的马路杀手,怎么能只开这一款车那,于是我们之前做的设计就不能适用。
我们需要从新设计,让小明这个马路杀手可以去驾驶各种各样的小汽车。
我们通过接口的方式就可以实现了让司机驾驶多种车,只需要在驾驶不同的车型时传入不同的实现类就可以了。
这样我们就实现了接口或抽象不依赖实现类,而且依赖关系也是通过接口传递的。
3. 依赖的三种写法
无论以怎样的方式实现依赖,对象的依赖关系都是通过抽象传递的。
我们将通过以上的例子来说明如何实现以下三种依赖方式。
2.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.2 Setter方法传递依赖对象
//司机接口
public interface IDriver{
public void drive();
}
//司机实现类
public class Driver implements IDriver{
private ICar car;
public void setCar(ICar car){
this.car = car;
}
public void drive(){
this.car.run();
}
}
2.3 接口声明依赖对象
基于接口的方法就是我们在第二小节中实例中使用的方法,把接口作为参数传递给drive方法。
接口隔离原则
接口隔离原则的英文名称是:Interface Segregation Principle,简称ISP。
1. 定义
接口隔离原则的定义可以概括为:建立单一接口, 即接口不要过于庞大,应当尽量喜欢。接口的设计应该保证其接口内功能相对的少。
2. 约束
接口隔离原则是对接口进行规范约束,主要有一下四个方面:
- 接口要尽量小:本条约束主要是指希望程序中不会出现臃肿的接口,但是”小”的同时要保证接口不违反单一职责原则。
- 接口要高内聚:高内聚就是指要提高接口、类、模块的处理能力,减少对外部的交互。
- 定制服务:系统设计过程不同模块中会或多或少存在耦合,一旦出现耦合就需要提供相互访问的接口。因此我们需要在给访问者提供接口是提供定制服务,也就是只提供访问者需要的方法,
- 接口设计是有限度的:接口的设计粒度越小,系统越灵活。但是,灵活的同时也会带来结构的复杂化,开发难度增加,导致可维护性降低。因此设计过程中要根据业务需求和性能需求来判断这个”度”。
3. 实践规则
- 一个接口只服务于一个子模块或者业务逻辑
- 通过业务逻辑压缩接口中public方法
- 已经被污染的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转换处理
- 了解环境,拒绝盲从
迪米特法则
迪米特法则的英文名称是:Law of Demeter, LoD。迪米特法则也叫最少知识原则(Leask Knowledge Principle, LKP).
1. 定义
迪米特法则:一个对象对其他对象有最少的了解。即:一个类要对自己需要耦合或调用的类知道得最少。
粗俗来讲:调用者在调用其他的接口或者是类时,应当只知道被调用者提供了那些public
方法。保证调用者对别调用者最少的访问。
2. 约定
迪米特法则的核心观念就是类间解耦,弱耦合,从而提高类的复用率。但是,在使用迪米特法则的时候尤其要注意”度”的问题。与接口隔离原则一样,当耦合度降低的时候,也会导致系统的复杂度增加。在实际的项目中应当权衡利弊,既要保证项目结构清晰,也要做到高内聚、低耦合。
开闭原则
开闭原则的核心就是对拓展开放,对修改关闭。