单一职责原则

2015/5/20 posted in  设计模式

写出符合规范的代码能够快速、直观的解决实际问题,但写出优美的代码,能够具有更好的可拓展性,让你当发生需求变化的时候不需要大范围的重构。 而所谓的优美,并不是说有一个漂亮的排版,而是从项目的设计层次上更加科学。符合科学的设计原则,能让代码更加健壮、可拓展。 单一设计原则(SRP),是一个本着单一原则,对接口和对象进行设计,以达到功能分离,从而达到当需求变更时能更少的重构的目的。 在做用户、机构、角色管理这些模块的时候,使用的基本都是RBAC(Role-Based Access Control,基于角色的访问控制,通过分配和取消角色来完成用户权限的授予和取消,使动作主体(用户)与资源行为(权限)分离)。说白了,就是让业务对象(被操作者)和业务逻辑(操作方法)分离。 如果有这么一个接口:

interface UserInfo{
    void setUserID(String ID);
    String getUserID();
    void setPassword(String password);
    String getPassword();
    void setUserName(String name);
    String getUserName();
    boolean changePassword(String oldPassword);
    boolean deleteUser();
    void mapUser();
    boolean addOrg(int orgID);
    boolean addRole(int roleID);
}

我们叫它牛类,自己独占实现了很多功能,的确能减少类和接口的数量,方便管理,作为单纯实现功能的话还是不错的。但这样科学吗?显然不可取。如果无论是用户模块需求发生变化,还是对管理的规则改变,都将导致对这个接口以及实现这个接口的类重写。 这显然没有符合单一设计原则,而应该做的就是把用户信息抽取成一个业务对象(Business Object,BO),而把行为抽取成为一个业务逻辑(Business Logic,Biz)。 那么就会把上述接口拆分成两个:UserBo和UserBiz,分别负责收集和反馈用户的属性信息和负责用户的行为,完成用户信息的维护和变更。

abstract class UserInfo implements UserBiz,UserBo{

}

interface UserBo{
    void setUserID(String ID);
    String getUserID();
    void setPassword(String password);
    String getPassword();
    void setUserName(String name);
    String getUserName();
}

interface UserBiz{
    boolean changePassword(String oldPassword);
    boolean deleteUser();
    void mapUser();
    boolean addOrg(int orgID);
    boolean addRole(int roleID);
}
class IUserInfo extends UserInfo{

    ....
}

因为是面向接口的编程,因此,产生IUserInfo对象之后,可以用UserBo接口使用,也可以用UserBiz接口使用。那么,获得用户就可以直接使用UserBo接口,而管理用户,维护用户信息则使用UserBiz接口:

UserInfo user = new IUserInfo();

UserBo userBo = (UserBo)user;
//就认为他是一个纯粹的BO
userBo.setPassword("admin");

UserBiz userBiz = (UserBiz)user;
//就认为他是一个纯粹的BIZ
userBiz.deleteUser();

这样,即使当用户或用户管理模块发生变动,则只需要修改变动的模块就可以,而没有必要将使用其模块的相关模块修改。那么总结一下,如果要依照单一职责原则,应该让类有且仅有一个原因使其变更。 也就是说,如果两个职责的变化不相互影响,就不应该放在同一个接口中实现,比如:

interface Phone{
    //打电话
    void dial(String phoneNumber);
    //通话
    void chat(Object o);
    //通话完毕,挂断电话
    void hangup();
}

这样的接口是否合理,是否符合单一原则? 那么做个假设,之前手机只能打电话,现在需求变了,要求手机还可以上网,那么会不会引起这个对象以及相关类的修改呢?必须会啊,那就说明这个接口还是存在问题的。 那么应该再把这个接口拆分,拆分为协议管理和数据传送,那么一个管理连接问题,一个管理数据传送问题各司其职。那么如果要添加上网功能的话,也不过就是新实现一个数据传输接口的类。 那么使用单一职责原则的好处也很明显了:

  • 降低类的复杂性,实现什么职责都有清晰明确的定义
  • 可读性提高
  • 可维护性提高
  • 变更引起的风险更低,一个接口只对相应的实现类有影响,对其他的接口无影响。

单一原则提出一个写程序的标准,用“职责“或变化原因来衡量接口或类设计的是否优良,但是”职责“和”变化原因“都是不可度量的,因项目而异,因环境而异。 对于接口,在设计的时候一定要做到单一,但对于实现类要考虑的方面将要更多,如果对单一原则生搬硬套,则实现类的数量将会剧增,反而人为的增加了系统的复杂性。规则是死的,人是活的,这些都可以灵活使用起来。 那么对于单一职责原则:接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化(不会有多个导致其重构的原因)