设计模式是软件设计中对常见问题的典型解决方案,每个模式就像一个蓝图,你可以通过它进行定制,来解决代码中特定的设计问题。设计模式不像算法那样提供详细的、明确的实现步骤,以下是深入设计模式的读书笔记。

概念

为什么要学习设计模式

或许你已从事程序开发工作多年, 却完全不知道单例模式是什么。 很多人都是这样。 即便如此, 你可能也在不自知的情况下已经使用过一些设计模式了。 所以为什么不花些时间来更进一步学习它们呢?

  • 设计模式是针对软件设计中常见问题的工具箱, 其中的工具就是各种经过实践验证的解决方案。 即使你从未遇到过这些问题, 了解模式仍然非常有用, 因为它能指导你如何使用面向对象的设计原则来解决各种问题。
  • 设计模式定义了一种让你和团队成员能够更高效沟通的通用语言。 你只需说 “哦, 这里用单例就可以了”, 所有人都会理解这条建议背后的想法。 只要知晓模式及其名称, 你就无需解释什么是单例。

设计模式的分类

常用的设计模式主要分为:

  • 创建型模式,提供创建对象的机制, 增加已有代码的灵活性和可复用性。
  • 结构型模式,介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。
  • 行为模式负责对象间的高效沟通和职责委派。

创建型模式

结构型模式

适配器模式

适配器模式是一种结构型模式,主要作用是使接口不兼容的对象能够相互合作。

主要的应用场景

  • 当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类,可以创建一个中间层类,其可以作为代码与遗留类、第三方类或者提供怪异接口的类之间的转换器。
  • 如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。

适配器模式的结构

对象适配器
实现时使用了构成原则:适配器实现了其中一个对象的接口, 并对另一个对象进行封装。 所有流行的编程语言都可以实现适配器。

适配器模式.png

  1. 客户端包含当前程序业务逻辑;
  2. 客户端接口描述了其他类和客户端代码合作时必须遵循的协议;
  3. 服务中有一些功能类,客户端和接口无法直接调用;
  4. 适配器是一个可以同时与客户端和服务交互的类,它在实现客户端接口的同时封装了服务对象,适配器接受客户端通过适配器接口发起的调用,并将其转换为适用于被封装服务对象的调用;
  5. 客户端只需通过接口与适配器的交互即可,无需与其具体的适配器类耦合,因此,你可以向程序中添加新类型的适配器而无需修改已有的代码,在服务类的接口被更改或替换时很有用,你无需修改客户端代码就可以创建新的适配器类。

类适配器
这一实现使用了继承机制: 适配器同时继承两个对象的接口。 请注意, 这种方式仅能在支持多重继承的编程语言中实现, 例如 C++。
类适配器

实现方式

  1. 确保至少有两个类的接口不兼容:

    • 一个无法修改 (通常是第三方、 遗留系统或者存在众多已有依赖的类) 的功能性服务类。
    • 一个或多个将受益于使用服务类的客户端类。
  2. 声明客户端接口, 描述客户端如何与服务交互。

  3. 创建遵循客户端接口的适配器类。 所有方法暂时都为空。

  4. 在适配器类中添加一个成员变量用于保存对于服务对象的引用。 通常情况下会通过构造函数对该成员变量进行初始化, 但有时在调用其方法时将该变量传递给适配器会更方便。

  5. 依次实现适配器类客户端接口的所有方法。 适配器会将实际工作委派给服务对象, 自身只负责接口或数据格式的转换。

  6. 客户端必须通过客户端接口使用适配器。 这样一来, 你就可以在不影响客户端代码的情况下修改或扩展适配器。

适配器模式优缺点

优点:

  • 单一职责原则你可以将接口或数据转换代码从程序主要业务逻辑中分离。
  • 开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。

缺点:

  • 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。

行为模式

策略模式

策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。

适合的应用场景

  • 想使用对象中各种不同的算法变体,并希望能在运行时切换算法时,可使用策略模式,策略模式能让你能够将对象关联至可以不同方式执行特定子任务的不同子对象,从而以间接的方式在运行时更改对象行为;
  • 有许多仅在执行行为时略有不同的相似类时,可使用策略模式,将不同行为抽取到一个独立类层次结构中,并将原始类组合成同一个,从而减少重复代码;
  • 如果算法在上下文的逻辑中不是特别重要,使用该模式能将类的业务逻辑与算法实现细节隔离开来,将各种不同算法代码,内部数据,依赖关系与其他代码隔离开来,不同客户端可通过一个简单接口执行算法,并在运行时切换;
  • 当类中使用复杂的条件运算符以在同一算法的不同变体中切换时,可使用该模式,将所有继承自同样接口的算法抽取到独立类中,因此不需要条件语句,原始对象并不实现所有算法变体,而是将执行工作委派给其中一个独立的算法对象。

策略模式结构

策略模式.png

实现方式

  1. 从上下文类中找出修改频率较高的算法 (也可能是用于在运行时选择某个算法变体的复杂条件运算符)。

  2. 声明该算法所有变体的通用策略接口。

  3. 将算法逐一抽取到各自的类中, 它们都必须实现策略接口。

  4. 在上下文类中添加一个成员变量用于保存对于策略对象的引用。 然后提供设置器以修改该成员变量。 上下文仅可通过策略接口同策略对象进行交互, 如有需要还可定义一个接口来让策略访问其数据。

  5. 客户端必须将上下文类与相应策略进行关联, 使上下文可以预期的方式完成其主要工作。

优缺点

优点:
对你可以在运行时切换对象内的算法。
你可以将算法的实现和使用算法的代码隔离开来。
你可以使用组合来代替继承。
开闭原则。 你无需对上下文进行修改就能够引入新的策略。

缺点:
如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。
客户端必须知晓策略间的不同——它需要选择合适的策略。
许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法。 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁。