当前位置:   article > 正文

李建忠「设计模式」笔记_李建忠 设计模式

李建忠 设计模式

目录


设计模式目标 — 可复用

设计模式假设条件 — 存在稳定点

设计模式真谛 — 编译时复用,运行时变化。

思维方式 作用 内容
底层思维 把握机器底层微观构造 语言构造、编译转换、内存模型、运行机制
抽象思维 将现实世界抽象为程序代码 面向对象、组件封装、设计模式、架构模式

面向对象

  • 向下

    • 封装 — 隐藏内部代码

    • 继承 — 复用已有代码

    • 多态 — 改写对象行为

  • 向上

    • 深刻把握面向对象机制带来的抽象意义,理解如何利用这些机制来表达现实世界。
软件设计复杂原因

软件设计复杂性根本原因 — 变化(客户需求、技术平台、开发团队、市场)

解决复杂性
  • 分解 — 分而治之,大问题分解为多个小问题,复杂问题分解为多个简单问题(独立实现每个小类,分别实现相应的功能)

  • 抽象 — 由于不能掌握全部复杂对象,选择忽视一些非本质的细节。而去处理泛化和理想化的对象模型。(抽象一个虚基类,每个具体小类继承并重写)

C++对象模型

对象模型

几乎所有的设计模式都采用类内部组合一个对象指针的形式(指针指向多态对象以解耦合)

什么时候不用设计模式
  • 代码可读性差
  • 需求理解很浅
  • 变化尚未显现
  • 不是系统关键依赖点
  • 项目无复用价值
  • 项目将要发布
经验之谈
  • 不要为了模式而模式
  • 关注抽象类和接口
  • 理清变化点和稳定点
  • 审视依赖关系
  • 要有框架和应用的区隔思维
  • 良好的设计是演化的结果
设计模式成长之路
  1. 「手中无剑,心中无剑」 — 见模式而不知
  2. 「手中有剑,心中无剑」 — 可以识别模式,作为应用开发人员使用模式
  3. 「手中有剑,心中有剑」 — 作为框架开发人员为应用设计模式
  4. 「手中无剑,心中有剑」 — 忘掉模式,只有原则

面向对象设计原则

面向对象设计最大优势 — 抵御变化

面向对象
  • 隔离变化 — 面向对象构建方式更能适应软件变化,能将变化带来的影响降到最小(宏观)
  • 各司其职 — 需求变化导致的新增类型,不影响原来类型的实现(微观)
对象
  • 语言层面 — 对象封装了代码和数据
  • 规格层面 — 对象定义了一系列接口
  • 概念层面 — 对象是拥有某种责任的抽象
设计原则
  1. 依赖倒置原则(DIP)

    • 高层模块(稳定)不应该依赖于低层模块(变化),二者均依赖于抽象(稳定)。

    • 抽象(稳定)不应该依赖于实现细节(变化),实现细节应该依赖于抽象(稳定)。

  2. 开放封闭原则(OCP)

    • 对扩展开放,对更改封闭

    • 类模块应该是可扩展的,但不可修改。

  3. 单一职责原则(SRP)

    • 一个类应该仅有一个引起变化的原因
    • 变化的方向隐含类的责任
  4. Liskov替换原则(LSP)

    • 子类必须能够替换基类(子类能调用父类方法)
    • 继承表达类型抽象
  5. 接口隔离原则(ISP)

    • 不应该强迫客户程序(使用者)依赖不用的方法
    • 接口应该小而完备
  6. 优先使用对象组合,而不是类继承

    • 类继承通常为「白盒复用」,对象组合通常为「黑盒复用」
    • 继承在某种程度上破坏了封装性,耦合度高。而对象组合则要求被组合的对象具有良好定义的接口,耦合度低。
  7. 封装变化点

    • 使用封装创建对象之间的分界层,让设计者可以在其一侧修改,不会对另一侧产生不良影响。实现层次间的松耦合
  8. 针对接口编程,而非针对实现。

    • 不将变量类型声明为具体类,而是声明为接口。
    • 客户程序无需知晓对象的具体类型,只需要知道所具有的接口。

产业强盛标志 — 接口标准化

设计经验

由设计原则归纳、总结出的点

  1. 设计习语(Design Idioms) — 与特定编程语言相关的底层模式、技巧惯用法

  2. 设计模式(Design Patterns) — 类与对象之间的组织关系,包括角色、职责、协作方式

  3. 架构模式(Architectural Patterns) — 系统中与组织结构关系密切的高层模式,包括子系统划分、职责、组织关系。

设计模式分类

23个设计模式的分类原则

  • 目的

    • 创建型 — 对象创建

    • 结构型 — 对象需求变化对结构造成的冲击

    • 行为型 — 多个类交互

  • 范围

    • 类模式 — 处理类与子类的静态关系(继承)
    • 对象模式 — 对象间的动态关系(组合)
  • 封装变化

    • 组件协作 — Template Method、Strategy、Observer/Event
    • 单一职责 — Decorator、Bridge
    • 对象创建 — Factory Method、Abstract Factory、Prototype、Builder
    • 对象性能 — Singleton、Flyweight
    • 接口隔离 — Facade、Proxy、Mediator、Adapter
    • 状态变化 — Memento、State
    • 数据结构 — Composite、Iterator、Chain of Resposibility
    • 行为变化 — Command、Visitor
    • 领域问题 — Interpreter

由于时代的发展,一些设计模式已不常用:Builder、Mediator、Memento、Iterator、Chain of Resposibility、Command、Interpreter、Visitor

Refactoring to Patterns

重构获得模式是普遍认为最好的使用设计模式方法

  • 面向对象设计模式可以应对变化、提高复用。
  • 现代软件设计特征 — 需求频繁变化。
  • 设计模式的要点 — 寻找变化点(将稳定部分和变化不分分离开),变化点处使用设计模式来应对变化
  • 设计模式的应用不该先入为主(防止误用)。没有一步到位的设计模式。(故,要Refactoring to Patterns
步骤
  1. 自主增加相应的模块
  2. 思考违背哪些设计原则
  3. 重构代码
重构
  • 静态绑定 → \to 动态绑定
  • 早绑定 → \to 晚绑定
  • 继承 → \to 组合
  • 编译时依赖 → \to 运行时依赖
  • 紧耦合 → \to 松耦合

GoF23

组件协作

通过晚期绑定,实现框架与应用程序之间的松耦合。实现「框架与应用程序之间的划分」

Template Method

模板方法。定义一个操作中的算法的骨架(稳定),将一些步骤延迟(变化)到子类。使得子类可以不改变一个算法的结构(复用),同时重定义该算法的某些特定步骤。

动机

软件构造过程中,对于某项任务,有稳定的整体操作结构,但各个子步骤却有很多改变的需求;或者由于固有原因而无法和任务整体结构同时实现。

模板方法能够在稳定操作的前提下,灵活应对各个子步骤的变化及晚期实现需求。

在这里插入图片描述

  • AbstractClass — 稳定的流程
  • ConcreteClass — 实现时会变化的步骤
要点
  • Template Method是非常常用的基础设计模式,面向对象系统中大量使用。
  • Template Method机制简洁(虚函数的重载),为许多应用程序架构提供了灵活扩展点,是代码复用层面的基本实现结构。
  • Template Method内含反向控制结构(App调用Lib中的方法 → \to Lib调用App重写的方法)
  • Template Method调用的虚方法可以不做实现,但一般设计为protected方法。(流程中的一部分,不供外界调用)
样例

基类(Lib)实现执行流程,关于具体细节部分(步骤的详情),通过相应的派生类(App)去重写。

步骤的具体功能改变,不需要重写框架中的执行流程

//库
class Library{
   
public:
    //具体执行流程(定)
    void Run() {
   
        Step1();
        if(Step2()) {
   
            Step3();
        }
    }
    virtual ~Library() {
   ...}
protected:
    // 定
    void Step1() {
   ...}
    void Step3() {
   ...}
    // 变
    virtual bool Step2() = 0;
 };
//使用
class Application : public Library {
   
protected:
    virtual bool Step2() {
   ...}
};
int main() {
   
    Library* pLib = new Application();
    pLib->Run();
    delete pLib;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
Strategy

策略模式。定义一系列算法,分别封装,并且使他们可以相互替换(变化)。该模式使得算法「独立」于使用他的客户程序(稳定)而变化。

动机

软件构造过程中,某些对象使用的算法多种多样,经常改变。如果将算法都编码到对象中,会使得对象异常复杂且冗余。

策略模式能够在运行时根据需求透明的更改对象的算法,将算法与对象解耦合。

请添加图片描述

  • ContextStrategy — 稳定的共有算法(都需要计算某个项)
  • ConcreteStrategy — 具体的算法细节(计算的方式可能不同)
样例

银行支持多个国家税额的计算。具体的支持国家会根据银行的发展改变。

增加新的国家税额计算方式时(具体实现细节、客户程序),无需改变框架(算法)。

//计算策略 定
class TaxStrategy{
   
    TaxBase tax;
public:
    virtual double CalculateTax(...) = 0;
    virtual ~CalculateTax();
};
//具体国家的计算式 变
class CNTax : public TaxStrategy {
   
    virtual double CalculateTax(...) {
   
        ...
    }
};
//订单类 需要计算的值
class SalesOrder{
   
private:
    TaxStrategy* strategy;
public:
    //传入国家,新增
    SalesOrder(...) {
   
        this->strategy = new ...;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
要点
  • Strategy及其子类为组件提供一系列可重用算法,使得运行时方便切换。
  • Strategy提供了条件判断语句以外的选择,消除判断本身以解耦合。
  • 如果Strategy对象不实例化,那么 各个上下文共享一个Strategy对象以节省开销。
Observer/Event

观察者模式。定义一种对象间的一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖它的对象都得到通知并自动更新

动机

软件构造过程中,需要为某些对象创建一种“通知依赖关系” — 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)随之改变。若此依赖关系过于紧密,则软件不能很好的抵御变化。

观察者模式能够弱化这种依赖关系,形成稳定依赖关系、解耦合。

在这里插入图片描述

  • Subject Observer为稳定的框架部分
  • ConcreteObserver ConcreteSub
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/正经夜光杯/article/detail/897294
推荐阅读
相关标签
  

闽ICP备14008679号