目录
- 软件设计七宗罪
- 面向对象设计的原则
- SRP:Single Responsibility Principle 单一职责原则
- OCP:Open-Closed Principle 开放-封闭原则
- LSP:Liskov Substitution Principle 里氏替换原则
- ISP:Interface Sepregation Principle 接口分离原则
- DIP:Dependence Inversion Principle 依赖倒置原则
- CRP:Composite/Aggregate Reuse Principle 组合/聚合复用原则
- PLK:Principle of Least Knowledge(Law of Demeter) 最小知识原则
软件设计七宗罪
- Rigidity(僵化)–make it hard to change
- Fragility(脆弱)–make it easy to break
- Immobility(固化)–make it hard to reuse
- Viscosity(黏滞)–make it hard to do the right thing
- NeedlessComplexity(非必要复杂性)–over design
- NeedlessRepetition(非必要重复)–error prone
- Not doing any design
面向对象设计的原则
- SRP:Single Responsibility Principle 单一职责原则
- OCP:Open-Closed Principle 开放-封闭原则
- LSP:Liskov Substitution Principle 里氏替换原则
- ISP:Interface Segregation Principle 接口分离原则
- DIP:Dependence Inversion Principle 依赖倒置原则
- CRP:Composite/Aggregate Reuse Principle 组合/聚合复用原则
- PLK:Principleof Least Knowledge 最小知识原则
- SOLID让软件系统更易懂、灵活、易于维护,也可以形成迅捷软件开发的方法论
SRP:Single Responsibility Principle 单一职责原则
Definition
- 从软件变化的角度来看,就一个类而言,应该仅有一个让他发生变化的原因(即职责)
- 简单来说就是一个类只做一件事
Description
- 满足单一职责可以提高内聚性,降低耦合
- 若耦合过高:
- 难以复用
- 改变其中一个职责可能会影响其他,使其在改变时很脆弱
- 比如手机,有很多功能,就是一个多职责的系统,我们离了它就不行
Example
- 以一个modem的接口为例,这是一个多职责系统,有负责连接(拨号、挂断)、负责通信(收发信息)的功能
- 此时我们可以把两种功能分开
Kernel
- SRP原则的核心是正确的抽象,从实现方面来说,核心就是把代码(职责)移到另一个类中,通过调用来使用它们
- 在面向过程编程时,我们也有类似的思想,我们通常不会写一个很长的函数,而是写多个函数,再依次调用它们
OCP:Open-Closed Principle 开放-封闭原则
Definition
- Open For Extension
- Closed For Modification
- 软件实体(类、模块、函数等等)应该是可以扩展的,但是不可修改的
Description
- 实现OPC原则的优点
- 可以不修改现存代码以增加代码的方式实现现有需求,软件适应性和灵活性更好
- 已有的代码通常已经经过了测试,不适合改
Implementation
- 主要依赖抽象、多态、继承关系、接口来实现
- 接口与抽象类都是相对固定的,但它们的实现可以有很多种不同行为
- 接口适宜扩展,因为它可以有多种实现,但是不适合修改,因为修改后会影响到实现
Example
- 举个例子,我要在图形编辑器类中判断图形的形状来调用不同的绘制方法
- 如果我要加一个图形,就要修改GraphicEditor类
- 我们可以通过多态对其进行修改,对一个抽象方法有多种实现
Kernel
- OCP原则很难完全被满足,设计者需要预判软件系统可能的变化,将其实现到一个较为合适的程度即可
- OCP是面向对象设计的核心,抽象是OCP原则的核心
- 设计方式
- 传统方式是不要给系统添加新的需求,OCP的方式是在不重新设计的前提下系统支持什么样的变化
- 不必过度考虑,也不可能完全满足
LSP:Liskov Substitution Principle 里氏替换原则
Definition
- 任何基类可以出现的地方,子类一定可以出现
- 当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系
- 也就是说,只有子类的实例替换任何其他父类的实例时,程序运行结果没有改变,才说它们是is-A的关系
Example
- 这里如果说正方形是长方形,那么计算面积时就有冲突了,就不合理了
- 可以让正方形和长方形都实现一个四边形的接口
Solution
- 由以上内容我们知道了子类重写父类的方法其实违背了LSP原则
- 一种方式是把A、B代码的公共部分放进一个抽象类C,把它们要实现的方法放进一个接口IC,然后让C实现IC
- 另一种方法是不用继承而是聚合,即人要像车一样跑得快,不需要继承车变成汽车人,只需要拥有一辆车
Conclusion
- 在设计继承关系时我们一定要仔细考虑LSP原则
- 只有在有充足的理由时才可以打破这个规则,具体如何以后会说
ISP:Interface Sepregation Principle 接口分离原则
Definition
- 两个类之间的依赖程度取决于最小的接口
- 接口要内聚,尽量只把相关程度高的方法放进一个接口,而不是把所有放进一个接口
- 节省接口数量不能减少代码量,而是会污染接口
- 有时候接口里没有方法也是有其合理性的
- 用作指示,Cloneable,Serializable,Remote
Example
- 应该改成
Kernel
- ISP是SRP的接口版本
DIP:Dependence Inversion Principle 依赖倒置原则
- 也叫Hollywood Principle
Definition
- A类的某个方法中,使用了B类,那么就说A类依赖于B类
- 高层模块不应该依赖于低层模决,二者都应该依赖于抽象。进一步的,抽象不应该依赖于细节,细节应该依赖于抽象
- 如果B类出现漏洞,A也会受到影响
Description
- 增加灵活性,减少耦合
Implementation
- 高层模块依赖于接口,底层模块实现接口
- 人拥有一辆奔驰,如果奔驰出问题了,人就跑不快了,不如直接给人一辆车,具体是什么车人不必知道,如果奔驰坏了,可以换别的车
Example
- 实例一,一个模块的实现
- 实例二,开关与灯
Rules
- 任何变量存储的引用都不能指向实体类,只能指向接口或抽象类
- 任何类都不能从实体类继承
- 任何方法都不能重写其基类实现的方法
- DIP的规则有点过度严格了
Kernel
- DIP可以解耦,从而为实现OCP提供基础
- DIP总是针对接口编程,不针对实现编程
- 当类经常有可能变化时使用DIP,除非是一些工具类和静态类
- 共同的代码应该放在抽象层,私有的数据之类的要放在实现
关于耦合
- 三种耦合
- NilCoupling(零耦合:两个类丝毫不依赖于对方。但只使用零耦合却无法创建出一个有意义的OO系统,因为所有的类都是独立、不相关的,相互之间没有消息的传递,这样最多只能创建出一个类库。)
- Concrete Coupling
- Abstract Coupling
- Abstract Coupling更灵活,也更复杂
- 当类比较稳定时(即不怎么变化)Concrete Coupling更合适
Conclusion
- 接口不是万能的,不是银子弹,不稳定的接口可能打破抽象与实现间的隔离
- 接口应该由使用者的需求定义
- DIP是面向对象设计的基础与关键
CRP:Composite/Aggregate Reuse Principle 组合/聚合复用原则
Definition
- 多用组合,少用继承。
- 组合/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。
Description
- 聚合与组合
- 聚合是HAS-A的关系
- 组合是整体与部分的关系
- 面向对象初学者会过度使用继承,最后会产生巨大的僵化的继承关系,CRP原则是为了提醒我们,可以通过更灵活的聚合和组合来实现复用的目的
Example
- JDK容器类,栈没继承Vector,而是有一个Vector
- 老古董例子,如果Worker要升值,可能比较困难,需要先移植记忆到新的Manager躯体再人道处理(销毁)
- 这样就方便了,至少我还是我
Kernel
- 组合/聚合比继承要好
- 从复用来说
- 组合/聚合是黑盒复用
- 继承是白盒复用
- 从多态来说
- 组合/聚合是更灵活的多态
- 继承比较固定
- 组合/聚合在运行时可修改,继承不能修改
PLK:Principle of Least Knowledge(Law of Demeter) 最小知识原则
- 因为美国东北大学的德米特里项目出名的
Definition
- 每个模块只知道自己最近的模块的知识
- 就是不和陌生人说话
Description
- 不要让太多类耦合在一起,否则会脆弱且难懂
Advantages
- 易维护,适应性好
Disadvantage
- 需要增加许多包装类
- 某些情况下,会增加时间和空间开销