面向对象设计五大原则

时间:2021-09-24 14:45:19

在写设计模式读书笔记之前,我想先总结一下自己对于面向对象设计五大原则的理解,他们分别是:SRP——单一职责原则;OCP——开放封闭原则;LSP——Liskov替换原则;DIP——依赖倒置原则;ISP——接口隔离原则。

 

1.  单一职责原则

 

在《敏捷软件开发》中,把“职责”定义为“变化的原因”,也就是说,就一个类而言,应该只有一个引起它变化的原因。

 

在《UML与模式应用》一书中又提到,“职责”可以定义为“一个类或者类型的契约或者义务”,并把职责分成“知道”型职责和“做”型职责。

 

其中“做”型职责指的是一个对象自己完成某种动作或者和其他对象协同完成某个动作;“知道”型职责指的是一个对象需要了解哪些信息。如果按照这种方式来定义“职责”的话,就与《敏》中对单一职责原则的定义不太相符了,所以还是理解为“变化的原因”比较恰当。

 

这个原则很好理解,但是既然谈到了职责,就不妨再来看看GRASP——通用职责分配软件模式(选自《UML与模式应用》)。按照我自己的看法来讲,在下面这些职责分配模式中所涉及到的设计问题,是建立在现实世界抽象层次上的设计,从这个层次上进一步细化,才到了设计模式所说的针对接口编程和优先使用组合的两大原则。

 

在这个层次上的抽象,一定要按照现实生活中的思维方法来进行,从我们人类考虑问题的角度出发,把解决现实问题的思维方式逐渐转化成程序能够理解的思维方式,绝不允许在这一步考虑程序代码如何实现,那样子的架构就是基于程序实现逻辑,而不是从解决问题的角度出发来实现业务逻辑(参考“面向对象的思维方法”)。

 

1)   专家模式。

 

在一个系统中可能存在成千上万个职责,在面向对象的设计中,定义对象的交互时,就要做出如何将职责分配给类的设计选择。

 

专家模式的解决方案就是:把一个职责分配给信息专家——掌握了为履行职责所必需的信息的类。

 

按照专家模式可以得到:一个对象所执行的操作通常是这个对象在现实世界中所代表的事物所执行的操作——这恰恰印证了我上面中的说法。

 

不过使用专家模式的时候,一定要仔细判断什么样的职责是应该只由一个类完成,什么样的职责应该由不同的类协作完成。举一个小小的反例吧,在“思维方法”一文中,提供了一个收发邮件的例子用以说明作者的观点,源码如下所示:

public class JunkMail {

private String head;

private String body;

private String address;

public JunkMain() { // 默认的类构造器

this.head=...;

this.body=...;

}

public static boolean sendMail(String address) {

// 调用qmail,发送email

}

public static Collection listAllMail() {

// 访问数据库,返回一个邮件地址集合

}

}

作者在这里就犯了一个职责分配的错误:上面的head、body和address都是属于邮件自身的属性,但是这个类却有一个叫做sendMail的方法,错误就在这个方法这里。在现实生活中,我们发送邮件的时候,是通过邮递员来进行的,绝对没有一封信会长上翅膀自己飞到收信人的手中,在程序中也是一样,一封邮件绝不可能自己把自己发送出去,应该通过某个MailController之类的类来完成这个功能(之所以不命名为MailSender,是因为后面可能还要添加receiveMail等功能)。

 

2)    创建者模式

 

   如果下列条件满足的话,就把创建类A的实例的职责分配给类B的实例:

a)        B聚集了A对象

b)        B包含了A对象

c)        B记录了A对象的实例

d)        B要经常使用A对象

e)        当A的实例被创建时,B具有要传递给A的初始化数据(也就是说B是创建A的信息专家)

如果以上条件中不止一条满足的话,那么最好让B聚集或者包含A

 

创建者模式用于指导对象实例创建任务的分配,基本目的就是找到一个与被创建对象有关联关系的创建者。

 

3)    低耦合度

4)    高聚合度

 

以上两个模式都是在设计过程中要记住的原则,是时时刻刻需要注意的隐含实现目标。

 

剩下的几种模式要么和以后的话题关系不大,要么在23种模式中有着体现,不再一一赘述。


 As an example, consider a module that compiles and prints a report. Such a module can be changed for two reasons. First, the content of the report can change. Second, the format of the report can change. These two things change for very different causes; one substantive, and one cosmetic. The single responsibility principle says that these two aspects of the problem are really two separate responsibilities, and should therefore be in separate classes or modules. It would be a bad design to couple two things that change for different reasons at different times.

The reason it is important to keep a class focused on a single concern is that it makes the class more robust. Continuing with the foregoing example, if there is a change to the report compilation process, there is greater danger that the printing code will break if it is part of the same class.