2.1 单一职责原则
单一职责原则强调一个类或模块应仅有一个引起其变化的因素(职责)。通俗地讲,一个类或模块应只承担一个(或一种类型的)业务职责,而不是将内聚度(指模块或类内部代码之间业务联系的紧密程度,是代码质量的重要特征;一般地,内聚度越高,代码质量越好)较弱的业务行为强行耦合(指模块或类与外部代码之间的关联方式,是代码质量的重要特征;一般地,耦合度越高,代码质量越差)在一起。因为,当类承担的业务职责较多时,该类中任何职责需求的变化都会引起静态实现的变化,导致其代码不稳定。无论是向目标类添加新的代码,还是修改已有的代码,都是对原有代码设计的破坏,导致程序开发人员花费巨大成本进行代码修复。
例如,COS系统中客户角色具有的业务行为有登录系统、支付订单、查看菜单等,如图2.1所示,如果将所有业务行为实现在客户类Patron中,当这些业务行为中的任何一个需求发生变化时,如支付或登录行为需求发生变化,都需要修改Patron类。可见,影响Patron类的变化因素多于1个时,会导致Patron类的代码不稳定性增加。
图2.1中Patron类的代码框架如下。
图2.1 多个业务行为的Patron类
public class Patron { public boolean login(){ // 登录逻辑 } public PayBill pay(){ //支付订单逻辑 } //public … (){} 其他业务行为 }
按照单一职责原则设计Patron类时,可以将登录系统、支付订单等业务职责分别单独地封装在其他类中,从而减少Patron类的业务职责。例如,可将登录行为封装在Employee类中,支付订单行为封装在PayOrder类中,如图2.2所示。
图2.2 遵守单一职责原则设计的Patron类
此时,Patron类继承Employee类,依赖PayOrder类,其代码框架如下。
public class Patron extends Employee { // 其他代码 public PayBill pay(){ PayOrder payOption;// 支付方式 //payOption.check(); 调用payOption 的check()行为完成订单支付 //return 语句 } // 其他业务行为 }
可以看到,将登录系统、支付订单等业务行为从Patron类中分离出去后,Patron类的代码就不再受到这些业务需求变化的影响了。由于登录行为封装在Employee类中,所以,登录业务需求变化时只需要修改Employee类的代码,而不需要修改Patron类的代码。而且,Employee类代码的变化只受到登录业务需求的影响;同样地,PayOrder类封装了支付订单业务行为,也只受到支付订单需求变化的影响。因此,符合“单一职责”设计原则的图2.2所示的设计方案减少了影响Patron类的变化因素,代码更加稳定。
但是,开发人员在使用“单一职责”原则设计类时,也会产生如下问题。
(1)设计类数量的增加。如果一个庞大的业务系统的所有类都按单一职责设计,则有可能导致设计类数量的“爆炸(Explosion)”。此外,设计类数量的增加也会使设计方案的复杂度增大。
(2)破坏类的封装特性。由于数据域封装在目标(实体)类中,如果从目标类中将含有数据域访问逻辑的业务行为分离出去,则势必造成外部代码访问目标类私有域的问题,而最终破坏目标类的封装特性。
(3)其他问题。完全教条式地运用单一职责原则设计类,也可能会降低代码的内聚或增加代码的耦合。同时,类职责没有明确的定义,可以是具体业务功能或行为,也可以是抽象逻辑,其边界是模糊的,难以清晰地划定单一职责。