六大原则
设计原则 | 概述 | 目的 |
---|---|---|
开闭原则 | 对扩展开放,对修改关闭 | 易于维护 |
单一职责 | 一个类只干一件事,实现类要单一 | 提升可读性 |
里氏替换 | 不要重写父类的方法 | 健壮性、防止错误继承 |
迪米特法则 | 最少知道,对象之间少建立联系 | 低耦合 |
接口隔离 | 一个接口只干一件事,接口要精简单一 | 高内聚 |
依赖倒置 | 高层不应该依赖低层,要面向接口编程 | 利于结构升级 |
开闭原则
Software entities like classes,modules and functions should be open for extension but closed for modifications.
一个软件实体如类,模块和函数应该对扩展开放,对修改关闭
随着业务发展需要增加新的方法,有几种方式:
- 在接口上添加新方法
- 导致每一个实现类都需要进行实现,改动量大
- 不一定所有的实现类都需要该方法
- 修改实现类旧方法
- 替代原有旧方法功能,如果同时需要使用旧方法则无法采用此方式
(
getPrice()
获取的是原价格还是打折后价格?如果需要同时获取原价和打折后价格如何处理)
- 替代原有旧方法功能,如果同时需要使用旧方法则无法采用此方式
(
- 面向扩展,使用新接口或者新类继承父类
- 新接口方法接口,需要的类进行实现
- 新类继承父类,在父类基础上增加方法
明显面向扩展更容易维护,这就是开闭原则的目的
单一职责
There should never be more than one reason for a class to change. —— Robert C. Martin
一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分
控制业务实现的粒度问题;一个类可以具备任意数量的方法,但都属于同一个功能簇中
严格控制类中方法的粒度,必要时进行分析与拆分
里氏替换
Inheritance should ensure that any property proved about supertype objects also holds for subtype objects. —— Liskov
继承必须确保父类所拥有的性质在子类中仍然成立
在里氏替换原则的指导方针下,可得出:仅仅依据两个类之间有没有 "is a" 的关系,来判断两个类能不能发生继承关系是不够的
应该遵守一个大原则:任何使用父类的地方,都能被透明的替换成子类,且替换成子类后,程序行为不会发生问题
不应该滥用继承关系,鸵鸟是否是鸟的子类(不会飞),鲸鱼是否是鱼的子类(没有腮),继承具有侵入性,当需要使用继承关系时需要辨明是否是真正的继承,父类的每个方法都必须适用于子类
- 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
- 子类可以增加自己特有的方法
- 子类重载父类方法时,方法的形参要比父类方法更为宽松
- 子类实现父类抽象方法时,方法的返回值要比父类方法更为严格
迪米特法则
talk only to your immediate friends. —— Ian Holland
只和直接的朋友交流
迪米特法则也称为最少知道原则,最少知道包含两个目的:
- 只和直接的朋友交互
- 例如 MVC 架构,视图层和业务层交互,业务层和持久层交互,视图层不应该和持久层产生联系
- 较少对朋友的了解
- 类门面模式(门面指暴露重要工作的简单入口,而迪米特法则在于只暴露该暴露的入口,还是有所区别)思想,对外只暴露能够满足外部需要的内容
目的在于降低类之间的耦合关系
接口隔离
Interface Segregation Principle, ISP
低耦合、高内聚中的高内聚
接口隔离原则中所说的接口并不是狭意的指 Java 中的 Interface,而是一切的提供方法定义的对象,例如 Java 中的接口、抽象类、实体类
接口隔离的原则:
- 客户端不依赖不需要的接口
- 类间依赖关系建立在最小的接口上
- 接口应该细化,不应该具备臃肿的方法
例如发送方式的实现类
Send
,此时具备两种发送方式:邮件和短信,如果将方法都放在
Send
类中,则应该分别定义 sendEmail()
和
sendNotice()
两个方法,不利于后面的拓展;好的解决方法应该是定义接口,然后分别创建
EmailSend
和 NoticeSend
两个实现类,这就是接口隔离的目的
接口隔离和单一原则看似冲突,目标是达到二者的平衡
避免接口污染
依赖倒置
Dependence Inversion Principle,DIP
不依赖于具体实现,而依赖于抽象
不正确的依赖关系是上层调用下层,上层依赖下层
面向对象其实就是依赖倒置的一种体现,依赖倒置让下层依赖上层,比如接口,接口方法的扩展会影响所有实现类,但抽象层的变动远远少于实现层,所以依赖倒置可以很好地避免频繁的修改
面向接口编程
补充
如何满足里氏替换原则
里氏替换的原则指:需要使用父类的地方可以替换为子类使用,因为父类的方法在子类上应该保持一致
在实际中遇到直接继承不能满足里氏替换的场景,就说明抽象不足,需要向上抽象
如果一个 Add
类的 compute(int n)
操作为加法,减法也想要进行实现,则继承 Add
类后重写了
compute(int n)
方法,此时则违反了里氏替换原则,整个方法的实现被重写了
向上抽象出 Compute
接口,即可以实现里氏替换原则
1 | public interface Compute { |