※ 引述《hsnucsc (hsnugo)》之铭言:
: 我找了很多网站
: 都将解释SRP成: 每一个物件, 应该要只有单一的responsibility
: 而将responsibility解释成: 只有一个理由去改变物件
: 但是我还是觉得responsibility这个词很模糊
看了一下你的po的两篇文 我想你对于OO的design pattern应该是不熟悉
我会强烈建议你先去念Design Pattern
自然而然就会知道这些原则是怎么回事 以及知道什么才是真正的OO programming
一般来说 要写OO程式 初学者会直接硬干
也就是列出会用到的物件类别之后 就开始拼命对class塞attr和method
然后遇到分歧处就开始subclassing
但是这样的编写程式只会造成后续的麻烦 尤其是在维护上(无论是扩充或是修改)
当你开发到一定规模的程式时 修改程式码往往比开发新功能更花时间
而OO design pattern就是这个现象的救世主
在四人帮(GoF)的Design Pattern (1994)中
书皮就讲明了"Elements of Reusable Object-Oriented Software"
它阐述了一个OO的程式码 该如何撰写才能让程式具有弹性
在不同的情况下 大都有适合的pattern可以使用
而当你使用了这些pattern 将会对于你未来对于这些程式码的维护或再利用有很大帮助
你看的Head First OOA&D
事实上 它是再开始撰写程式码之前的动作 先明确的确定该如何撰写 再去实作
简单的说 就是“谋定而后动”
较好的写OO流程是
1. 先做 OO analysis & design
2. 再用 OO design pattern决定该如何实作
3. 最后 OO programming撰写程式码
如果你不懂为什么要先做OOA&D 是因为你不知道中间有design pattern的步骤
或是无法体会design pattern所能带来难以想像的帮助
正常情况下 OOA&D可以帮助你了解各个物件类别 该套用到哪些design pattern
以及整个程式完整的架构(如果你有好好做UML图的话)
如果要学完整的OO programming
则在学完基本OO特性、如何使用物件及如何撰写程式码之后
会先完整学完OO design pattern(OODP)
最后才去学OOA&D
关于Design Pattern的学习
我会建议你先去看“大话设计模式”(程杰 著) 它比较适合新手
同时搭配http://www.oodesign.com/ 这个网站
先确定把这些所有提到的DP都弄懂精髓所在 以及所有OO原则
之后再去看Head First Design Pattern才会更了解这本书在说什么
当你彻底的了解OO原则的精髓 就能自然而然的使用design pattern了
所以当你再去学OOA&D 就能够对于新的专案开发能快速切入要点
最后 关于你问的SRP原则 事实上是OO设计原则的一部分 它的前提顺序是这样的:
1. 当你程式需要改变时 你应该是增加新的程式码 而不是修改旧的 (OCP)
=>简单的说 已经写好的程式(或说是封装好的) 则不应该再去更动原有类别
更改已经写的程式码通常不会有什么好下场 往往只会弄得一团乱
当你要修改一个类别时 你就是在破坏它原有的封装 这是不好的现象
而这部份可以借由多型来完成
2. 情非得已下 必须修改现有的程式码时 则你应该只需修改一个类别(或是最少类别)
=>简单的说 如果你只需要修改一个类别 就比较能够追踪修改后的影响
这部份可以借由封装“因为一种理由而产生变化”的程式码到一个类别来完成
3. 程式码中绝对不能含有相同的算法 你必须把相同的部份抽取出来 才能达到2的原则
=>简单的说 就是因为你要改这个算法时 你不用再去一个一个类别去改
这样就不会遗漏哪些部分忘了修改 而造成debug的大灾难
这部份会因为不同的状况 有适合的design pattern可以使用
4. 当你要修改一个类别时 你应该只会因为一种“原因”而去改它 (SRP)
=>简单的说 当你要“拆开(或说是修改)”一个已经封装的类别时
你应该只会修改跟这个原因相关的程式码 其他的就不会动到
但是当你封装在一起时 你就有可能会不小心动到
(哪怕是你上个厕所时 被你家的猫踩键盘)
因为你不会去花时间检查你预期完全没有修改到的部份
所以一但同时被“开放”修改时 你是在增加风险 (早叫你鸡蛋不要放同一篮了)
而这部份 可借由封装不同原因才会修改的程式码来达成
PS 事实上 这是要减少耦合 增加reuse的机会
5. 这是OO programming中最恐怖的单字:耦合(coupling)
=>基本上 所有的design pattern和OO原则都是围绕在这个单字
其实coupling的意义就是“把两个东西绑在一起”(这年代讲耦合谁听得懂阿!)
耦合(或说“紧的耦合”)只发生在3个状况下:
1) inherit
2) reference
3) encapsulate
“紧的耦合”应该发生在两个程式码意义是99.99%以上的关联时
而任何间接方式就是“松的耦合”(例如多型)
我改用另一个例子来说明
如果你把“eat():”和“fly():”两个方法都“封装”在Bird的类别当中
你就是把这两个method耦合在一起(这个耦合是借由封装)
但是这两个method的意义是有99.99%以上的关联吗?
我想是没有 因为会吃饭不见得就会飞 (不然我就不会坐在这里长篇废话)
当你下次要让“狗”会吃饭时 或是让“飞机”会飞时 你之前实作的程式码就不能再利用
(当然例子不是举的很好 因为飞机的飞法和鸟的飞法是不一样的)
汽车也是
“引擎”的动力输出 和“方向盘”的使用接口 并不互相耦合
这两样只是跟“汽车”耦合
(直升机也有引擎但是没有方向盘 玩具车有方向盘但没有引擎)
换句话说 把“引擎”的能力 和“方向盘”的功能都封装在“汽车类别”里 有违原则
哪天你想改车 来替你的爱车升级引擎时 你就要改原本汽车的程式码啦!
但是借由设计模式 你只需要将“汽车类别”去跟“引擎接口”耦合(不是引擎实体类别)
这样的意思就是 汽车的确是有引擎 但是是哪颗引擎则可以有弹性的动态选择
简单的说 你就是借由多型 而可以弹性的让汽车拥有不同的引擎
但是如果你好不容易抽离出来成为多型的“引擎”程式码
却将“方向盘”的功能也封装在“引擎”的接口底下(封装就是耦合啦!)
那么当使用者选择某颗引擎的时候 他就被强制的安装一个耦合在一起的方向盘(怒!)
反过来讲 当使用者安装方向盘的时候 也被强制换一颗引擎
更扯的是 当你想改装你的方向盘时 你也可以看到引擎的设计图 甚至可以偷改引擎
(因为你打开了已经封装好的东西)
SRP就是告诉你:“别在把风马牛不相干的东西凑在一起坐撒尿牛丸啦!”
其他的原则我就不赘述了
基本上 用design pattern之后 你的程式码有可能会拆得到处都是
然后在检视或使用一个类别时 会搞不懂这个类别底层到底在干嘛、要怎么运行
但是事实上 你有做好documentation和UML图的话 就会很容易使用