OOD 四大原则
识别“臭”代码
好的软件设计应该是"强内聚,松耦合"的,尽量避免一些闻起来有“臭味”的代码,这些代码通常有以下的一些特点:
僵化性(regidity):很难改动系统现有代码,动一发而出全身,为了修改功能A做的改动会导致,B,C,D由于模块的依赖关系也发生连锁改动。
脆弱性(fragility):对系统的改动,导致跟修改点无关的地方也出现问题,但是这个地方跟修改的地方没有任何概念上的关联。
牢固性(immobility):很难将模块功能中有用的部分从系统中抽离出来,整个系统纠缠太深,无法抽取可重用部分做成组件。
粘滞性(viscosity):做正确的事比错误的事。因为系统的高粘滞性,新的设计比那些破坏设计的方法更难应用。
不必要的复杂性(needless complexity):设计包括了不具有任何好处的基础结构。开发人员预测需求变化,将目前无用的代码放置在处理中就会导致不必要的复杂性。
不必要的重复(needless repetion):重复代码,重复结构,没有进行合理的抽象统一。当copy代码时就会发生。
晦涩性(opacity):很难阅读
OOD原则就是为了避免以上这些臭味需要遵循的一些基本原则
开闭原则(OCP)
OCP-Open/Closed Principle
对扩展开发
对修改封闭
即:不修改软件实体(类,模块,函数等),就能扩展新功能。
传统的扩展模块方式就是修改模块源代码,现在可以依靠抽象实现不修改而扩展。
依赖倒置原则(DIP)
DIP-Dependency Inversion Principle
高层模块不依赖底层模块,大家都依赖抽象
抽象不依赖实现,实现依赖抽象
DIP倒置的既有代码结构:模块之间的依赖关系,也有开发的过程:开发顺序和职责,由高层来定义接口,由前端来定义后端
参看我的另外一篇总结 https://xie.infoq.cn/article/8005b3865675c0837516207a4
Liskov替换原则(LSP)
之前说实现OCP的关键在于抽象,而抽象的威力在于多态和继承
一个正确的继承需要符合什么需求?Liskov替换原则:子类型(Sub)必须能够替换掉他们的基类型(Base)
违反LSP的几种情况
不符合(Sub)IS-A(Base)的继承,一定不符合LSP
IS-A关系是关于行为的,设计和界定一个类,应该以其行为作为区分。
比如正方形 IS-NOT-A 长方形,比如计算面积CalArea()的行为,长方形是width*hight,而正方形是width*width
子类的契约比基类更严格
比如
基类是public的方法,子类覆盖为protected
正方形长宽相等,这个契约比长方形要严格
properties的契约比hashtable要严格
派生类中有退化函数
派生类抛出基类不会产生的异常
改善LSP问题的两种方法
继承:将共性提到基类
组合:将共性抽取到另外的类,原类调用这个类
继承的优点是比较容易,基类的大部分功能可以继承进入子类,但是缺点更多:
继承会破坏封装,将更多细节暴露给子类。继承因而被称为“白盒复用”
基类改变时会层层影响子类
继承是静态的,无法在运行是改变组合
类数量爆炸
因此优选组合。如果你只是为了让子类拥有某种方法,应该使用组合,如果是为了重写方法才考虑用继承。
如何检测LSP
孤立的去评估一个模型是否违反LSP是没有意义的,比如长方形和正方形并没有什么问题。只有通过它的客户程序才能体现
单一职责原则(SRP)
SRP- Single Responsibility Principle
内聚性原则,一个类,只能有一个引起它的变化的原因
指出如何设计一个类:只能有一种原因才能促使类发生改变
接口分离原则(ISP)
ISP-Interface Segregation Principle
指出如何设计一个接口:从客户的需要出发,强调不要让客户看到他们不需要的方法
比如:
cache 实现类中有四个方法,其中 put get delete 方法是需要暴露给应用程序的,rebuild 方法是需要暴露给系统进行远程调用的。如果将 rebuild 暴露给应用程序,应用程序可能会错误调用 rebuild 方法,导致 cache 服务失效。按照接口隔离原则:不应该强迫客户程序依赖它们不需要的方法。
优化后的类图如下:
推荐阅读
评论