设计原则
面向对象设计(Object-Oriented Design,OOD)
将OOA所创建的分析模型转化为设计模型,其目标是定义系统构造蓝图
设计原则 SOLID
单一职责原则 SRP
(single responsibility principle)
就一个类而言,应该有且仅有一个引起它变化的原因。即,当需要修改某个类的时候原因有且只有一个,让一个类只做一种类型的责任
There should never be more than one reason for a class to change.
单一职责原则的好处:
- 降低类的复杂性,实现什么职责都有清晰明确的定义
- 提高可读性
- 提高可维护性
- 变更引起的风险降低
里氏替换原则 LSP
(Liskov substitution principle)
继承关系
优点
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性
- 提高代码的重用性
- 子类可以形似父类但又异于父类
- 提高代码的可扩展性
- 提高产品或者代码的开放性
缺点
- 继承是侵入性的
- 降低了代码的灵活性
- 增加了耦合度
定义
第一种定义
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T
如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型
第二种定义
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it
所有引用基类的地方必须能透明地使用其子类的对象,通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应
含义
- 子类必须完全实现父类的方法
注意
- 在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则
- 如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承
子类可以有自己的个性
覆盖或者实现父类的方法时输入参数可以被放大
子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松覆写或者实现父类的方法时输出结果可以被缩小
依赖倒置原则 DIP
(decency inversion principle)
High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions
- 高层模块不应该依赖于底层模块
- 抽象不应该依赖于细节
- 细节应该依赖于抽象
每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑的再组装就是高层模块
依赖倒置原则在Java语言中的体现:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系时通过接口或者抽象类产生的
- 接口或者抽象类不依赖于实现类
- 实现类依赖于接口或者抽象类
采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性
对象的依赖关系传递
构造函数传递依赖对象
在类中通过构造函数声明依赖对象,按照依赖注入的说法,这种方式叫做构造函数注入
Setter方法传递依赖对象
在抽象中设置Setter方法声明依赖关系,依照依赖注入的说法,这是Setter依赖注入
接口声明依赖对象
在接口的方法中声明依赖对象,该方法也叫做接口注入
实战规则
每个类尽量都有接口或者抽象类,或者接口和抽象类两者都具备
变量的表面类型尽量是接口或者抽象类
任何类都不应该从具体类派生
尽量不要覆写基类的方法
结合里氏替换原则使用
接口负责定义public属性和方法,并且声明与其他对象的依赖关系,抽象类负责公共构造部分的实现,实现类准确的实现业务逻辑,同时在适当的时候对父类进行细化**
依赖正置就是类间的依赖是实实在在的实现类间的依赖,也就是面向实现编程
接口隔离原则 ISP
(Interface Segregation Principle)
定义
- Clients should not be forced to depend upon interfaces that they don’t use.(客户端不应该依赖它不需要的接口)
- The dependency of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)
含义
- 接口要尽量小
- 接口要高内聚
- 定制服务
- 接口设计是有限度的
实战规则
- 一个接口只服务于一个子模块或业务逻辑
- 通过业务逻辑压缩接口中的public方法
- 已经被污染的接口尽量去修改,若变更的风险太大,可使用适配器模式进行转换处理
- 了解环境,拒绝盲从
迪米特法则 LoD
(Law of Demeter)
也称知识最少原则(Least Knowledge Principle),即一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少
含义
- 只和朋友交流 Only talk to your immediate friends(只与直接的朋友通信。)
- 朋友间也是有距离的
- 是自己的就是自己的
谨慎使用Serializable
迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也为维护带来了难度
开闭原则 OCP
(Open-closed Principle)
Software entities like classes,modules and functions should be open for extension but closed for
modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭)
变化
- 逻辑变化
- 子模块变化
- 可见视图变化
实战规则
抽象约束
通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放, 其包含三层含义:
第一,通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中不存在的public方法; 第二,参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;
第三,抽象层尽量保持稳定,一旦确定即不允许修改
元数据(metadata)控制模块行为
制定项目章程
封装变化
对变化的封装包含两层含义:
第一,将相同的变化封装到一个接口或抽象类中;
第二,将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一个接口或抽象类中。封装变化,也就是受保护的变化(protected
variations),找出预计有变化或不稳定的点,我们为这些变化点创建稳定的接口,准确地讲是封装可能发生的变化,一旦预测到或“第六感”发觉有变化,就可以进行封装,23个设计模式都是从各个不同的角度对变化进行封装的 SRP原则:用于类的设计
OCP原则:总的指导思想
LSP原则:用于指导类继承的设计
ISP原则:用于指导接口的设计
DIP原则:用于指导如何抽象
开闭原则 OCP
(Open-closed Principle)
软件实体应该是可扩展的,即开放的;但是不可修改的,即封闭的
重用发布等价原则 REP
Release Reuse Equivalency Principle
重用的粒度就是发布的粒度
共同封闭原则 CCP
Common Closure Principle
包中所有类对于同一性质的变化应该是共同封闭的。一个变化若是对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响
共同重用原则 CRP
Common Reuse Principle
一个包中的所有类应该是共同重用的,如果重用了包中的一个类,就要重用包中的所有类
无环依赖原则 ADP
Acyclic Dependencies Principle
在包的依赖关系图中不允许存在环,即包之间的结构必须是一个直接的无环图形
稳定依赖原则 SDP
Stable Dependencies Principle
朝着稳定的方向进行依赖
稳定抽象原则 SAP
Stable Abstractions Principle
包的抽象程度应该和其稳定程度一致
