`

【编程思想】转载:单一职责原则

阅读更多

 

本文转载自:http://www.cnblogs.com/cbf4life/archive/2009/12/11/1622166.html

作者: cbf4life 


1.1 我是“牛”类,我可以担任多职吗

     单一职责原则的英文名称是Single Responsibility Principle,简称是SRP。这个设计原则备受争议,只要你想和别人争执、怄气或者是吵架,这个原则是屡试不爽的。如果你是老大,看到一个接口或类是这样或那样设计的,你就问一句:“你设计的类符合SRP原则吗?”,保准对方立马“萎缩”掉,而且还一脸崇拜地看着你,心想:“老大确实英明”。这个原则存在争议之处在哪里呢?就是对职责的定义,什么是类的职责,以及怎么划分类的职责。我们先举个例子来说明什么是单一职责原则。

     只要做过项目,肯定要接触到用户、机构、角色管理这些模块,基本上使用的都是RBAC模型,确实是很好的一个解决办法。我们今天要讲的是用户管理、修改用户的信息、增加机构(一个人属于多个机构)、增加角色等,用户有这么的信息和行为要维护,我们就把这些写到一个接口中,都是用户管理类嘛,我们先来看它的类图,如1-1所示。

clip_image002

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

     太Easy的类图了,我相信,即使是一个初级的程序员也可以看出这个接口设计得有问题,用户的属性(Property)和用户的行为(Behavior)没有分开,这是一个严重的错误!非常正确,这个接口确实设计得一团糟,应该把用户的信息抽取成一个BO(Bussiness Object,业务对象),把行为抽取成一个BIZ(Business Logic,业务逻辑),按照这个思路对类图进行修正,如图1-2所示。

clip_image004

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

     重新拆封成两个接口,IUserBO负责用户的属性,简单地说,IUserBO的职责就是收集和反馈用户的属性信息;IUserBiz负责用户的行为,完成用户信息的维护和变更。各位可能要说了,这个与我实际工作中用到的User类还是有差别的呀!别着急,我们先来看一看分拆成两个接口怎么使用。OK,我们现在是面向接口编程,所以产生了这个UserInfo对象之后,当然可以把它当IUserBO接口使用。当然,也可以当IUserBiz接口使用,这要看你在什么地方使用了。要获得用户信息,就当是IUserBO的实现类;要是希望维护用户的信息,就把它当作IUserBiz的实现类就成了,如代码清单1-1所示。

代码清单1-1 分清职责后的代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.......
 
IUserBiz userInfo = new UserInfo();
 
//我要赋值了,我就认为它是一个纯粹的BO
 
IUserBO userBO = (IUserBO)userInfo;
 
userBO.setPassword("abc");
 
//我要执行动作了,我就认为是一个业务逻辑类
 
IUserBiz userBiz = (IUserBiz)userInfo;
 
userBiz.deleteUser();
 
.......

     确实可以如此,问题也解决了,但是我们来回想一下我们刚才的动作,为什么要把一个接口拆分成两个呢?其实,在实际的使用中,我们更倾向于使用两个不同的类或接口:一个是IUserBO, 一个是IUserBiz,类图应该如图1-3所示。

clip_image006

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

     以上我们把一个接口拆分成两个接口的动作,就是依赖了单一职责原则,那什么是单一职责原则呢?单一职责原则的定义是:应该有且仅有一个原因引起类的变更。

1.2 绝杀技,打破你的传统思维

     解释到这里,估计你已经很不屑了,“切!这么简单的东西还要讲?!弱智!”好,我们来讲点复杂的。SRP的原话解释是:There should never be more than one reason for a class to change。这句话初中生都能看懂,不多说,但是看懂是一码事,实施就是另外一码事了。上面讲的例子很好理解,在实际项目中大家已经都是这么做了,那我们再来看下面这个例子是否好理解。电话这玩意,是现代人都离不了,电话通话的时候有四个过程发生:拨号、通话、回应、挂机,那我们写一个接口,其类图应该如图1-4所示。

clip_image008

图1-4 电话类图

     我不是有意要冒犯IPhone的,同名纯属巧合,我们来看一个这个过程的代码,如代码清单1-2所示。

代码清单1-2 电话过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface IPhone {
 
//拨通电话
 
public void dial(String phoneNumber);
 
//通话
 
public void chat(Object o);
 
//回应,只有自己说话而没有回应,那算啥?!
 
public void answer(Object o);
 
//通话完毕,挂电话
 
public void huangup();
 
}

     实现类也比较简单,我就不再写了,大家看看这个接口有没有问题?我相信大部分的读者都会说这个没有问题呀,以前我就是这么做的呀,某某书上也是这么写的呀,还有什么什么的源码也是这么写的!是的,这个接口接近于完美,看清楚了,是“接近”!单一职责原则要求一个接口或类只有一个原因引起变化,也就是一个接口或类只有一个职责,它就负责一件事情,看看上面的接口只负责一件事情吗?是只有一个原因引起变化吗?好像不是!

     IPhone这个接口可不是只有一个职责,它包含了两个职责:一个是协议管理,一个是数据传送。diag()和huangup()两个方法实现的是协议管理,分别负责拨号接通和挂机;chat()和answer()是数据的传送,把我们说的话转换成模拟信号或数字信号传递到对方,然后再把对方传递过来的信号还原成我们听得懂语言。我们可以这样考虑这个问题,协议接通的变化会引起这个接口或实现类的变化吗?会的!那数据传送(想想看,电话不仅仅可以通话,还可以上网)的变化会引起这个接口或实现类的变化吗?会的!那就很简单了,这里有两个原因都引起了类的变化,而且这两个职责会相互影响吗?电话拨号,我只要能接通就成,甭管是电信的还是网通的协议;电话连接后还关心传递的是什么数据吗?不关心,你要是乐意使用56K的小猫传递一个高清的片子,那也没有问题(顶多有人说你13了)。通过这样的分析,我们发现类图上的IPhone接口包含了两个职责,而且这两个职责的变化不相互影响,那就考虑拆开成两个接口,其类图如图1-5所示。

clip_image010

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

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

clip_image012

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

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

     通过上面的例子,我们来总结一下单一职责原则有什么好处:

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

     看过电话这个例子后,是不是有点反思了,我以前的设计是不是有点的问题了?不,不是的,不要怀疑自己的技术能力,单一职责原则最难划分的就是职责。一个职责一个接口,但问题是“职责”是一个没有量化的标准,一个类到底要负责那些职责?这些职责该怎么细化?细化后是否都要有一个接口或类?这些都需要从实际的项目去考虑,从功能上来说,定义一个IPhone接口也没有错,实现了电话的功能,而且设计还很简单,仅仅一个接口一个实现类,实际的项目我想大家都会这么设计。项目要考虑可变因素和不可变因素,以及相关的收益成本比率,因此设计一个IPhone接口也可能是没有错的。但是,如果纯从“学究”理论上分析就有问题了,有两个可以变化的原因放到了一个接口中,这就为以后的变化带来了风险。如果以后模拟电话升级到数字电话,我们提供的接口IPhone是不是要修改了?接口修改对其他的Invoker类是不是有很大影响?!

     注意 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否有优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。

1.3 我单纯,所以我快乐

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

     单一职责原则很难在项目中得到体现,非常难,为什么?在国内,技术人员的地位和话语权都比较低,因此在项目中需要考虑环境,考虑工作量,考虑人员的技术水平,考虑硬件的资源情况,等等,最终妥协的结果是经常违背单一原则。而且,我们中华文明就有很多属于混合型的产物,比如筷子,我们可以把筷子当做刀来使用,分割食物;还可以当叉使用,把食物从盘子中移动到口中。而在西方的文化中,刀就是刀,叉就是叉,你去吃西餐的时候这两样肯定都是有的,刀就是切割食物,叉就是固定食物或者移动食物,分工很明晰。这种文化的差异是很难一步改造过来,但是我相信随着技术的深入,单一职责原则必然会深入到项目的设计中去,而且这个原则是那么的简单,简单得不需要我们更加深入地思考,单从字面上大家都应该知道是什么意思,单一职责嘛!

     单一职责适用于接口、类,同时也适用于方法,什么意思呢?一个方法尽可能做一件事情,比如一个方法修改用户密码,不要把这个方法放到“修改用户信息”方法中,这个方法的颗粒度很粗,比如图1-7中所示的方法。

clip_image014

图1-7 一个方法承担多个职责

     在IUserManager中定义了一个方法changeUser,根据传递的类型不同,把可变长度参数changeOptions修改到userBo这个对象上,并调用持久层的方法保存到数据库中。在我的项目组中,如果有人写了这样一个方法,我不管他写了多少程序,花了多少工夫,一律重写!原因很简单:方法职责不清晰,不单一,不要让别人猜测这个方法可能是用来处理什么逻辑。比较好的设计如图1-8所示。

clip_image016

图1-8 一个方法承担一个职责

     通过上面的类图,如果要修改用户名称,就调用changeUserName方法;要修改家庭地址,就调用changeHomeAddress方法;要修改单位电话,就调用changeOfficeTel方法。每个方法的职责非常清晰明确,不仅开发简单,而且日后的维护也非常容易,大家可以逐渐养成这样的习惯。

     所以,如果对接口、类、方法使用了单一职责原则,那么快乐的就不仅仅是你了,还有你的项目组成员,大家可以轻松而又愉快地进行开发;还有你的老板,减少了因为变更引起的工作量,减少了无为人员和资金消耗。当然,最快乐的也许就是你了,因为加官进爵可能等着你哟!

1.4 最佳实践

     你阅读到这里,可能就会问我,你写的是类的设计原则吗?你通篇都在说接口的单一职责,类的单一职责你都违背了呀!呵呵,这个还真是的,我的本意是想把这个原则讲清楚,类的单一职责嘛,这个很简单,但当我回头写的时候,发觉并不是这么回事,翻看了以前的一些设计和代码,基本上拿得出手的类设计都是与单一职责相违背的。静下心来回忆,发觉每一个类这样设计都是有原因的。这几天我查阅了Wikipedia、OODesign等几个网站,专家和我也有类似的经验,基本上类的单一职责都用了类似的一句话来说“This is sometimes hard to see”,这句话翻译过来就是“这个有时候很难说”。是的,类的单一职责确实受非常多因素的制约,纯理论地来讲,这个原则是非常优秀的,但是现实有现实的难处,你必须去考虑项目工期、成本、人员技术水平、硬件情况、网络情况甚至有时候还要考虑政府政策、垄断协议等原因。比如,2004年我就做过一个项目,做加密处理的,甲方就甩过来一句话,你什么都不用管,调用这个API就可以了,不用考虑什么传输协议、异常处理、安全连接等。所以,我们就直接使用了JNI与加密厂商提供的API通信,什么单一职责原则,根本就不用考虑,因为对方不公布通信接口、异常判断。

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

分享到:
评论

相关推荐

    简单讲解Java设计模式编程中的单一职责原则

    Java设计模式中的单一职责原则(Single Responsibility Principle,SRP)是面向对象设计的基本原则之一,它的核心思想是:一个类或者一个模块应该只有一个引起它变化的原因。这意味着一个类应该只负责一项职责,使得...

    java并发编程:设计原则与模式.rar

    面向对象的设计原则,如单一职责原则(SRP)、开放封闭原则(OCP)、里氏替换原则(LSP)、依赖倒置原则(DIP)和接口隔离原则(ISP),同样适用于并发编程。遵循这些原则可以使代码更加模块化,更易于维护和扩展,...

    编程之魂:与27位编程语言创始人对话

    《编程之魂:与27位编程语言创始人对话》是27位杰出的设计师与你分享他们的智慧和经验。...因此,如果你想深入学习设计成功编程语言的思想,《编程之魂:与27位编程语言创始人对话》会对你大有帮助。

    c++编程思想 PDF

    总的来说,《C++编程思想》不仅涵盖了C++的基础知识,还深入到高级特性和设计原则,是学习和理解C++不可或缺的参考资料。通过阅读本书,读者可以建立起扎实的C++编程基础,进一步提升软件开发能力。

    java 企业编程思想

    企业级Java编程的基础是面向对象设计原则,如单一职责原则、开闭原则、里氏替换原则、依赖倒置原则和接口隔离原则。这些原则指导我们编写出可维护、可扩展的代码。同时,设计模式是OOP的精华,例如工厂模式、单例...

    C++编程思想

    《C++编程思想》是一本深受程序员喜爱的经典书籍,它深入浅出地介绍了C++这一强大的编程语言。这本书不仅涵盖了C++的基础语法,还详细探讨了面向对象编程、泛型编程以及设计模式等高级主题。以下是对这本书中关键...

    JAVA六大原则代码.zip

    单一职责原则(Single Responsibility Principle,SRP):一个类应该只有一个引起它变化的原因,即一个类应该只有一个职责。这个原则鼓励将不同的功能分离到不同的类中,以减少类的复杂性,提高代码的可维护性。 ...

    JAVA编程思想中文版.zip

    《JAVA编程思想》是 Bruce Eckel 的经典著作,中文版为国内Java开发者提供了深入理解Java语言的宝贵资源。这本书全面而深入地介绍了Java编程的核心概念和技术,是学习和提升Java编程技能的重要参考资料。 本书主要...

    《C++ 编程思想》

    《C++ 编程思想》是一本经典的C++教程,由世界知名计算机科学家Bjarne Stroustrup所著。这本书不仅深入浅出地讲解了C++语言的基础概念,还涵盖了面向对象编程、模板、STL(标准模板库)以及C++11/14/17等新特性。其...

    C++编程思想源代码.rar

    《C++编程思想》是一本深受程序员喜爱的经典之作,由Bruce Eckel撰写,全面而深入地探讨了C++编程语言的各个方面。这本书以其独特的视角和深入浅出的讲解方式,引领读者理解C++的设计理念和核心特性。源代码是学习...

    设计模式之六大原则

    单一职责原则不仅适用于面向对象编程,对于任何模块化的程序设计都是适用的。 #### 二、里氏替换原则 (LSP) **定义**: 里氏替换原则(LSP, Liskov Substitution Principle)最初由Barbara Liskov教授提出,其核心...

    设计模式详解

    - **职责细化**:单一职责原则并没有给出明确的指导如何划分职责,这需要根据实际情况和经验来判断。 - **平衡考量**:在实际应用中需要平衡职责分离与代码重用之间的关系,过度分割可能导致类的数量过多,反而增加...

    面向对象七大原则

    面向对象编程的七大原则是指在面向对象设计中所遵循的七个基本原则,它们是:开闭原则、依赖倒转原则、单一职责原则、接口隔离原则、迪米特法则、里氏替换原则和组合优于继承原则。 1. 开闭原则(Open-Closed ...

    《C++编程思想》书中代码

    1. 面向对象编程:C++是一种支持面向对象编程(OOP)的语言,书中的代码会展示类的设计、对象的创建与销毁、封装、继承和多态性等概念。通过这些例子,你可以了解到如何利用OOP来组织代码,提高代码的复用性和可维护...

    C++编程思想,强力二合一

    3. **封装与继承**:封装的基本原则,单一职责原则,以及类的继承和多态性。 4. **模板**:函数模板和类模板的使用,以及模板元编程的概念。 5. **STL**:容器(如vector、list、set)、迭代器、算法和函数对象的...

    java文档和编程思想

    2. **设计原则**:介绍SOLID原则,包括单一职责、开闭、里氏替换、接口隔离和依赖倒置等,这些都是构建可维护和可扩展软件的关键。 3. **模式与重构**:探讨设计模式如何解决常见的编程问题,以及重构如何改善代码...

    c++ 面向对象设计五大原则

    单一职责的核心思想:一个类只做好一件事情。 单一职责原则可以看作是高内聚、低耦合在面向对象原则上的引申。类的职责过多,容易导致类间职责依赖,提高耦合度,降低内聚性。通常意义下的单一职责,指的是类只有一

    [Bruce.Eckel编程思想系列丛书].part_03.rar

    《Bruce Eckel编程思想系列丛书》是一套备受推崇的经典计算机科学读物,由著名软件工程师Bruce Eckel撰写。这套丛书中涵盖了丰富的编程理念和实践,旨在帮助读者深入理解编程的本质和技巧,提升软件开发能力。由于...

Global site tag (gtag.js) - Google Analytics