类设计原则
- 格式:docx
- 大小:167.33 KB
- 文档页数:27
1 / 27
第20章 类设计原则
20.1 SRP原则
类设计原则
职责集合:学生信息管理类单一职责原则SRP:Single Responsibility Principle一、使用范围:基础类单一管理职责:相关方法的集合1)添加学生信息方法2)修改学生信息方法3)删除学生信息方法4)查询学生信息方法打印机单一打印职责:相关方法的集合1)print()方法复印机单一复印职责:相关方法的集合1)()方法传真机单一传真职责:相关方法的集合1)fax()方法扫描仪单一扫描职责:相关方法的集合1)fax()方法二、不适用范围:基于基类构建的复杂的聚合类单一打印职责:相关方法的集合1)print()方法一体机单一复印职责:相关方法的集合1)()方法单一传真职责:相关方法的集合1)fax()方法单一扫描职责:相关方法的集合1)fax()方法
【SRP原则详解】
SRP,single responsibility principle,中文翻译为“单一职责原则”!
这是面向对象类设计的第一个原则,也是看起来最简单的一个原则,但这实际上远远没有那么简单,很多人都不一定真正理解了!
我们随便找几个网上的解释,看看各位大师或者经典网站是如何解释的:
百度百科(/view/1545205.htm):
一个类应该有且仅有一个职责。关于职责的含意,面向对象大师Robert.C.Martin有一个著名的定义:所谓一个类的职责是指引起该类变化的原因,如果一个类具有一个以上的职责,那么就会有多个不同的原因引起该类变化,其实就是耦合了多个互不相关的职责,就会降低这个类的内聚性。 2 / 27
说句实话,虽然是面向对象大师Martin的解释,我还是看得不甚明白:引起类变化的原因太多了,例如:
给类加一个方法是变化吧?
给类加一个属性是变化吧?
类的函数增加一个参数是变化吧?
。。。。。。
引起这些变化的原因太多了,如果每个原因都是一个职责,那SRP原则简直就没法判断了!
Wiki百科(/wiki/Single_responsibility_principle )内容和百度百科基本一致,看起来百度百科像wiki百科的翻译:)
Martin defines a responsibility as a reason to change, and concludes that a class or module should have one, and only one, reason to
change.
除了这些标准的解释外,还有一种说法:SRP就是指每个类只做一件事!
这个解释更通俗易懂,也更加适合中国人理解。虽然比Martin大师的解释更清楚一些,但仔细想想,还是有个地方比较难以理解:什么叫做“一件事”?
比如说一个学生信息管理类,这个类有“添加学生信息”、“查询学生信息”、“修改学生信息”、“删除学生信息”,那么这是4件事情,还是一件事情呢?
看起来好像是4个事情,但稍有经验的朋友应该都知道,这4个事情绝大部分情况下都是一个类来实现的,而不是分成4个类!
所以关键的问题在于:什么是“一件事”?是每个功能一件事情么?
其实答案就在我们自己身上,因为只要我们工作,无时不刻的承担着一定的“职责”!
现在让我们抛开面向对象,抛开软件,抛开计算机,来看看我们自己的“职责”。
比如说,我是一个程序猿,我的职责应该是“写程序”,但写程序有很多事情,例如:编码,单元测试、系统测试,bug修复,开会,写文档。。。。。。
再比如说,我的BOSS是一个管理者,他的职责是“管理程序猿”,他也有很多工作,例如:制定计划,团队建设、开会、协调资源、写文档。。。。。。
又比如说,我是一个快递员,也有很多工作:分包、快递、收款、开会。。。。。。
3 / 27
这些职责其实都不是我们自己定义的,而是公司或者部门或者组织给我们安排工作的时候定义的,也就是说:“职责”是站在他人的角度来定义的,而不是自己定义的,也许你想把自己定义成CEO,但你的老板会真的让你当CEO么?
经过对我们自己的职责的分析,我们可以得出两个关于职责的重要结论:
1) 职责是站在他人的角度来定义的
2) 职责不是一件事,而是很多事情,但这些事情都是和职责紧密相关的
对应到面向对象设计领域,我们可以说一个类的职责应该如下定义:
1) 类的职责是站在其它类的角度来定义的;
2) 类的职责包含多个相关功能;
因此,SRP可以翻译成“一个类只负责一组相关的事情”,对应到代码中就是:一个类有多个方法,这些方法是相关的。
当然,如果你再让我解释什么是“相关”,那我只能吐血了:)
有了这个定义,我们再来看“学生信息管理类”,很明显,学生管理类具有的4个功能都是和“管理”相关的,按照SRP原则,应该只设计一个“学生信息管理类”就可以了。
【SRP原则范围】
但现实世界往往比理想要复杂,一个最典型的例子就是“办公一体机”。
根据SRP原则,打印机可以设计成一个类,复印机也可以设计成一个类,扫描仪也可以设计成一个类,传真机还是可以设计成一个类,但偏偏就是出了个“办公一体机”,这个机器集成了“打印、复印、扫描、传真”4个职责!
如果我们要设计一个“办公一体机”的类,怎么也不可能设计出一个符合SRP原则的“办公一体机”的类来!
怎么办?是SRP不正确么,还是我们永远都不要设计“办公一体机”这样的类?
其实SRP也没有错,“办公一体机”也应该设计,但:不要用SRP来约束“办公一体机”这样的类!
也就是说,SRP其实是有适应范围的,SRP只适合那些基础类,而不适合基于基础类构建复杂的聚合类。
4 / 27
在“办公一体机“的样例中,“打印机”、“复印机”、“扫描仪”、“传真机”都是基础类,每个类都承担一个职责,而办公一体机是“聚合类”,同时集成了4种功能!
细心的朋友可能会继续问道:SRP不能应用于聚合类,那么如何保证聚合类的设计质量呢?
这个问题的答案在GoF的《设计模式》一书中有详细的答案,即:优先使用对象组合,而不是类继承。详细内容请参考后续“设计模式”部分的内容
20.2 OCP原则
上层、下层代码的实现原则
1) 上层的调用者只知道下层被调用方法的声明和功能,不知道下层方法功能的具体实现
a. 接口的API方法的声明和功能
类的API方法的声明和功能
API函数的声明和功能
b. 公共(耦合/通讯)的数据结构
2) 下层方法的实现不应该知道上层的实现.
3) 下层方法的功能重新实现,上层调用者代码不需要修改或少修改。
4) API声明的变化、数据结构的变更,上、下代码均需要重新实现。
5) 因此,设计时要求API的声明、公共的数据结构要稳定。
====================================================================
OCP,Open-Closed Principle,中文翻译为“开闭原则”。
当我第一次看到OCP原则时,我的感觉就是这原则也太抽象了吧,什么开,什么闭呢?
然后我去寻找更加详细的答案,最经典也是最常见的解释就是维基百科了:
/wiki/Open/closed_principle
"software entities (classes, modules, functions, etc.) should be open
for extension, but closed for modification";
翻译一下就是:对扩展开放,对修改封闭!
虽然这句解释更详细了,但其实还是很难理解,我因此去请教了一个前辈高人,他的回答更加惊世骇俗:不修改代码就可以增加新功能!!
5 / 27
当时我听到这句话就震惊了,这是多么神奇的事情啊,不修改代码就能够增加新功能!
但问题是:怎么做到的呢?难道这个原则是有关人工智能,又或者有什么高超的技巧,能够做到不修改代码增加新功能?
这么牛逼的原则当然要继续探索了,但怎么也没有找到“不修改代码就可以增加新功能”的独门秘籍!
于是对这个原则有了怀疑,经过继续的探索和查看各种资料,才发现原来是各位大师们在解释这个原则的时候隐藏了非常重要的“主语”,而这才是OCP原则的关键!
大师们省略的主语一个就是consumer(翻译成使用者、消费者),一个就是provider(翻译成生产者、提供着),例如A类调用了B类的方法,则A就是consumer,B就是provider。
完整的OCP原则实际上应该这样表述:open for provider extension,closed for consumer modification,翻译一下就是:对使用者修改关闭,对提供者扩展开放!
更通俗的意思就是:提供者增加新的功能,但使用者不需要修改代码!
虽然到这里我们已经基本上将OCP原则解释清楚了,但实际上细心的朋友还是会发现有问题的:提供者增加新的功能,使用者不修改代码就能用上么?
比如说:你设计一款有关车游戏,需要设计一个“car”的类,这个类原来有“加速”、“刹车”、“转向”三个功能,现在你要加一个新功能“改装”,游戏中其它类例如player,不修改代码就可以用上“改装”这个功能么?
很显然这是不可能的,我都新加了一个函数,你都不调用就能用新的功能,这也太邪乎了吧?
答案在于所谓的增加新功能,并不是增加一个全新的功能,而是原有的功能有了替代实现,这也是英文的“extension”所隐含的深意!
继续以赛车car作为例子,假设现在你设计了“卡车”、“跑车”、“家用车”三种车,现在要增加一种车“卡丁车”,只要“卡丁车”也实现了“加速”、“刹车”、“转向”,那么player不需要修改代码,就可以玩“卡丁车”了;但如果你增加了一种“改装”的功能,那么player必须修改才能使用“改装”功能。