第2章 单一指责原则(SRP)

一、单一指责原则(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、建议接口一定要做到单一指责,类的设计尽量做到只有一个原因引起变化。

相关文章

什么是设计模式一套被反复使用、多数人知晓的、经过分类编目...
单一职责原则定义(Single Responsibility Principle,SRP)...
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强...
适配器模式将一个类的接口转换成客户期望的另一个接口,使得...
策略模式定义了一系列算法族,并封装在类中,它们之间可以互...
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,...