这里介绍了5个面向对象设计与开发原则–SOLID原则,分别是:单一职责原则、开放封闭原则、里氏替换原则、接口隔离原则、依赖倒置原则。另外还介绍了其他3个原则:迪米特法则、"Tell, Don’t ask"原则、单一抽象层次原则。在面向对象的程序设计和开发过程中时,这些原则非常重要,我们应该尽量去遵守它。
SRP(The Single Responsibility Principle) 单一职责原则
OCP(The Open Closed Principle) 开放封闭原则
LSP(The Liskov Substitution Principle) 里氏替换原则
ISP(The Interface Segregation Principle) 接口隔离原则
DIP(The Dependency Inversion Principle) 依赖倒置原则
LoD(The Law of Demeter) 迪米特法则
“Tell, Don’t ask” "Tell, Don’t ask"原则
SLAP(Single Level of Abstrction Principle) 单一抽象层次原则
声明:
A class should have only one reason to change.
一个类应该仅有一个引起它变化的原因。
解释:
SRP是所有原则中最简单的一个,概念简洁易懂。但却也是最难正确运用,或者说是最容易过度使用的原则。
一个类或者一个方法只做一件事。如果一个类承担的职责过多,就等于把这些职责耦合在一起了,一个职责的变化就可能抑制或者削弱这个类完成其他职责的能力。所以正确的做法是发现职责并把这些职责相互分离。在实际的设计和开发过程中,一定要把握好分离“单个”职责的度,过度的分离只能适得其反,违背了面向对象的封装思想。
例如:餐厅服务员负责把订单交给厨师去做,而不是服务员又要处理订单又要炒菜。
声明:
Software entities(classes,modules,functions,etc.) should be open for extension, but closed for modification.
软件实体(如类、模块、函数等)应该对扩展开放,对修改关闭。
解释:
OCP是整个面向对象设计的核心。它代表了灵活性、可重用性和可维护性。
对扩展开放,对修改关闭。意为一个类实现后就不应该去修改它,而是以扩展的方式适应新需求。通过创建稳定的抽象基类或接口,并将具体的行为实现为派生类或实现类,这种做法能很好的解决开放扩展的问题。
例如:一开始做了普通计算器程序,突然添加新需求,要再做一个程序员计算器,这时不应该修改普通计算器内部,应该使用面向接口编程,组合实现扩展。
声明:
Derived types must be completely substitutable for their base types.
子类型必须能够完全的替换掉它们的基类型。
解释:
LSP是使OCP成为可能的主要原则之一,也是继承层次特征的主要设计原则。
所有基类出现的地方都可以用子类替换而不会程序产生错误。子类可以扩展基类的功能,但不能改变基类原有的功能。
例如:机动车必须有轮胎和发动机,子类宝马和奔驰不应该改写成没轮胎或者没发动机。
声明:
Clients should not be forced to depend upon interfaces that they don’t use.
不应该强迫客户程序依赖并未使用的接口。
解释:
ISP有效的降低了程序间的耦合性,提高了程序集的内聚性。避免了接口污染。
客户程序应该仅仅依赖于他们实际调用的方法。分离客户就是分离接口。
类不应该依赖不需要的接口,知道越少越好。通过把胖类的接口分解为多个特定于客户程序的接口,可以实现这个目标。有效的解除了客户程序和它们没有调用到的方法间的依赖关系。
例如:电话接口只约束接电话和挂电话,不需要让依赖者知道还有通讯录。
声明:
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
高层模块不应该依赖于低层模块,两者都应该依赖于抽象。
B. Abstractions should not depend on details. Details should depend on abstractions.
抽象不应该依赖于细节,细节应该依赖于抽象。
解释:
依赖倒置原则是实现许多面向对象技术所宣称的好处的基本低层机制,他的正确应用对于创建可重用的框架来说是必须的。同时它对于构建在变化面前富有弹性的代码也是非常重要的,由于抽象和细节彼此隔离,所以代码也非常容易维护。
针对接口编程,而不要对实现编程。倒置接口的所有权。高级模块不应该依赖低级模块,而是依赖抽象。抽象不能依赖细节,细节要依赖抽象。
例如:类A内有类B对象,称为类A依赖类B,但是不应该这样做,而是选择类A去依赖抽象。例如:垃圾收集器不管垃圾是什么类型,要是垃圾就行。
声明:
A. Each unit should have only limited knowledge about other units: only units “closely” related to the current unit.
每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
B. Each unit should only talk to its friends; don’t talk to strangers.
C. Only talk to your immediate friends.
解释:
迪米特法则又称为最少知识原则(Least Knowledge Principle 简称LKP)。主要有三层意思,就是让调用者对于目标对象的知识最少,而且只和你的直接朋友对话,千万不要和陌生人说话。其初衷在于降低类之间的耦合。
例如:
类C的方法f只应该调用以下对象的方法:
类C本身(this对象)的方法
类C成员对象的方法
在方法f中创建的对象的方法
作为参数传递给f的对象的方法
方法不应调用由任何函数返回的对象的方法。
“只跟朋友谈话,不与陌生人谈话”
声明:
Procedural code gets information then makes decisions. Object-oriented code tells objects to do things.
过程式程序获取信息然后决策;OO程序则告诉对象做某事情。
解释:
你应该尽量告诉对象你希望它们去做的事情;而不要询问它们的状态之后做出决定,最后才告诉它们做什么事情。
示例:
# 违反“Tell,Don’t ask”原则,询问对象状态再做判断
...
if (person.getAddress().getCountry().equals(”Australia”))
...
# 遵循“Tell,Don’t Ask”原则的代码,告诉对象去判断
...
if (person.livesIn(”Australia”))
...
声明:
让一个方法中所有的操作处于相同的抽象层。
解释:
我认为统一抽象层次原则是最重要的优秀编码原则,没有之一。需要熟练掌握各种设计模式、设计原则,具备优秀编码态度,长时间学习实践积累优秀编码技能,养成优秀编码习惯后才能做到这一点。
SLAP原则是指让一个方法中所有的操作处于相同的抽象层。否则跳跃的代码的抽象层次破坏了代码的流畅性。
如下所示:
示例1:方法的操作不在同一个抽象层次,前后是抽象,中间是细节。
void compute()
{
input();
flags = 0x0080;
output();
}
示例2:方法的操作在同一个抽象层次上
void compute()
{
input();
process();
output();
}
这些原则是数十年软件工程经验来之不易的结果。是众多开发人员和研究人员思想和著作的结晶。
我们应该牢记面向对象设计的原则,理解OO的设计思路。