一、单一指责原则(single responsibility principle,SRP)
什么是类的职责?以及怎么划分类的职责
1、单一指责的定义:应该有且仅有一个原因引起类的变更。 变化的原因就是所说的"职责"。
2、如果一个类有多个引起它变化的原因,也就意味着这个类有多个职责。即把多个职责耦合在一起了。
3、“职责”的粒度不好量化。实际开发中,这个原则最容易违反
二、单一指责的优点
1、类的复杂性降低,一个类负责一项职责,实现什么职责都有清晰明确的定义。
2、提高类的可读性和可维护性
3、变化引起的风险降低,变化是必然的,如果接口的单一指责做得好,一个接口的修改只会对相应的实现类有影响,对其他接口无影
响,对系统的扩展性、维护性有好处。
三、实例分析
1、用户管理类的实现
只要做过项目,肯定接触过用户、机构、角色管理这些模块,基本上使用RBAC模型(基于角色的访问控制,通过分配和取消角色来完
成用户权限的授予和取消,是动作主体 用户 与 资源的行为 权限 分离),确实是很好的解决方法。
(1)、臃肿的接口设计
用户管理类,通过一个接口实现。
接口设计的问题:用户的属性和用户的行为没有分开,严重的错误!我们应该把用户的信息抽取成一个BO(Business Object业务对
象),把行为抽取成一个Biz(Business Logic业务逻辑),按照这个思路进行接口设计:
(2)、职责分离
为什么要把一个接口拆分成两个呢?实际使用中,我们更倾向于使用两个类或接口,一个是IUserBO,一个是IUserBiz。就是依赖单
一指责原则。 java中实现可以是依赖的关系,但是C++中实现是关联 或者 聚合应该都可以。
(3)根据类图代码实现(如果能清晰的看懂类图,代码实现起来还是很简单的)
#include <iostream> #include <string> using namespace std; //业务对象抽象类 class IUserBO { public: virtual void setUserID(string userID) = 0; virtual string getUserID() = 0; virtual void setPassword(string password) = 0; virtual string getPassword() = 0; virtual void setUserName(string userName) = 0; virtual string getUsername() = 0; }; class UserBO : public IUserBO { private: string userID; string userName; string passWD; public: UserBO(string userid,string username,string passwd) { userID = userid; userName = username; passWD = passwd; } UserBO(const UserBO& object) { userID = object.userID; userName = object.userName; passWD = object.passWD; } UserBO& operator = (const UserBO& object) { if (this != &object) { userID = object.userID; userName = object.userName; passWD = object.passWD; } return *this; } void setUserID(string userID) { this->userID = userID; } string getUserID() { return userID; } void setPassword(string password) { this->passWD = password; } string getPassword() { return passWD; } void setUserName(string userName) { this->userName = userName; } string getUsername() { return userName; } }; //业务逻辑抽象类 class IUserBiz { public: virtual bool changePassword(string passwd) = 0; virtual bool deleteUser(string username) = 0; virtual bool addOrg(int orgID) = 0; virtual bool addRole(int roleID) = 0; }; class UserBiz : public IUserBiz { private: IUserBO* userBO; public: UserBiz(IUserBO* object) { userBO = object; } bool changePassword(string passwd) { bool rtn = true; if (userBO->getPassword() != passwd) { userBO->setPassword(passwd); cout << "修改密码成功!" << endl; } else { rtn = false; } return rtn; } bool deleteUser(string username) { bool rtn = true; if (userBO->getUsername() != username) { rtn = false; } else { cout << "删除用户成功!" << endl; } return rtn; } bool addOrg(int orgID) { cout << "自行实现!" << endl; return true; } bool addRole(int roleID) { cout << "自行实现!" << endl; return true; } }; int main(void) { UserBO* userBO = new UserBO("1","spach","lixin101357"); //用户逻辑类对象可以对这个进行处理 IUserBiz* userbiz = new UserBiz(userBO); if (userbiz->changePassword("helloworld")) { cout << userBO->getPassword() << endl; } cin.get(); return 0; }
2、电话类图
电话通话的时候有四个过程:拨号、通话、回应、挂机,我们实现一个接口:类图如下所示:请忽视图是截过来的,重点是分析
拨通电话、通过、通话完毕挂电话
单一职责要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,负责一件事情,貌似上面的类不是这样的。
Iphone接口两个职责:协议管理、数据传送。dial()和hangup()实现的事协议管理,分别负责拨号接通和挂机;chat()实现的是数据传送。
我们可以这样考虑问题:协议接通的变化会引起接口或类的变化,数据传送也会引起类或接口的变化。两个原因都引起了类的变化。但是两个职责会相
互影响吗?不会的。
类图上的IPhone接口包括两个职责,而且职责的变化不会相互影响,那就可以考虑两个接口,类图如下所示:
一个手机类要把ConnectionManager和DataTransfer组合在一块才能使用。组合是强耦合关系,共同的生命周期,这样的强耦合关系还不如使用接口实
一个类实现两个接口,把两个职责融合在一个类中。你会觉得这个Phone有两个原因引起变化,是的,但是别忘了我们是面向接口编程的,对外公布的
是接口而不是实现类。而且,如果真的要实现类的单一指责原则,必须使用上面的组合模式,会引起类间耦合过重、类数量增加等问题,人为增加设计
的复杂性。
四、总结
1、单一指责原则适用于类、接口,同时也适用于方法。一个方法尽可能做一件事情。
方法职责不清晰,不单一,不要让别人猜测这个方法是用来处理什么逻辑的。
2、建议接口一定要做到单一指责,类的设计尽量做到只有一个原因引起变化。