设计模式—— 一:单一职责原则


@




什么是单一设计模式?Why单一设计模式?


单一职责原则的英文名称是Single Responsibility Principle,简称是SRP。

在项目中,会用到用户、机构、角色管理这些模块,基本上使用的都是 RBAC模型(Role-Based Access Control,基于角色的访问控制,通过分配和取消角色来完成 用户权限的授予和取消,使动作主体(用户)与资源的行为(权限)分离)。用户有用户管理、修改用户的信息、增加机构(一个人属于多个 机构)、增加角色等信息和行为要维护,假如把这些写到一个接口中, 看看它的类图,如图1-1所示:


1-1:用户信息维护类图

在这里插入图片描述

这个类真的是足够糟糕的!用户的属性和行为都耦合到一块了。为什么不可以呢?因为如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类其它职责的能力,这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。

所以应该把用户的信息抽取成一个BO(Business Object,业务对象),把行为抽取成一个 Biz(Business Logic,业务逻辑),按照这个思路对类图进行修正,如图1-2所示:


1-2:职责划分后的类图

在这里插入图片描述


重新拆封成两个接口,IUserBO负责用户的属性:职责是收集和反馈用户的属性信息;IUserBiz负责用户的行为:职责是完成用户信息的维护和变更。

看一看分拆成两个接口怎么使用呢?现在是面向接口编程,所以产生了这个UserInfo对象之后,当然既可以把它当IUserBO接口使用,也可以当IUserBiz接口使用。 要获得用户信息,就当是IUserBO的实现类;要是希望维护用户的信息,就把它当作IUserBiz 的实现类。比如:

IUserInfo userInfo = new UserInfo(); 
//要赋值了,把它作为一个纯粹的BO 
IUserBO userBO = (IUserBO)userInfo; 
userBO.setPassword("abc"); 
//要执行动作了,把它作为一个业务逻辑类 
IUserBiz userBiz = (IUserBiz)userInfo; 
userBiz.deleteUser();


为什么要把一个接口 拆分成两个呢?其实,在实际的使用中,更倾向于使用两个不同的类或接口:一个是 IUserBO,一个是IUserBiz,类图如图1-3所示:

1-3:项目中经常采用的SRP类图

在这里插入图片描述


以上把一个接口拆分成两个接口的动作,就是依赖了单一职责原则,那什么是单一 职责原则呢?

单一职责原则的定义是:应该有且仅有一个原因引起类的变更。


More Single! More Single!


单一职责原则看起来似乎比较简单,实际上软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。在实际的业务当中去把那些职责区分出来不是那么简单。

比如,生活中常用的电话,通话的时候有4个过程发生:拨号、通话、回 应、挂机,写一个接口,其类图如图1-4所示:


1-4:电话类图

在这里插入图片描述

接口的代码:

public interface IPhone { 
   //拨通电话 
   public void dial(String phoneNumber); 
   //通话 
   public void chat(Object o); 
   //通话完毕,挂电话
   public void hangup();

 }

这个类看起来是没什么问题的。单一职责原则 要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一 件事情,上面的接口只负责一件事情吗?是只有一个原因引起变化吗?并不是!

IPhone这个接口包含了两个职责:一个是协议管理,一个是数传送。

dial()和hangup()两个方法实现的是协议管理,分别负责拨号接通和挂机;chat()实现 的是数据的传送,把说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递 过来的信号还原成听得懂的语言。实际上,协议接通的变化会引起这个接口或实现类的变化。数据传送的变化同样也会引起这个接口或实现类的变化。那么现在这里就有两个原因都引 起了类的变化。这两个职责会相互影响吗?电话拨号,要能接通就行,不管是电信的还 是网通的协议;电话连接后还关心传递的是什么数据吗?通过这样的分析,发现类图上的IPhone接口包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆分成两个接口,其类图如图1-5所示:


1-5: 职责分明的电话类图

在这里插入图片描述

这个类图看上去有点复杂了,虽然完全满足了单一职责原则的要求,每个接口职责分明,但是一个手机类要把 ConnectionManager和DataTransfer组合在一块才能使用。组合是一种强耦合关系,你和我都有共同的生命期,这样的强耦合关系还不如使用接口实现的方式,而且还增加了类的复杂性,多了两个类。经过这样的思考后,再修改一下类图,如图1-6所示:


1-6:简洁清晰、职责分明的电话类图

在这里插入图片描述

这里一个类实现了两个接口,把两个职责融合在一个类中。似乎Phone有两个原因引起变化了,但是这是面向接口编程,对外公布的是接口而不是实现类。而且,如果真要实现类的单一职责,这个就必须使用上面的组合模式了,这会引起类间耦合过重、类的数量增加等问题,人为地增加了设计的复杂性。

单一职责原则的好处:

● 类的复杂性降低,实现什么职责都有清晰明确的定义;
● 可读性提高,复杂性降低,那当然可读性提高了;
● 可维护性提高,可读性提高,那当然更容易维护了;
● 变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。

当然了,追求完美也得从项目的实际功能需求出发。从功能上来说,定义一个IPhone接口也是可行的,实现了电话的功能,而且设计还很简单,仅仅一个接口一个实现类。




⇐⇐设计模式——零:设计模式概览      设计模式—— 二:里氏替换原则⇒⇒




参考:
【1】:《设计模式之禅》
【2】:《大话设计模式》
【3】:面向对象设计原则之单一职责原则

posted @ 2020-02-22 21:48  三分恶  阅读(513)  评论(1编辑  收藏  举报