贫瘠之地

华北无浪漫,死海扬起帆
多少个夜晚,独自望着天

0%

面向对象六大原则

六大原则

设计原则 概述 目的
开闭原则 对扩展开放,对修改关闭 易于维护
单一职责 一个类只干一件事,实现类要单一 提升可读性
里氏替换 不要重写父类的方法 健壮性、防止错误继承
迪米特法则 最少知道,对象之间少建立联系 低耦合
接口隔离 一个接口只干一件事,接口要精简单一 高内聚
依赖倒置 高层不应该依赖低层,要面向接口编程 利于结构升级

开闭原则

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" 的关系,来判断两个类能不能发生继承关系是不够的

应该遵守一个大原则:任何使用父类的地方,都能被透明的替换成子类,且替换成子类后,程序行为不会发生问题

不应该滥用继承关系,鸵鸟是否是鸟的子类(不会飞),鲸鱼是否是鱼的子类(没有腮),继承具有侵入性,当需要使用继承关系时需要辨明是否是真正的继承,父类的每个方法都必须适用于子类

  1. 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
  2. 子类可以增加自己特有的方法
  3. 子类重载父类方法时,方法的形参要比父类方法更为宽松
  4. 子类实现父类抽象方法时,方法的返回值要比父类方法更为严格

迪米特法则

talk only to your immediate friends. —— Ian Holland

只和直接的朋友交流

迪米特法则也称为最少知道原则,最少知道包含两个目的:

  • 只和直接的朋友交互
    • 例如 MVC 架构,视图层和业务层交互,业务层和持久层交互,视图层不应该和持久层产生联系
  • 较少对朋友的了解
    • 类门面模式(门面指暴露重要工作的简单入口,而迪米特法则在于只暴露该暴露的入口,还是有所区别)思想,对外只暴露能够满足外部需要的内容

目的在于降低类之间的耦合关系

接口隔离

Interface Segregation Principle, ISP

低耦合、高内聚中的高内聚

接口隔离原则中所说的接口并不是狭意的指 Java 中的 Interface,而是一切的提供方法定义的对象,例如 Java 中的接口、抽象类、实体类

接口隔离的原则:

  • 客户端不依赖不需要的接口
  • 类间依赖关系建立在最小的接口上
  • 接口应该细化,不应该具备臃肿的方法

例如发送方式的实现类 Send,此时具备两种发送方式:邮件和短信,如果将方法都放在 Send 类中,则应该分别定义 sendEmail()sendNotice() 两个方法,不利于后面的拓展;好的解决方法应该是定义接口,然后分别创建 EmailSendNoticeSend 两个实现类,这就是接口隔离的目的

接口隔离和单一原则看似冲突,目标是达到二者的平衡

避免接口污染

依赖倒置

Dependence Inversion Principle,DIP

不依赖于具体实现,而依赖于抽象

不正确的依赖关系是上层调用下层,上层依赖下层

面向对象其实就是依赖倒置的一种体现,依赖倒置让下层依赖上层,比如接口,接口方法的扩展会影响所有实现类,但抽象层的变动远远少于实现层,所以依赖倒置可以很好地避免频繁的修改

面向接口编程

补充

如何满足里氏替换原则

里氏替换的原则指:需要使用父类的地方可以替换为子类使用,因为父类的方法在子类上应该保持一致

在实际中遇到直接继承不能满足里氏替换的场景,就说明抽象不足,需要向上抽象

如果一个 Add 类的 compute(int n) 操作为加法,减法也想要进行实现,则继承 Add 类后重写了 compute(int n) 方法,此时则违反了里氏替换原则,整个方法的实现被重写了

向上抽象出 Compute 接口,即可以实现里氏替换原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Compute {
void compute(int n);
}

class Add implements Compute {
@Override
public void compute(final int n) {
System.out.println("加法");
}
}

class Subtract implements Compute {
@Override
public void compute(final int n) {
System.out.println("减法");
}
}

参考

常用设计模式有哪些? (refactoringguru.cn)