「OO」原则
1. 单一职责
一个类只负责一项职责。
这是很符合直觉的。但需要警惕职责扩散:即因为某种原因,职责P被分化为粒度更细的职责P1和P2,这时如果将类T也分解为两个类T1和T2,分别负责P1、P2两个职责,可能比较费时间。所以,简单的修改类T,用它来负责两个职责似乎是一个比较不错的选择(这样做的风险在于职责扩散的不确定性,因为职责P在未来可能会扩散为P1,P2,P3,P4……Pn)
2. 里氏替换原则
里氏代换原则(Liskov Substitution Principle
LSP
),实现抽象的规范,实现子父类互相替换。可以定义为:
- 如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
- 所有引用基类的地方必须能透明地使用其子类的对象。
通俗地讲:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下几点含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。
继承有利同时也有弊。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类。
3. 依赖倒置原则
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。面向接口编程,而非面向实现编程。
问题来源:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
举个栗子:
1 | class Mother{ |
这样依赖的是Book
这个具体的实现类,如果有一天故事的来源变成报纸,我们还要修改Mother
类,这样的设计显然不好,Mother
与Book
之间的耦合性太高,因此我们可以引入一个接口读物IReader
:
1 | interface IReader{ |
这样的传递依赖关系即为接口传递。另外还有两种传递方式:构造方法传递和setter方法传递。
在实际编程中,我们一般需要做到:
- 低层模块尽量都要有抽象类或接口,或者两者都有。
- 变量的声明类型尽量是抽象类或接口。
4. 接口隔离原则
使用多个隔离的接口,比使用单个接口要好。一个类对另一个类的依赖应该建立在最小的接口上。
也就是说,我们要为各个类建立专用的接口,尽量细化接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
5. 迪米特法则(最少知道原则)
一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。
通俗地讲,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。
6. 开闭原则
对扩展开放,对修改关闭
问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
当然,制定这六个原则的目的并不是要我们刻板的遵守他们,而需要根据实际情况灵活运用。对它们的遵守程度只要在一个合理的范围内,就算是良好的设计。
7. 合成复用原则
合成复用原则(Composite Reuse Principle
)是指:尽量使用合成/聚合的方式,而不是使用继承。
深拷贝 & 浅拷贝
拷贝方法的出现,方便了开发人员,在对象内部数据层次复杂的情况下,不通过 new
关键字和众多参数的传递,来完成拷贝对象的创建。
Object
类提供了一个受保护的 clone
方法,用以创建已有对象的一个浅拷贝。通过阅读 Object
类的源码,可以了解到的内容如下:
clone
方法的通用约定,在此不展开叙述。通过重写
clone
方法,可以创建已有对象的一个深拷贝。clone
方法的通用重写规则如下:- 首先,调用
super.clone()
。这会获得一个对象,此对象的字段与被克隆对象的字段对应相等。 - 而后,修改获得对象的某些字段。通常,这些字段引用的对象为可变对象,且构成了被克隆对象内部的深层结构,应当分别对其进行拷贝。如果一个类的字段仅包含基本类型和不可变对象类型,则通常没有字段需要被修改。
- 首先,调用
待重写
clone
方法的类,应当声明实现java.lang
包下的Cloneable
接口,否则,调用该类的clone
方法时,将抛出CloneNotSupportedException
。