设计模式学习整理
目录
- 学习参考:重构大师
- 设计模式:
1、创建型模式
- 创建型模式提供了创建对象的机制, 能够提升已有代码的灵活性和可复用性。
工厂方法模式
- 在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型;
- 实现方式:使用特殊的工厂方法代替对于对象构造函数的直接调用 (即使用 new运算符), 对象仍将通过 new运算符创建, 只是该运算符改在工厂方法中调用罢了。 工厂方法返回的对象通常被称作 “产品”。
抽象工厂模式
- 创建一系列相关的对象, 而无需指定其具体类;
- 实现方式:
- 对于系列产品的每个变体, 都将基于抽象工厂接口创建不同的工厂类,每个工厂类都只能返回特定类别的产品;
- 客户端代码可以通过相应的抽象接口调用工厂和产品类。 你无需修改实际客户端代码, 就能更改传递给客户端的工厂类, 也能更改客户端代码接收的产品变体。
生成器模式
- 能够分步骤创建复杂对象, 该模式允许你使用相同的创建代码生成不同类型和形式的对象;
- 应用场景:对于一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作,通常情况下, 绝大部分的参数都没有使用, 这使得对于构造函数的调用十分不简洁(如大部分参数不适用传入NULL等);
- 实现方式
- 将对象构造代码从产品类中抽取出来, 并将其放在一个名为生成器的独立对象中;
- 每次创建对象时, 你都需要通过生成器对象执行一系列步骤。 重点在于你无需调用所有步骤, 而只需调用创建特定对象配置所需的那些步骤即可;
- 可以进一步将用于创建产品的一系列生成器步骤调用抽取成为单独的主管类。 主管类可定义创建步骤的执行顺序, 而生成器则提供这些步骤的实现。
原型模式
- 使使用者能够复制已有对象, 而又无需使代码依赖它们所属的类;
- 实现方法:将克隆过程委派给被克隆的实际对象;即所有支持克隆的对象声明了一个通用接口, 该接口让你能够克隆对象, 同时又无需将代码和对象所属类耦合。 通常情况下, 这样的接口中仅包含一个克隆方法。
单例模式
- 保证一个类只有一个实例, 并提供一个访问该实例的全局节点;
- 实现方式:将默认构造函数设为私有;新建一个静态构建方法作为构造函数供外部调用,其中调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中,对该函数当调用都将返回缓存的对象;
2、结构型模式
- 结构型模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。
适配器模式
- 意图:适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。
- 解决方案:
- 适配器实现与其中一个现有对象兼容的接口;
- 现有对象可以使用该接口安全地调用适配器方法;
- 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。
桥接模式
- 意图:桥接模式是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。
- 解决方案:
- 对于有多个不同维度的类扩展时,每新增一个类别考虑其他维度都会导致子类指数增加;
- 抽取其中一个维度并使之成为独立的类层次, 这样就可以在初始类中引用这个新层次的对象, 从而使得一个类不必拥有所有的状态和行为;
抽象部分与实现部分: - 抽象部分 (也被称为接口) 是一些实体的高阶控制层。 该层自身不完成任何具体的工作, 它需要将工作委派给实现部分层 (也被称为平台)。
组合模式
- 意图:组合模式是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。
- 结构图:
- 实现方式:
- 1、确保应用的核心模型能够以树状结构表示。 尝试将其分解为简单元素和容器。 记住, 容器必须能够同时包含简单元素和其他容器。
- 2、声明组件接口及其一系列方法, 这些方法对简单和复杂元素都有意义。
- 3、创建一个叶节点类表示简单元素。 程序中可以有多个不同的叶节点类。
- 4、创建一个容器类表示复杂元素。 在该类中, 创建一个数组成员变量来存储对于其子元素的引用。 该数组必须能够同时保存叶节点和容器, 因此请确保将其声明为组合接口类型。
- 实现组件接口方法时, 记住容器应该将大部分工作交给其子元素来完成。
- 5、最后, 在容器中定义添加和删除子元素的方法。
装饰模式-- 装饰者模式、装饰器模式、Wrapper、Decorator
- 意图:允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
- 聚合或组合概念:一个对象包含指向另一个对象的引用, 并将部分工作委派给引用对象;
- 封装器:一个能与其他 “目标” 对象连接的对象。 封装器包含与目标对象相同的一系列方法, 它会将所有接收到的请求委派给目标对象。 但是, 封装器可以在将请求委派给目标前后对其进行处理, 所以可能会改变最终结果。
- 装饰器:当封装器实现了与其封装对象相同的接口就被称为装饰器。此时从客户端的角度来看, 这些对象是完全一样的。 封装器中的引用成员变量可以是遵循相同接口的任意对象。
- 结构图:
- 实现方法:
- 1、确保业务逻辑可用一个基本组件及多个额外可选层次表示。
- 2、找出基本组件和可选层次的通用方法。 创建一个组件接口并在其中声明这些方法。
- 3、创建一个具体组件类, 并定义其基础行为。
- 4、创建装饰基类, 使用一个成员变量存储指向被封装对象的引用。 该成员变量必须被声明为组件接口类型, 从而能在运行时连接具体组件和装饰。 装饰基类必须将所有工作委派给被封装的对象。
- 5、确保所有类实现组件接口。
- 6、将装饰基类扩展为具体装饰。 具体装饰必须在调用父类方法 (总是委派给被封装对象) 之前或之后执行自身的行为。
- 7、客户端代码负责创建装饰并将其组合成客户端所需的形式。
外观模式
- 意图:能为程序库、 框架或其他复杂类提供一个简单的接口。
- 外观类为包含许多活动部件的复杂子系统提供一个简单的接口。 与直接调用子系统相比, 外观提供的功能可能比较有限, 但它却包含了客户端真正关心的功能。
- 结构图:
- 实现方式:
- 1、考虑能否在现有子系统的基础上提供一个更简单的接口。 如果该接口能让客户端代码独立于众多子系统类, 那么你的方向就是正确的。
- 2、在一个新的外观类中声明并实现该接口。 外观应将客户端代码的调用重定向到子系统中的相应对象处。 如果客户端代码没有对子系统进行初始化, 也没有对其后续生命周期进行管理, 那么外观必须完成此类工作。
- 3、如果要充分发挥这一模式的优势, 你必须确保所有客户端代码仅通过外观来与子系统进行交互。 此后客户端代码将不会受到任何由子系统代码修改而造成的影响, 比如子系统升级后, 你只需修改外观中的代码即可。
- 4、如果外观变得过于臃肿, 你可以考虑将其部分行为抽取为一个新的专用外观类。
享元模式--缓存、Cache、Flyweight
- 意图:摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。
- 内在状态与外在状态:
- 对象的常量数据通常被称为内在状态, 其位于对象中, 其他对象只能读取但不能修改其数值。
- 而对象的其他状态常常能被其他对象 “从外部” 改变, 因此被称为外在状态。
- 享元模式建议不在对象中存储外在状态, 而是将其传递给依赖于它的一个特殊方法。 程序只在对象中保存内在状态, 以方便在不同情景下重用。 这些对象的区别仅在于其内在状态 (与外在状态相比, 内在状态的变体要少很多), 因此你所需的对象数量会大大削减。
- 享元工厂:为了能更方便地访问各种享元, 你可以创建一个工厂方法来管理已有享元对象的缓存池。 工厂方法从客户端处接收目标享元对象的内在状态作为参数, 如果它能在缓存池中找到所需享元, 则将其返回给客户端; 如果没有找到, 它就会新建一个享元, 并将其添加到缓存池中。
- 结构图:
- 实现方式:
- 1、将需要改写为享元的类成员变量拆分为两个部分:
- 内在状态: 包含不变的、 可在许多对象中重复使用的数据的成员变量。
- 外在状态: 包含每个对象各自不同的情景数据的成员变量
- 2、保留类中表示内在状态的成员变量, 并将其属性设置为不可修改。 这些变量仅可在构造函数中获得初始数值。
- 3、找到所有使用外在状态成员变量的方法, 为在方法中所用的每个成员变量新建一个参数, 并使用该参数代替成员变量。
- 4、可以有选择地创建工厂类来管理享元缓存池, 它负责在新建享元时检查已有的享元。 如果选择使用工厂, 客户端就只能通过工厂来请求享元, 它们需要将享元的内在状态作为参数传递给工厂。
- 5、客户端必须存储和计算外在状态 (情景) 的数值, 因为只有这样才能调用享元对象的方法。 为了使用方便, 外在状态和引用享元的成员变量可以移动到单独的情景类中。
- 1、将需要改写为享元的类成员变量拆分为两个部分:
代理模式--Proxy
- 意图:代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。
- 结构图:
- 应用场景:
- 延迟初始化 (虚拟代理)。 如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。从而无需在程序启动时就创建该对象,延迟到真正有需要的时候。
- 访问控制 (保护代理)。 如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。
- 本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。
- 记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。代理可以在向服务传递请求前进行记录。
- 缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。
- 智能引用。 可在没有客户端使用某个重量级对象时立即销毁该对象。
3、行为模式
- 行为模式负责对象间的高效沟通和职责委派。
责任链模式
- 意图:允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理者。
- 责任链将特定行为转换为被称作处理者的独立对象;
- 处理者可以决定不再沿着链传递请求, 这可高效地取消所有后续处理步骤;
- 结构图:
- 应用场景:
- 当程序需要使用不同方式处理不同种类请求, 而且请求类型和顺序预先未知时, 可以使用责任链模式;
- 当必须按顺序执行多个处理者时, 可以使用该模式;
- 如果所需处理者及其顺序必须在运行时进行改变, 可以使用责任链模式;
命令模式
- 意图:可将请求转换为一个包含与请求相关的所有信息的独立对象。 该转换让你能根据不同的请求将方法参数化、 延迟请求执行或将其放入队列中, 且能实现可撤销操作。
- 结构图:
- 应用场景:
- 如果你需要通过操作来参数化对象, 可使用命令模式;
- 如果你想要将操作放入队列中、 操作的执行或者远程执行操作, 可使用命令模式;
- 如果你想要实现操作回滚功能, 可使用命令模式;
- 实现方式:
- 1)声明仅有一个执行方法的命令接口;
- 2)抽取请求并使之成为实现命令接口的具体命令类;每个类都必须有一组成员变量来保存请求参数和对于实际接收者对象的引用;所有这些变量的数值都必须通过命令构造函数进行初始化;
- 3)找到担任发送者职责的类。 在这些类中添加保存命令的成员变量。 发送者只能通过命令接口与其命令进行交互。 发送者自身通常并不创建命令对象, 而是通过客户端代码获取;
- 4)修改发送者使其执行命令, 而非直接将请求发送给接收者;
- 5)客户端按顺序初始化对象:接收者、命令、发送者(并将其与特定命令关联);
迭代器模式
- 意图:让你能在不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素;
- 主要思想:将集合的遍历行为抽取为单独的迭代器对象;
- 结构图:
- 应用场景:
- 1)当集合背后为复杂的数据结构, 且你希望对客户端隐藏其复杂性时 (出于使用便利性或安全性的考虑), 可以使用迭代器模式;
- 2) 使用该模式可以减少程序中重复的遍历代码;
- 3)如果你希望代码能够遍历不同的甚至是无法预知的数据结构, 可以使用迭代器模式;
- 实现方式:
- 1)声明迭代器接口。 该接口必须提供至少一个方法来获取集合中的下个元素;
- 2)声明集合接口并描述一个获取迭代器的方法。 其返回值必须是迭代器接口;
- 3)为希望使用迭代器进行遍历的集合实现具体迭代器类。 迭代器对象必须与单个集合实体链接。 链接关系通常通过迭代器的构造函数建立;
- 4)在你的集合类中实现集合接口。 其主要思想是针对特定集合为客户端代码提供创建迭代器的快捷方式。 集合对象必须将自身传递给迭代器的构造函数来创建两者之间的链接;
- 5)检查客户端代码, 使用迭代器替代所有集合遍历代码。 每当客户端需要遍历集合元素时都会获取一个新的迭代器;
中介者模式
- 意图:让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互, 迫使它们通过一个中介者对象进行合作。
- 结构图:
- 应用场景:
- 1)当一些对象和其他对象紧密耦合以致难以对其进行修改时, 可使用中介者模式;
- 2)当组件因过于依赖其他组件而无法在不同应用中复用时, 可使用中介者模式;
- 3)如果为了能在不同情景下复用一些基本行为, 导致你需要被迫创建大量组件子类时, 可使用中介者模式;
- 实现方式:
- 1)找到一组当前紧密耦合, 且提供其独立性能带来更大好处的类;
- 2)声明中介者接口并描述中介者和各种组件之间所需的交流接口(主要是一个接受组件通知的方法);
- 3)实现具体中介者类。 该类可从自行保存其下所有组件的引用中受益;
- 4)让中介者负责组件对象的创建和销毁。 此后, 中介者可能会与工厂或外观类似;
- 5)组件必须保存对于中介者对象的引用。 该连接通常在组件的构造函数中建立, 该函数会将中介者对象作为参数传递;
- 6)修改组件代码, 使其可调用中介者的通知方法, 而非其他组件的方法。 然后将调用其他组件的代码抽取到中介者类中, 并在中介者接收到该组件通知时执行这些代码。
备忘录模式
- 意图:允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态;
- 关键术语:
- 状态快照(Snapshot);
- 原发器(Originator);
- 备忘录(Memento);
- 负责人(Caretakers);
- 结构图:
- 基于嵌套类的实现:
- 基于中间接口的实现:
- 封装更加严格的实现:
- 基于嵌套类的实现:
- 应用场景:
- 1) 当你需要创建对象状态快照来恢复其之前的状态时, 可以使用备忘录模式;
- 2) 当直接访问对象的成员变量、 获取器或设置器将导致封装被突破时, 可以使用该模式;
- 实现方式:
- 1)确定担任原发器角色的类;
- 2)创建备忘录类。 逐一声明对应每个原发器成员变量的备忘录成员变量;
- 3)将备忘录类设为不可变。 备忘录只能通过构造函数一次性接收数据。 该类中不能包含设置器;
- 4)将备忘录嵌套在原发器中 or 从备忘录类中抽取一个空接口, 然后让其他所有对象通过接口来引用备忘录;
- 5)在原发器中添加一个创建备忘录的方法,将自身状态传递给备忘录;
- 6)在原发器类中添加一个用于恢复自身状态的方法。 该方法接受备忘录对象作为参数;
- 7)无论负责人是命令对象、 历史记录或其他完全不同的东西, 它都必须要知道何时向原发器请求新的备忘录、 如何存储备忘录以及何时使用特定备忘录来对原发器进行恢复;
- 8)负责人与原发器之间的连接可以移动到备忘录类中。
观察者模式
- 意图: 允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象;
- 结构图:
- 应用场景:
- 当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式;
- 当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用;
状态模式
- 意图:让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样;
- 问题:对于有限状态机,添加更多状态和依赖于状态的行为后, 基于条件语句的状态机就会暴露其最大的弱点。 为了能根据当前状态选择完成相应行为的方法, 绝大部分方法中会包含复杂的条件语句。 修改其转换逻辑可能会涉及到修改所有方法中的状态条件语句, 导致代码的维护工作非常艰难;
- 结构图:
- 应用场景:
- 1)如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式;
- 2)如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式;
- 3)当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式;
策略模式
- 意图: 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换;
- 结构图:
- 应用场景:
- 1)当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式;
- 2)当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式;
- 3)如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来;
- 4)当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式;
- 实现方式:
- 1)从上下文类中找出修改频率较高的算法 (也可能是用于在运行时选择某个算法变体的复杂条件运算符);
- 2)声明该算法所有变体的通用策略接口;
- 3)将算法逐一抽取到各自的类中, 它们都必须实现策略接口;
- 4)在上下文类中添加一个成员变量用于保存对于策略对象的引用;然后提供设置器以修改该成员变量;上下文仅可通过策略接口同策略对象进行交互, 如有需要还可定义一个接口来让策略访问其数据;
- 5)客户端必须将上下文类与相应策略进行关联, 使上下文可以预期的方式完成其主要工作;
模板方法模式
- 意图:它在超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤;
- 结构图:
- 应用场景:
- 1)当你只希望客户端扩展某个特定算法步骤, 而不是整个算法或其结构时, 可使用模板方法模式;
- 2) 当多个类的算法除一些细微不同之外几乎完全一样时, 你可使用该模式。 但其后果就是,只要算法发生变化,你就可能需要修改所有的类;
- 实现步骤:
- 1)分析目标算法, 确定能否将其分解为多个步骤。 从所有子类的角度出发, 区分通用步骤和各算法不同步骤;
- 2)创建抽象基类并声明一个模板方法和代表算法步骤的一系列抽象方法;模板方法中根据算法结构依次调用相应步骤;注意:可用
final
修饰模板方法防止子类对其重写; - 3)实现一些通用步骤方法:虽然可将所有步骤全都设为抽象类型, 但默认实现可能会给部分步骤带来好处, 因为子类无需实现那些方法;
- 4)考虑在算法的关键步骤之间添加钩子——钩子是内容为空的可选步骤,方便后续扩展;
- 5)为每个算法变体新建一个具体子类, 它必须实现所有的抽象步骤, 也可以重写部分可选步骤。
访问者模式
- 意图:将算法与其所作用的对象隔离开来;
- 关键思想:
- 访问者模式建议将新行为放入一个名为访问者的独立类中, 而不是试图将其整合到已有类中;
- 需要执行操作的原始对象将作为参数被传递给访问者中的方法, 让方法能访问对象所包含的一切必要数据;
- 使用双分派技巧实现对不同类型节点对象的算法操作:与其让客户端来选择调用正确版本的方法, 不如将选择权委派给作为参数传递给访问者的对象本身;
- 结构图:
- 应用场景:
- 1)如果你需要对一个复杂对象结构 (例如对象树) 中的所有元素执行某些操作, 可使用访问者模式;
- 2)可使用访问者模式来清理辅助行为的业务逻辑;
- 3)当某个行为仅在类层次结构中的一些类中有意义, 而在其他类中没有意义时, 可使用该模式;
- 实现步骤:
- 1)在访问者接口中声明一组 “访问” 方法, 分别对应程序中的每个具体元素类;
- 2)声明元素接口,如果程序中已有元素类层次接口,可在层次结构基类中添加抽象的 “接收” 方法。 该方法必须接受访问者对象作为参数;
- 3)在所有具体元素类中实现接收方法;
- 4)元素类只能通过访问者接口与访问者进行交互;
- 5)为每个无法在元素层次结构中实现的行为创建一个具体访问者类并实现所有的访问者方法;
- 6)客户端必须创建访问者对象并通过 “接收” 方法将其传递给元素。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY