GoF23 & OOP 设计原则

GoF23

设计模式(Design pattern)

对软件设计中普遍存在问题的优秀解决方案。

好处

  1. 提高思维、编程和设计能力。
  2. 使程序设计更标准化、代码编制更工程化,提高开发效率,缩短开发周期。
  3. 优化代码:重用性、可读性、可扩展性(可维护性)、可靠性、高内聚低耦合;

类型

  1. 创建型:工厂方法、抽象工厂、单例、建造者、原型。
  2. 结构型:装饰者、适配器、外观、组合、代理、桥接、享元。
  3. 行为型:观察者、命令、策略、模板方法、状态、迭代器、中介者、备忘录、访问者、职责链、解释器。

23 种设计模式

使用层次 变化 实现 OOP原则
Factory Method 代码级 对象实例化 对象的实例化推迟到子类 开闭、里氏替换、依赖倒置
Abstract Factory 应用级 产品家族的扩展 封装产品族的创建 开闭、依赖倒置
Singleton 代码级、应用级 唯一实例 封装对象产生的个数
Observer 应用级、构架级 通讯对象 封装对象通知 开闭
Decorator 代码级 对象的组合职责 在稳定接口上扩展 开闭
Command 应用级 请求的变化 封装命令对象 开闭
Adapter 代码级 对象接口的变化 转换接口
Facade 应用级、构架级 子系统的高层接口 简化子系统 迪米特、开闭
Strategy 应用级 算法的变化 封装算法 合成复用、里氏替换、开闭
Template Method 代码级 算法子步骤的变化 封装算法结构(子步骤) 依赖倒置
State 应用级 对象状态的变化 封装状态的相关行为 单一职责、开闭
Iterator 代码级、应用级 对象内部集合的变化 封装对象内部集合的使用 单一职责
Composite 代码级 复杂对象接口的统一 统一复杂对象的接口 里氏替换
Proxy 应用级、构架级 对象访问的变化 封装对象的访问 里氏替换
Builder 代码级 对象组建的变化 封装对象的组建 开闭
Bridge 代码级 对象的多维度变化 分离接口以及实现 开闭
Mediator 应用级、构架级 对象交互的变化 封装对象间的交互 开闭
Memento 代码级 状态的辅助保存 封装对象状态的变化 接口隔离
Visitor 应用级 对象操作变化 封装对象操作变化 开闭
Prototype 应用级 实例化的类 封装对原型的拷贝 依赖倒置
Flyweight 代码级、应用级 系统开销的优化 封装对象的获取
Chain of Resp. 应用级、构架级 对象的请求过程 封装对象的责任范围
Interpreter 应用级 领域问题的变化 封装特定领域的变化

OOP 设计原则

前置知识 👉 UML:类图、类的关系

OOP 设计原则

  1. 设计模式的基础,即设计的依据;
  2. 对封装、继承和多态,关联和组合关系的充分理解。

① 单一职责

Single Responsibility

尽量降低类的复杂度,一个类只负责一项职责

  1. 提高类的可读性、可维护性,降低变更引起的风险;
  2. 分解类的粒度,高内聚低耦合。

示例:有一个类 A,负责职责 x 和职责 y。

  1. 问题:当修改职责 1 的代码时,可能对职责 2 的代码造成影响。
  2. 解决:将类 A 的粒度分解为 A1,A2(即分为两个类)。

② 接口隔离

Interface Segregation

客户端不依赖不需要的接口

  1. 类对另一个类的依赖应建立在最小的接口上。
  2. 为各个类建立它们需要的专门接口。

示例:有一个接口 F,拥有实现类 F1 和 F2。

有一个类 A,需要使用 F1 的方法;有一个类 B,需要使用 F2 的方法。

