六大设计原则——单一职责原则【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();
  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(); 
    }
  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” ,这句话翻译过来就是“这个有时候很难说” ,是的,类的单一职责确实受非常多的因素制约,纯理论的来讲,这个原则是非常优秀的,但是现实有现实难处,你必须去考虑项目工期、成本、人员技术水平、硬件情况、网络情况甚至有时候还要考虑政府政策、垄断协议等等原因。

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结