• 六大设计原则——单一职责原则【Single Responsibility Principle】


    声明:本文内容是从网络书籍整理而来,并非原创。

    用户管理的例子

    1. 先看一张用户管理的类图: 
      这里写图片描述
    2. 再看一眼上面的图,思考:这样合理吗?

    3. 这个接口是一个很糟糕的设计! 用户的属性和行为竟然混合在一起!!!

    4. 正确的做法是把用户的信息抽取成一个业务对象(Bussiness Object,简称 BO),把行为抽取成另外一个接口中,我们把这个类图重新画一下: 
      这里写图片描述

    5. 这样划分成了两个接口,IUserBO 负责用户的属性,IUserBiz 负责用户的行为,因为是面向的接口编程,所有当产生了这个 UserInfo 对象之后,既可以把它当 IUserBO 接口使用,也可以当 IUserBiz 接口使用,类似下面代码:

      IUserBiz userInfo = new UserInfo(); 
      
      //我要赋值了,我就认为它是一个纯粹的BO 
      IUserBO userBO = (IUserBO)userInfo; 
      userBO.setPassword("abc"); 
      
      //我要执行动作了,我就认为是一个业务逻辑类 
      IUserBiz userBiz = (IUserBiz)userInfo; 
      userBiz.deleteUser();
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    6. 问题解决了,但实际中我们更倾向于使用两个不同的类或接口, 一个就是IUserBO,一个是IUserBiz,如下图: 
      这里写图片描述

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

      单一职责原则:应该有且仅有一个原因引起类的变更 
      SRP 的原话解释是:There should never be more than one reason for a class to change。

    打电话的例子

    1. 电话通话的时候有四过程发生:拨号、通话、回应、挂机,那么看下接口图: 
      这里写图片描述 
      接口代码:

      public interface IPhone { 
      
        //拨通电话 
        public void dial(String phoneNumber); 
      
        //通话 
        public void chat(Object o); 
      
        //回应,只有自己说话而没有回应,那算啥?! 
        public void answer(Object o); 
      
        //通话完毕,挂电话 
        public void huangup(); 
      } 
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
    2. 想想,符合单一职责原则吗?

    3. 其实它有两个职责:一个是协议管理,一个是数据传送,diag()和 huangup()两个方法实现的是协议管理,拨号接通和关闭;chat()和answer()是数据的传送,把我们说的话转换成模拟信号或者是数字信号传递到对方,然后再把对方传递过来的信号还原成我们听的懂人话。这两个职责互不影响,所以考虑拆开,如下图: 
      这里写图片描述

    4. 这种设计完全满足类和接口的单一职责要求,但是一个手机类要把 ConnectionManager 和DataTransfer 组合在一块才能使用,组合是一种强耦合关系,都是有共同的生命期,这样的强耦合关系还不如使用接口实现的方式呢,而且还增加了类的复杂性,多了两个类呀,好,我们修改一下类图: 
      这里写图片描述

    5. 这样设计才为完美,一个手机实现了两个接口,把两个职责融合一个类中,你会觉得这个 Phone有两个原因引起变化了呀,是的是的,但是别忘记了我们是面向接口编程,我们对外公布的是接口而不是实现类;而且如果真要实现类的单一职责的话,这个就必须使用了上面组合的方式了,那这个会引起类间耦合过重的问题。

      所以,对于接口,我们在设计的时候一定要做到单一,但是对于实现类就需要多方面考虑了,生搬硬套单一职责原则会引起类的剧增,给维护带来非常多的麻烦;而且过分的细分类的职责也会人为的制造系统的复杂性,本来一个类可以实现的行为非要拆成两个,然后使用聚合或组合的方式再耦合在一起,这个是人为制造了系统的复杂性,所以原则是死的,人是活的,这句话是非常好的。

    单一职责的好处:

    1. 类的复杂性降低,实现什么职责都有清晰明确的定义;
    2. 可读性提高,复杂性降低,那当然可读性提高了;
    3. 可维护性提高,那当然了,可读性提高,那当然更容易维护了;
    4. 变更引起的风险降低,变更是必不可少的,接口的单一职责做的好的话,一个接口修改只对相应的实现类有影响,与其他的接口无影响,这个是对项目有非常大的帮助。

      单一职责原则最难划分的就是职责,一个职责一个接口,但是问题是“职责”是一个没有量化的标准,一个类到底要负责那些职责?这些职责怎么细化?细化后是否都要有一个接口或类?这个都是需要从实际的项目区考虑的,从功能上来说,定义一个 IPhone 接口也没有错,实现了电话的功能呀,而且设计还很简单,就一个接口一个实现类,真正的项目我想大家一般都是会这么设计的,从设计原则上来看就有问题了,有两个可以变化的原因放到了一个接口中了,这就为以后的变化带来了风险,我从 2G 通讯协议修改到 3G 通讯,你看看你提供出的接口 IPhone 是不是要修改了?接口修改对其他的 Invoker 是不是有很大影响?!

    方法的单一职责原则

    单一职责使用于接口、类,同时也使用方法,什么意思呢?一个方法尽可能做一件事情,比如一个方法修改用户密码,别把这个方法放到“修改用户信息”方法中,这个方法的颗粒度很粗,比如这样一个方法: 
    这里写图片描述

    在 IUserManager 中定义了一个方法叫 changeUser,根据传递的 type 不同,把可变长度参数changeOptions 修改到 userBo 这个对象上,并调用持久层的方法保存到数据库中。在我的项目组中如果有人写了这样一个方法,我不管他写了多上程序化了多少工夫,一律重写!原因是:方法职责不清晰,不单一,一般方法设计成这样的: 
    这里写图片描述 
    你要修改用户名称,就调用 changeUserName 方法,你要修改家庭地址就调用 changeHomeAddress,你要修改单位单户就调用 changeOfficeTel 方法,每个方法的职责就非常清晰,这也是一个良好的设计习惯。 
    所以,不管是对接口、类、方法使用了单一规则原则,那么快乐的就不仅仅是你了,还有你项目的成员,你的板,减少了因为变更引起的工作量呀,加官进爵等着你幺!

    疑惑

    你看到这里,就会问我,你写是类的设计原则吗?你通篇都在说接口的单一职责,类的单一职责你都违背了呀,呵呵,这个还真是的,我的本意是想把这个原则讲清楚,类的单一职责嘛,这个很简单,但当我回头写的时候,发觉才不是这么回事,翻看了以前一些设计和代码,基本上拿的出手的类设计都是和单一职责向违背的,静下心来回忆,发觉每一个类这样设计都是有原因的。这几天我查阅了 wikipedia、oodesign 等几个网站,专家和我也有类似的经验,基本上类的单一职责都用了类似的一句话来说“This is sometimes hard to see” ,这句话翻译过来就是“这个有时候很难说” ,是的,类的单一职责确实受非常多的因素制约,纯理论的来讲,这个原则是非常优秀的,但是现实有现实难处,你必须去考虑项目工期、成本、人员技术水平、硬件情况、网络情况甚至有时候还要考虑政府政策、垄断协议等等原因。

    所以,对于单一职责原则,我的建议是接口一定要做到单一职责,类设计尽量只有一个原因引起变化。

    版权声明:本文为博主原创文章,未经博主允许不得转载。

     
    如果一件事情你觉得难的完不成,你可以把它分为若干步,并不断寻找合适的方法。最后你发现你会是个超人。不要给自己找麻烦,但遇到麻烦绝不怕,更不要退缩。 电工查找电路不通点的最快方法是:分段诊断排除,快速定位。你有什么启示吗? 求知若饥,虚心若愚。 当你对一个事情掌控不足的时候,你需要做的就是“梳理”,并制定相应的规章制度,并使资源各司其职。
  • 相关阅读:
    自定义TypeConverter把基础类型转换为复杂类型
    自学MVC看这里——全网最全ASP.NET MVC 教程汇总
    C#枚举器接口IEnumerator的实现
    nopCommerce架构分析系列(二)数据Cache
    NET下三种缓存机制(Winform里面的缓存使用 )
    【RequireJS--API学习笔记】
    Linux文件的所有权与权限
    ftp服务及其实现之vsftpd
    计算机传输层端口分类
    命令:tr
  • 原文地址:https://www.cnblogs.com/wvqusrtg/p/4812998.html
Copyright © 2020-2023  润新知