(F1 实现了方法 m1, m2, m3,F2 实现了方法 m1, m4, m5。

  1. 问题:接口 F 对于类 A 和类 B 来说不是最小接口,因为其中包含了它们不需要的方法。

  2. 解决:将接口 F 拆分为独立的 3 个接口,类 A 和类 B 分别与需要的接口建立依赖关系。

    image-20211201015043745

③ 合成复用

Composite Reuse

优先使用聚合/组合关系,其次考虑继承关系。

④ 里式替换

Liskov Substitution

引用父类的地方,必须能透明地使用其子类的对象。

继承必须确保父类的性质在子类中成立)

继承存在的问题

  1. 破坏封装
  2. 耦合性
  3. 子类重写父类方法可能导致的影响:
    1. 降低继承体系的复用性。
    2. 影响多态的使用。

⑤ 依赖倒置

Dependence Inversion

尽量依赖抽象类,而不依赖具体类(面向抽象编程)。

  1. 变量的声明类型尽量是抽象类或接口。
  2. 注意
    1. 低层模块最好有抽象类或接口。
    2. 高层模块不依赖低层模块,二者都应依赖低层模块的抽象。
    3. 抽象不依赖于细节,细节应依赖于抽象。
    4. 继承需要遵循里式替换原则
  3. 常见注入方式:Spring IOC 使用构造器注入或 setter 注入。
    1. 构造器:定义成员变量,构造器初始化。
    2. setter:定义成员变量,调用 setter 初始化。
    3. 方法调用时:定义方法形参列表,调用方法时参数绑定。

示例

Person 类是高层模块,需要实现接收不同消息的功能。

Email 类是低层模块,需要实现接收电子邮件消息的功能。

  • 问题:若增加需求(接收微信、QQ消息),需要新增对应的类和 Person 中对应接收方法;

  • 解决

    • 声明接口 MessageReceiver,低层模块(Email、WeChat、QQ)实现该接口,则Person类只需与 MessageReceiver 建立依赖关系即可;
    • 此时高层模块(Person)和低层模块(Email、WeChat、QQ),都依赖低层模块的抽象(MessageReceiver)。

    image-20211201143741834

⑥ 迪米特

Demeter(aka 最少知道原则)

一个类对自己依赖的类知道的越少越好

  1. 被依赖的类:代码逻辑都封装在类内部,对外只提供 public 方法。
  2. 简单理解:只与直接朋友交流。
    • 类的依赖关系:成员变量(聚合 / 组合)、方法参数、方法返回值、局部变量
    • 直接朋友:成员变量(聚合 / 组合)、方法参数、方法返回值;
    • 陌生朋友局部变量

示例

已知 Student 和 Teacher 类,需要分别定义管理类。

StudentManage 能够获取所有学生信息,TeacherManage 能够获取所有老师和学生信息。

  • 问题:TeacherManage类中的 “输出所有学生”方法,使用到了成员变量Student(陌生朋友),违背迪米特法则;

  • 解决

    1. StudentManage 类中提供输出学生信息的方法。

    2. TeacherManage 类中通过方法参数调用 StudentManage 提供的方法。

      image-20211201213622912

⑦ 开闭(❗)

Open Close

对扩展开放,对修改关闭(扩展 - 增加新的类,修改 - 修改原有代码)

最重要的设计原则,是所有设计原则和设计模式的最终目标。

  1. 用抽象构建框架,用实现扩展实现。
  2. 当需要变化时,通过扩展软件实体的行为,而不是通过修改已有代码。

示例

已知 GraphEditor 类用于绘制图形,还有一些已知的图形类。

  • 问题:当新增图形需求时,需要增加 Shape子类、在GraphEditor 类中增加判断分支、增加绘图方法。

  • 解决

    1. 将 Shape类声明为抽象类,声明一个抽象方法用于绘图(原本在GraphEditor类中的 “绘图方法”)。

    2. 子类实现该方法。而 GraphEditor类只需通过多态调用方法即可,无需通过分支判断 id。

      image-20211201225444363

posted @ 2021-08-05 16:12  Jaywee  阅读(190)  评论(0编辑  收藏  举报

👇