为了能够更好地写出低耦合、高内聚、便于开发、测试、持续集成的成熟代码,程序员可能需要以某种框架来规范自己的代码逻辑。
设计模式可以被认为是一种在需求确认之后产生的蓝图,可以在最小的模块上应用,也可以从整体架构上实现。
设计模式有一些公认的原则,它们可能是经验性的,不一定完全严格遵循,但需要时刻注意:
- 单一职责原则
- 接口隔离原则
- 依赖倒转原则
- 里氏替换原则
- 开闭原则
- 迪米特法则
-
原则
单一职责原则
接口隔离原则
Interface Segregation,客户端不应该依赖它不需要的接口。
也就是说,接口的抽象性使得其方法可以被任意客户端实现,但如果负责不同逻辑的客户端(类)想要只依赖某一接口的部分方法实现,而这些方法都是该接口的真子集且彼此可能存在交集,这样的情况就是接口没有隔离。 非接口隔离情况
上图,假定X通过foo依赖B;Y通过foo依赖D,B、D分别实现了foo的方法,则类图中接口没有隔离。
问题在于,B、D其实只需要分别实现X、Y依赖的那部分方法即可,然而由于接口没有隔离,B、D不得不实现它们本不需要实现的2个额外方法。
按照接口隔离原则,重新设计类图如下: 接口隔离情况
虽然看起来类更加复杂了,总体的耦合度却减少了。依赖倒转原则
Dependency Inversion
高层模块不应该依赖底层模块,二者都应该依赖其抽象(接口或抽象类)
- 抽象不应该依赖细节,细节应该依赖抽象
- 中心思想:面向接口编程
面向接口编程可能不是最高效的,但往往是稳定的。
比较下面两种情况:
class A{
public int foo(){
return 1000;
}
}
class B{
// 显然,在B中显式地依赖了A,那么就会损失一部分对类C、类D等的扩展性
// 如果类C、类D也是细节的实现,那么应该使用依赖倒转原则建立通用的接口
public double bar(A a){
return a.foo() * 0.1;
}
}
public class Demo{
public static void main(String[] args){
B b = new b();
b.bar(new A());
}
}
(过度)依赖细节的情况
interface I{
public int foo();
}
class A implement I{
public int foo(){
return 1000;
}
}
class B{
// 细节依赖了抽象
public double bar(I i){
return i.foo() * 0.1;
}
}
class C implement I{
public int foo(){
return 1020;
}
}
class D implement I{
public int foo(){
return 1030;
}
}
public class Demo{
public static void main(String[] args){
B b = new b();
b.bar(new A());
b.bar(new C());
}
}
依赖关系传递的三种方式
以Java为例:
- 接口传递
- 构造方法传递
-
里氏替换原则
Liskov Suvstitution
面向对象编程中,核心在于继承,而在继承关系中,父子关系的绑定是约定式的,父子类之间的耦合使得程序的侵入性大大增强,而庞大的继承树则使得维护父类的任务变得过于复杂。里氏替换原则作为一种规范在OO编程中需要尽量满足。 子类能完全满足父类的任务,即引用父类的地方必须能透明地使用其子类对象(理想情况)
- 子类尽量不要重写父类方法
- 在适当的情况下,通过聚合、组合、依赖(继承转移到更基础的类)避免不必要的重写和继承。
开闭原则
Open & Close
编程中最基础、最重要的设计原则。
所谓“开放”指的是扩展开放,而对修改“关闭”。
而且开放的最终对象是类、方法;关闭的最终对象是客户端,调用等使用场景。
另外在需求有变时,尽量通过扩展而不是修改已有代码来实现。编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则。
迪米特法则
- 一个对象应该对其他对象保持最少的了解
- 类与类的关系越密切,耦合度越大
- Demeter Principle又称最少知道原则,尽量将逻辑封装在类的内部。
- 只和直接的朋友(耦合关系)通信
什么是直接的朋友?
:某类的成员变量、方法参数、方法返回值中的类是该类的直接朋友;
:局部变量中的类就不是直接朋友。
=>不要轻易在局部变量中实例化外部定义的类。
私以为链式调用很好地体现了Demter Principle
合成复用原则
Composite Reuse Principle
尽量使用合成/聚合的方式,而不是使用继承。
从另一方面讲,继承是要花费成本的,use it wisely。
尤其是在依赖的方式不仅限于继承的时候,使用继承在长远看来得不偿失。
设计模式的分类
这里就把它当作目录了,中文对应的链接是理论部分的文章链接,学习记录自韩顺平图解设计模式;英文对应的链接是我对 Game Programming Patterns 一书的译文。