转自:http://www.cnblogs.com/ldp615/archive/2009/08/28/1555952.html
“单一职责原则”是面向对象软件开发的基本原则之一,面向对象的思想又是从现实世界中总结出来。可最近发现面向对象的单一职责原则与现实好似有些冲突。
我们先看下引起事非争端的优雅的瑞士军刀吧:
瑞士军刀都是多功能工具刀,上面左图的至少有14项功能,右图的至少有34项功能!
这个是带MP3播放功能的!还有更加强大的:
这是知名瑞士刀制造商,威戈(Wenger)最新推出了一把长8.75英寸、重2磅(1公斤)内含87种工具的瑞士刀,编号16999。看来几乎曾经出现在瑞士刀里头的东西都有了,它包括七种不同的小刀、高尔夫球鞋清理钳、脚踏车链铆钉调整器,还有一只测距达300英尺(90公尺)的雷射笔。这把瑞士刀的作用,可能是让一个当打完高尔夫球的人,能够在清理完自己的球鞋以后,马上把脚踏车给调校好,接着骑到一个人烟罕至的树林里。
这个是相当“变态”的,我最近一直在写扩展方法的文章,所有文章中的扩展方法合在一起也和军刀有点类似了。
瑞士军刀和普通的刀(单一职责设计的刀)最大的不同就是方便(先不考虑最后一个,哈哈),它是多功能刀,而不是简简单单的刀。从功能上看,瑞士军刀“打破”了单一职责原则。也许产品的设计和软件系统的设计不能相提并论,毕竟归规模、复杂度相差太远。单一职责原则是为解决复杂问题而制定的。那我们就换个思路去考虑,我们构建一支军队,现在要给士兵配一些装备。军队、士兵、装备全是对象!有点类似了吧。我们是给士兵配一把多功能的瑞士军刀,还是配很多很多零散的小工具呢?
以上的对比可能不恰当,但从中可推出一个结论:有时使用单一职责,没看到好的效果,反而更麻烦。
先来看下单一职责原则(Single-Responsibility Principle)的定义,Robert C. Martin对它的解释是:Each class should have one and only one reason to change.单一职责并不是直观理解为类只能有单一的功能,单一职责是从变化的角度去考虑的。一个类应该(注意是should而不是must,是应该而不是必须)有且只有一个因起它变化的原因(直译)。
Martin的解释改变的单一职责原则的含义:
1.“单一职责”不是指具有单一功能(而是只因一个原因变化);
2.“原则”更像是一种比较强烈的建议。
基于这两点理解,引发出我的观点:
1.对象颗粒大小不同,对小颗粒对象应用单一职责是不合适的:小颗粒对象如小公司的员工,需要一人兼数职。如果应用单一职责,小颗粒对象又将分解成更多更小颗粒的对象,数量越来越多,给我们命名、使用、维护带来麻烦。
2.基础类库不适合:比如字符串常用处理的类,取子字符串、转换为其它类型、为空判断,放在一个类中反而使用更方便!
3.“单一职责原则”更适于中大型系统,系统越小使用该原则越不划算:一超个小系统就一个界面,面象对象都不必使用。
4.单一职责当然要用,但不是用到每个细节:一些局部不用对整体影响很小,反而更灵活、更省时间。
4.原则是前辈能多年的经验总结出来的,原则的名字可能不变,但原则的内含可能在悄悄变化。
5.“principle”、“原则”有可能是前人一种表达强烈建议的方式,而不是“must”、“必须”。
再回头看前面的军刀,它有多种功能,但能引起它变化的原因是什么呢?这里想到一个,客户应用。根据客户的具体应用,会有多种功能组合,不同组合又分成不同系列...客户有了新的需求,就会推出新产品...这个过程中可能包含了对象的继承、组合等等,应该很复杂。当然也遇到客户需求相当相当不一般,所以推出前面很变态的那款,应该算个特例吧!
再考虑:军刀上有一个开啤酒瓶用的启子,如果啤酒瓶的盖子大小变了呢?带MP3功能的军刀上有耳机插孔,耳机接口变了怎么办?仔细想想,能引起它变化的原因还真不少。但这些变化发生的可能性太小了,即便是真的发生了,军刀该退役了,新品就推出了。
所以,我认为瑞士军刀还是比较符合单一职责原则的!前面推出的结论“有时使用单一职责,没看到好的效果,反而更麻烦”,有两种解释:
1.将单一职责错误理解成了单一功能,又进一步去分解功能,所以麻烦;
2.不分对象大小,不看程序规模,统统使用:用的不恰当,所以麻烦。
最后结论,面向对象的思想与原则与现实世界是一致的,并且与时俱进。
本人没有受过正规的软件教育,对面向对象理解也是残缺不全,对技术也是东一块西一块,杂乱的想法也很多。说的不对请各位前辈指正!
-------------------
思想火花,照亮世界
#1楼 资深那一年[未注册用户]2009-08-28 19:25
先抢个沙发再评论#2楼 资深那一年[未注册用户]2009-08-28 19:39
先说一下瑞士军刀它到底是什么?是工具!所以如果要是一个工具类做成“瑞士军刀”是没问题的。因为引起他变化的原因少,相对稳定。(当然功能也要限量 向第三幅图的就不好了)OO 最根本的是要干什么?是要封装变化,哪里有可能出现变化就封装哪里。所以个人认为单一原则 和 军刀 基本是两回事。#3楼 丁丁2009-08-28 19:48
瑞刀三层的最实用,四层的已经太厚,对于特定用途,一层瑞刀的也没啥不好。所以,看方便啦,写代码也一样#4楼 Todd Wei2009-08-28 20:09
楼主选题很有启发性,能引发人的思考本身就很不错。#5楼 金色海洋(jyk)2009-08-28 21:02
我觉得用瑞士军刀举例子不大恰当。瑞士军刀只不过是若干“单一职责的刀”的组合,只不过这种组合比较紧密。
============
其实就是一句话:要分成小份,但是又不能太细。
#6楼 zwws[未注册用户]2009-08-28 21:05
引起瑞士军刀变化的原因只用一个,那就是功能的增加。军刀本身做为一个个体,和它实际应用的环境是非耦合的,所以我觉得不应该和单一职责放在一起谈,性质不同。比较赞同的说法是,单一职责的应该应用在粒度较大的点上,控制不好反而增加系统的复杂程度,得不偿失。
#7楼 Ivony...2009-08-28 21:46
其实怎么说呢,有句古话说的不错,尽信书不如无书,很多东西不能从字面上去理解,不过这倒不是什么大事,某些大牛也经常闹出这种事情。就像前阵子我特意撰文反驳的什么“类型要高内聚低耦合”。其实“单一职责原则”这个名字非常容易让人误解,而对它的解释“只应有一个引起变化的原因”又显得比较深奥,所以很多人是知其然不知其所以然。
事实上单一职责与粒度没有关系。简单的说,.NET Framework有一个类型,sqlConnection,这个类型是负责建立和维护数据库连接的,显然其设计是符合单一职责原则的。我们现在假设自己做了一个类型,这个类型不但负责打开和维护数据库连接,还要负责创建命令,执行查询,并包装输出结果等等一系列事情。但这个类并不是就违背了单一职责原则,它还是单一职责的,他负责与数据库的交互。如果我们要对数据库进行任何更改,需要修改这个类型的实现,但与数据库无关的更改,就不需要更改它了。在这样的例子里,显然后者的设计粒度比前者大得多。
那么什么是违背单一职责的呢,比如说一个类型除了与数据库交互,还要负责到前面去收集表单数据,这个时候我们就可以认为这里面有职责不明的问题,因为至少有两个引起它修改的原因,UI的变更或者数据库的变更。
#8楼 行万里路2009-08-29 00:40
如果从面向对象的角度去分析瑞士军刀, 其并不是见得是一个好的设计。导致它变化因素太多(可用功能、是否便于携带、是否可以随意拆卸等)。但是为什么它在市场仍如此受到青睐,这其中可能还有其他原因。如品牌、本身多功能就是它卖点。如果让我设计,我可能会把军刀的Body设计成一个接口容器(就像.NET Windows Form模型中的IServiceContainer),而军刀上那些功能我可能会将它们做容器中的服务提供者(ServiceProvider),他们实现了IServiceProvider这样一个接口。只有实现了这个接口的功能才可以装上军刀。大家可能知道了,他其实就是一个可拆卸、可插拔的多功能道具。
(愚见)
#9楼 飞林沙2009-08-29 02:45
其实我绝对单一职责究竟多单一,要看整个系统需求分析时所划分的对象粒度而定。还是那句话,满足需求的设计就是好设计。
#10楼 Nick Wang (懒人王)2009-08-29 04:52
“只因一个原因变化”这个确实很晦涩,可以从另一个角度衡量,如果在两个意图不同的功能中都是用了一个类,而这个类的某些方法只在其中一个功能中使用,那么可以说它不是职责单一的。但是意图是否相同,取决于你自己对它的划分,粒度可能很粗,也可能很细。1.对象颗粒大小不同,对小颗粒对象应用单一职责是不合适的:小颗粒对象如小公司的员工,需要一人兼数职。如果应用单一职责,小颗粒对象又将分解成更多更小颗粒的对象,数量越来越多,给我们命名、使用、维护带来麻烦。
--这个不认同,越小的对象越容易和应该职责单一,如果你所谓的小颗粒对象指的是功能(方法)较少或代码行数较少的类。
2.基础类库不适合:比如字符串常用处理的类,取子字符串、转换为其它类型、为空判断,放在一个类中反而使用更方便!
--字符串的例子正好是职责单一啊,因为它只管字符串,不管其它类型。
3.“单一职责原则”更适于中大型系统,系统越小使用该原则越不划算:一超个小系统就一个界面,面象对象都不必使用。
--跟系统规模大小无关,跟程序的寿命有关。一个程序写出来不管规模大小,如果要生存多年,维护就是主要工作,系统设计的好坏直接决定维护成本。
4.单一职责当然要用,但不是用到每个细节:一些局部不用对整体影响很小,反而更灵活、更省时间。
--一个系统的设计思路不统一,怎么让人理解和维护,今天的灵活和省时间,变成明天的难以维护和费时间。
#11楼[楼主] 鹤冲天2009-08-29 08:24
同意 资深那一年 的观点!
#12楼[楼主] 鹤冲天2009-08-29 08:26
同意,这是对“单一职责原则”最简单、最通俗的理解!#13楼[楼主] 鹤冲天2009-08-29 08:34
Ivony...的思想值得去好好思考!
#14楼[楼主] 鹤冲天2009-08-29 08:36
想法很独到!也期待有这种设计的军刀出现!
#15楼[楼主] 鹤冲天2009-08-29 08:51
功能(方法)少或代码行数少的类,如果功能相近,不如合成一个工具类,就像我们把很多小工具放入工具箱中一样。也和瑞士军刀差不多,它集合了多个单一职责的小工具。
我最近一直在写扩展方法的相关文章,通过扩展,我赋予string很多功能,如IsMatch(正则判断)、ToInt/ToDateTime(转换为其它类型)、ExecuteDOS/Executesql(执行DOS命令或sql语句)。扩展后的string职责多了很多,这些扩展是否也单一职责呢?
我感觉系统越小越稳定,系统越小引起它变化的原因也越少。超小系统维护不是问题,大不了重写一个。感觉超小系统中还要运行各种原则,是一种极端主义。
系统设计的思路应该是整体上的,一个类的设计应该侧重接口或公有成员部分,至于内部实现则可以相对灵活一点。
#16楼 heros2009-08-29 08:57
瑞士军刀方便吗,我并不觉得。也许我的系统中只需要一个指甲刀,但我不得不带上一堆我用不着的元素。如果这一堆元素的数量并不大时,也许我可以忍受,但数量庞大时…………,就像最后那一把军刀一样,要我说就是垃圾一推。功能设计应该尽量的细粒度,必要的时候可以组合。行万里路的想法很不错,对外提供稳定的接口,内部功能可以灵活变换。正如strategy、facade等的意图一样。#17楼[楼主] 鹤冲天2009-08-29 09:06
产品设计考虑特定的用户需求,你只需要一个指甲刀,去买指甲刀好了,还便宜,重点是适用!最后一把军刀我也很难接受,但存在就有道理,肯定有人喜欢这样,也许是为了收藏!
我也赞同这个!
#18楼 heros2009-08-29 09:14
产品可以包含n多的功能,但设计时要考虑细粒度。而不是在设计时就打造一个上帝。也许我的系统中只需要一个指甲刀,可能一会又需要一个小锉刀,需要指甲刀我给你一个指甲刀,需要小锉刀我给你一个小小锉刀,而不是一下扔一个瑞士军刀,“喂,都在这,自己挑吧。”存在就有道理,但存在不是就正确。
#19楼[楼主] 鹤冲天2009-08-29 09:31
@heros产品有n多功能,也会样n多种型号,也会有n多种样式,也会有n多公司生产...作为消费者我们选择的余地非常多的!
我现在有点感觉.Net Framwork就像是一个庞大的“军刀”,里面类异常多,类中的方法也很多,我们能用到的又能有多少,不还是从里面挑吗?
产品可多样化,但Framwork要考虑通用性,能满足绝大多数人的需要。
#20楼 heros2009-08-29 09:35
对不起,我们现在讨论的是Simple Response Principle。n多的类不在讨论范围。
#21楼 韦恩卑鄙2009-08-29 09:54
单一职责 在我来说 更像是对接口进行细分的标准比如
IPerson 吃饭说话听话微笑
{eat() say () listen () smile() }
IMale 站着尿尿 玩女人
{StandingPee(),FuxkChicken()}
而我实现了 Wayne :IPerson,IMale
在我想要 FuxkChicken的时候 我不要
Wayne.FuxkChicken()
因为方法太多了 我找不到
我宁可
((IMail)Wayne).FuxkChicken() 点起来比较好找(当然我一般参数类型就是 IMale 所以也不会拆箱)
这也是我建议你扩展方法分组的原因
换个角度说 你图中瑞士军刀变态 但是真的是无顺序存放的么?它仍然是多个功能分组,分别实现的,设计师那能那么不负责
所以我强调只要按照单一职责原则进行分组 那么大家也会欣然接受你的扩展实现
#22楼[楼主] 鹤冲天2009-08-29 10:12
@韦恩卑鄙韦恩兄言语不羁,很豪放!
IPerson、IMale让我有种恍然大悟的感觉!
对扩展进行分组的想法也在逐步形成中!
#23楼[楼主] 鹤冲天2009-08-29 10:16
Wayne.As<IMale>.StandingPee()
哈哈,我又多个一个思路:增加一个As<T>扩展,所有的扩展都扩展在T上,韦恩兄感觉如何?
T最好是接口,再者As以A打头,比较靠前,容易找也容易与其它的区分!
#24楼 乐蜂网[未注册用户]2009-08-29 10:38
写的很好!#25楼 Nick Wang (懒人王)2009-08-29 11:33
@鹤冲天1. 工具类本身的职责就是单一的啊,不过也可以细分成不同方面的工具,即使一个工具类有20多个方法,它也可能是SRP的。但如果其中包含了数据库、字符串、html等等的功能,也可以说不是SRP的。
2. 对字符串的扩展也还是字符串操作,当然也是SRP的。但是如果扩展中有针对数据库的、针对测试的等等,还是分成多个扩展类比较清晰。
--窃以为工具类并不是OO的一部分,SRP与否也并不是太大的问题,主要还是从清晰和可读性上考虑。
3.这要看你如何定义系统的大小了。如果以功能来看,twitter的功能也很单一啊,但是并不意味着就不需要维护,不会有很长的生命周期。
4.外部接口就像是穿的外衣,是给别人看的;内部实现就像内衣,舒服不舒服自己知道。而且外部接口一般是不能改变的或甚少改变的,内部实现则是会因为很多原因而改变,所以内部实现也是很重要的。
#26楼 陶发辉2009-08-29 11:50
瑞士军刀=管理(组合(单一职责刀*N))但是它的前题是:
1.每个刀都质量非常好,基本不会坏
2.有足够的空间
3.有足够的钱
程序同理。
#27楼 韦恩卑鄙2009-08-29 12:44
这个方法我很喜欢 搞得我也想试验下了
#28楼[楼主] 鹤冲天2009-08-29 13:38
这个观点(对我)很新颖!支持!
#29楼 Sojin2009-08-29 14:24
我觉得某个东西是否符合某个原则要看你是怎么去设计和理解这个东西。某种瑞士军刀如果不可灵活拆卸某个小工具,那他只能看成一个整体,当然就不符合,如果某种瑞士军刀是可以拆卸的,就可以将军刀看成容器,他的职责就是组合这些工具。这样军刀类并不需要知道具体的工具,添加和减少工具也不会引起他结构的变化。
在现实中可能虽然可拆卸的灵活,但成本更贵,用户就会按自己的需求去购买东西,都会有他的市场。
对应设计中有时候设计的成本高,又绝对没有这种变化的需要,不符合某个职责又有什么所谓呢。
#30楼 Todd Wei2009-08-29 15:22
SRP指的是组件职责单一,瑞士军刀不是组件,而是系统。没有谁说系统要职责单一的,比如一套ERP系统,又是生产,又是财务,又是客户,什么都有。放到瑞士军刀的例子里面,瑞士军刀系统是由多个xxx刀组件构成的,每个xxx刀的职责单一,这是SRP。系统功能多样化和组件职责单一化并不矛盾。其实,所谓职责或功能是有抽象层次的,高层次的功能包含了若干低层次的功能。系统对应高的抽象层次,也可以认为它是功能单一,比如:ERP系统,它唯一的功能就是企业资源计划,不是搜索引擎。谈论SRP应该放在对象所处的层次上。
#31楼 @L_638_301@2009-08-29 17:31
"谈论SRP应该放在对象所处的层次上"。上面我说的那么多,也没这句表达的清楚。SRP不能针对整个OA系统,它应该在设计员工类时被使用。
#32楼 麒麟.NET2009-08-29 22:42
瑞士军刀是一个产品,不是一个类,产品的功能自然会很多,而我们看到的是一个集合了多种功能的产品,功能多,职责复杂,也就不足为怪了#33楼 轩辕健2009-08-29 22:44
我觉得,瑞士军刀本身就已经是一个集成的系统,它并不是一个单一的整体对象。其组成就是由更多更小的功能实现体组合而成。#34楼 take it and go2011-04-29 13:25
瑞士军刀其实有些功能对我们多余了#35楼[楼主] 鹤冲天2011-05-22 17:28
@take it and go呵呵,是的