设计模式 学习笔记 之二
第2章 单一职责原则
单一职责原则(Single Responsibility Principle)可以如下表述:一个class只能有唯一一个引起它改变的原因。
换言之,如果你可以想到有超过一个动因来修改一个class,那么这个class就有不止一个职责,它就违反了单一职责原则。
单一职责原则说起来简单,但要真正贯彻却不容易,因为我们都习惯于把一套(而不是一个)职责指派给一个class。因此在实际中的情形是:在一开始设计class时,出于习惯或出于简单性的考虑,我们会给class指派不止一个职责,其中有一个是它的首要职责,其它的是一些次要职责。而在这之后,当需求真地发生变化时,我们抓住这个机会,把次要职责从class中剥离出来,并把这个需求变化隐藏到一个抽象接口中。这个设计重构的过程可以用下图来表示。
这个新设计满足了优良设计的几个要素:
l 降低了复杂性。原先的MyClass担负了较多的职责,而新的设计中,MyClass担负的次要职责变少了,MyClass的内聚性(cohesion)更强了,而与此同时又引入了一个具有单一职责的抽象接口。职责划分清晰的设计当然比原先职责耦合在一起的设计更简单和易于理解。
l 易于扩展。当同样的需求变化再次发生时,MyClass已无需再做变动,而只需将新的实现细节封装成抽象接口的另一个实现类里面。
l 是松散耦合的。原先的设计把首要职责和次要职责耦合到同一个class中,因此两者有可能相互牵制。新设计把次要职责拆离出来,让两者各司其职,从而实现松散耦合。
下面来看几个具体的例子,体会一下怎么应用这个原则。
例子1:Student类
假设我们在设计一个学生信息数据库系统。在这个系统的最初设计中,有一个Student类,它的目标职责是维护学生的相关信息,如姓名、性别、出生日期、入学时间、所获学分等。同时,它也负责产生一个纯文本格式的记录来描述学生的相关信息。
在这个系统部署之后一段时间,新的需求出现了:客户希望不仅能产生纯文本格式的Student记录,也能生成XML格式的记录,以便把学生信息保存到XML文件当中。
怎样应对这个需求变化?可能一个更恰当的问题是:怎样改进Student类,以便应对本次乃至今后的同种的需求变化?其实答案在前一章已经已经揭晓了:做“共性变性分析”(或称“抽象化”)。我们先来看看需求变化。
变化前:产生纯文本格式的Student记录。
变化后:产生XML格式的Student记录。
即使从字面上,我们也不难得出:
共性是什么?产生Student记录。
变性是什么?Student记录的格式。
找到了共性和变性,下面就是把共性用一个抽象接口来表示,把变性用具体实现类来封装。如下所示。
有了这个基础,新的设计就呼之欲出了,见下图。
最后,让我们把原有的设计与新的设计作一下比较,看看新的设计是不是真地具有更优良的品质。在老的设计中,Student类实际上把两个可以分享的职责耦合在一起了:一是记录学生的基本信息,二是产生文本式的学生信息报告。需求发生变化之前,这样做是可以接受的,因为我们没有必要去把系统设计得过于复杂。但是,当需求真地发生变化时,我们就要注意了,要抓住这个机会改进设计,把Student类的首要职责和它的次要职责解耦,而所采用的手段就是“共性变性分析”。这样得到的新设计,复杂性降低,扩展性增强,耦合度减轻,是一个更优良的设计。
例子2:Employee类
下面再看一个例子。假设我们在设计一个雇员信息系统。这个系统中,雇员的信息由Employee类来保存。同时,这个类还负责把雇员信息保存到MySQL DBMS中。
现在,请注意,新的需求来了:由于公司规模扩大,要把MySQL数据库更换成Oracle数据库。这样,Employee类也需要作相应的修改,以应对这个新的需求。
怎样应对这个变化?还是那个答案:使用共性变性分析。我们不难看出:
变化前:使用MySQL数据库保存Employee对象。
变化后:使用Oracle数据库保存Employee对象。
由此,共性是使用数据库保存Employee对象,而变性是使用哪种具体的数据库。找到了共性和变性,我们不难作出如下的设计重构。