当前位置: 首页 > news >正文

【读书笔记】Design Patterns (1994)✅

1、创建型

1.1 抽象工厂

1.1.1 目的

提供一个接口,创建一系列相关或相互依赖的对象,而无需指定他们具体的类

1.1.2 动机

应用程序中的组件(按钮,滚动条,窗口等)风格不能写死,不然以后更改风格会很困难

解决此问题的方法:

  • 定义一个抽象的组件工厂类(该类中声明了创建各种组件的方法)
  • 每个组件都有一个抽象类,子类以指定的界面外观实现具体
  • 调用抽象的组件工厂中的方法,来获得需要的组件对象
  • 客户端不关心正在使用的具体类

1.1.3 适用范围

  • 系统应该独立于其产品的创建、组合和表示方式
  • 一个系统应该配置一个或多个产品系列
  • 一系列相关的产品对象被设计成一起使用,您需要强制执行这个约束
  • 你想提供产品的类库,你仅仅想暴露 一个接口,而不是它的实现

1.1.4 结构

在这里插入图片描述

1.1.5 参与者

  • 抽象工厂(WidgetFactory),声明用于创建抽象产品对象的操作的接口
  • 具体工厂(MotifWidgetFactory, PMWidgetFactory),实现创建具体产品对象的操作
  • 抽象产品(Window, ScrollBar),声明一个接口或产品对象的类型
  • 具体产品(MotifWindow, MotifScrollBar),由具体工厂创建产品对象,实现抽象工厂接口
  • 客户端

1.1.6 协作

  • 通常,在运行时创建一个具体工厂类的实例
  • 这个具体工厂创建具有特定实现的产品对象
  • 为了创造不同的产品对象,客户应该使用不同的具体工厂
  • AbstractFactory将产品对象的创建推迟到它的子类ConcreteFactory中

1.1.7 效果

抽象工厂模式的优缺点:

  • 它分离的具体的类,客户端通过抽象接口操作实例。产品类名在具体工厂的实现中被隔离
  • 它使得交换产品系列变得容易
  • 它有利于产品的一致性
  • 难以支持新种类的产品,要添加新产品就要改AbstractFactory,这个类改变了,子类也得改变

1.1.8 实现

实现抽象工厂模式的有用技术:

  • 工厂作为单例,一个应用需要一个具体工厂,所以单例较好
  • 创建产品
    • 每一个产品定义一个工厂方法
    • 使用原型模式来实现,基于原型的方法使得并非每一个新的产品系列都要一个新的具体工厂类
  • 定义可扩展的工厂

1.1.9 代码示例

1.2 建造者

1.2.1 目的

使复杂对象的构建和表现分离,以便同样的构建过程,创建不同的表现

1.2.2 动机

文本阅读器,在打开的文件格式可能由多种,把阅读器的显示和格式的转换分离开

1.2.3 适用范围

  • 创建复杂对象的算法,应该和构建对象、组合对象分离开
  • 构造过程必须允许被构造对象的不同表示

1.2.4 结构

在这里插入图片描述

1.2.5 参与者

  • 构建者Builder(例如,TextConverter),指定用于创建Product对象的各个部分的抽象接口
  • 具体构建者ConcreteBuilder(例如,ASCIIConverter, TeXConverter, TextWidgetConverter)
    • 通过实现构建者接口,构建并组合产品部件
    • 定义并跟踪它创建的表示
    • 提供一个取回产品的接口(GetASCIIText, GetTextWidget,等)
  • 管理者Director(例如,RTFReader),使用构建者接口,构建对象
  • 产品Product(例如,ASCIIText, TeXText, TextWidget)
    • 表示正在构建的对象,具体的构建者,构建产品的内部表现,定义组合过程
    • 包括定义组成部分的类,包括用于将这些部分组装成最终结果的接口

1.2.6 协作

  • 客户端创建Director对象,并以期望的构建者对象配置它
  • 每当产品的一部分需要构建时,Director都会通知Builder
  • Builder处理来自Director的请求并向产品添加部件
  • 客户端从builder种获取产品

1.2.7 效果

  • 它让你改变产品的内部表现
  • 它隔离了用于构造和表示的代码
  • 它能让你更好的控制构建过程

1.2.8 实现

1.2.9 代码示例

建造者注重产品的构建步骤,而抽象工厂注重系列产品创建

1.3 工厂方法

1.3.1 目的

定义一个创建对象的接口,但是让子类决定实例化哪种类型,工厂方法,让类的实例化延迟到子类中

1.3.2 动机

1.3.3 适用性

  • 当一个类不知道它所创建对象的类时
  • 当一个类希望由它的子类来指定它所创建的对象的时候
  • 类委托责任给多个帮助子类时,并且本地化哪些帮助子类

1.3.4 结构

在这里插入图片描述

1.3.5 参与者

  • 产品Product,(例,Document),定义工厂方法创建对象的接口
  • 具体产品ConcreteProduct,(例,MyDocument),实现产品接口
  • 创建者Creator,(例,Application)
    • 声明返回产品对象的工厂方法,可以定义一个默认的工厂方法实现,返回默认的具体产品
    • 调用工厂方法去创建产品对象
  • 具体创建者ConcreteCreator,(例,MyAppliction),重写工厂方法,返回具体的产品实例

1.3.6 协作

Creator依赖于它的子类来定义工厂方法,以便它返回适当的ConcreteProduct的实例

1.3.7 效果

  • 为子类提供钩子(工厂方法)
  • 连接并行的类层次

1.3.8 实现

  • 两个主要的情况
    • Creator类是一个抽象类,不提供工厂方法的实现,
    • Creator类是一个具体类,为工厂方法提供一个默认的实现
    • 可以是抽象类,并提供默认的实现,这种形式较少使用
  • 参数化工厂方法
    • 工厂方法,通过参数创建各种类型产品
  • 特定语言的变化和问题
  • 使用模板以避免创建子类
  • 命名约束

1.3.9 代码

1.4 原型

1.4.1 目的

使用原型实例来指定要创建的对象种类,并通过复制这个原型来创建新的对象

1.4.2 动机

1.4.3 适用性

系统应当独立于产品的创建,组合,表示时

  • 当实例化的类是运行时指定时
  • 避免建立与产品的类层次结构相似的工厂类层次结构
  • 当一个类的实例只能有几种不同的状态组合之一时

1.4.4 结构

在这里插入图片描述

1.4.5 参与者

  • 原型Prototype,(例,Graphic),声明一个接口来克隆它自己
  • 具体的原型ConcretePrototype,(例,Staff, WholeNote, HalfNote),为克隆自己实现一个操作
  • 客户端Client,(例,GraphicTool),通过原型克隆它自己来创建一个新的对象

1.4.6 协作

1.4.7 效果

和抽象工厂和建造者模式类似的效果,但是有一些额外的好处:

  • 添加和移除产品在运行时
  • 通过改变值来指定新的对象
  • 通过改变结构来指定新的对象
  • 减少子类
  • 动态的用类配置应用程序

1.4.8 实现

  • 使用一个原型管理
  • 实现克隆操作
  • 初始化克隆

1.4.9 代码

1.5 单例

1.5.1 目的

确保一个类只有一个实例,并提供对它的全局访问点

1.5.2 动机

现实中有些东西只有一份,一个打印服务,一个文件系统,一个窗口管理等

1.5.3 适用性

  • 一个类必须只有一个实例,并且客户端必须能够从一个众所周知的访问点访问它
  • 唯一实例要被子类扩展,并且客户端在不修改代码的情况下,使用扩展实例

1.5.4 结构

在这里插入图片描述

1.5.5 参与者

单例Singleton,定义一个允许客户端访问其唯一实例的Instance操作。

1.5.6 协作

客户端只能通过Singleton的instance操作来访问Singleton实例

1.5.7 效果

  • 对唯一实例的受控访问,因为单例类封装了自己的唯一实例,它可以控制客户端如何什么时候访问它
  • 简化名称空间,单例模式是对全局变量的改进,它避免了使用存储单独实例的全局变量污染名称空间
  • 允许对操作和表示进行细化,Singleton类可以被子类化,并且很容易用这个扩展类的实例来配置应用程序。
  • 允许不同数量的实例
  • 比类操作更灵活

1.5.8 实现

  • 确保唯一的实例,使用静态成员函数或类方法
  • 创建Singleton类的子类

1.5.9 代码

1.6 模式对比

两种方法参数化系统:

  • 子类创建对象
    • Factory Method模式
  • 对象组合
    • Abstract Factory模式,工厂对象生产多个类的对象
    • Builder模式,工厂对象使用相应的协议,递增的构建复杂的产品
    • Prototype模式,工厂对象通过复制原型对象构建产品

总的来说,Prototype模式可能是绘图编辑器框架的最佳选择,因为它只需要在每个Graphics类上实现一个Clone操作

使用抽象工厂,原型,构建者比使用工厂方法更灵活,但是也更复杂

2、结构型

2.1 适配器模式

2.1.1 目的

将一个类的接口转换为客户端希望的另一个接口,接口不兼容的类可以一起工作

2.1.2 动机

有时需要两个不相关的类发生关系

2.1.3 适用性

  • 想使用一个已存在的类,而它的接口不符合你的要求
  • 想创建一个可以复用的类,该类可以与其他不相关或不可预见的类协同工作
  • (仅适用对象适配器)想使用已存在的类,但不可能对每一个都子类化,对象适配器可以适配它的父类接口

2.1.4 结构

类适配器(需要支持多继承)
在这里插入图片描述

对象适配器
在这里插入图片描述

2.1.5 参与者

  • 目标(Target),(例,Shape),定义客户端适用的与特定领域相关的接口
  • 客户端(Client),(例,DrawingEditor),和符合目标接口的对象协同
  • 被适配者(Adaptee),(例,TextView),定义一个已经存在的接口,这个接口需要适配
  • 适配器(Adapter),(例,TextShape),对Adaptee接口与Target接口进行适配

2.1.6 协作

客户端在适配器实例上调用一些操作,接着适配器调用Adaptee的操作实现这个请求

2.1.7 效果

类适配器:

  • 提交一个具体的Adaptee类,因此当想适配一个类或它的所有子类时,类适配器不工作
  • 因为Adapter是Adaptee的子类,所以Adapter重写了Adaptee的一些行为
  • 仅引入了一个对象,并不需要额外的指针以间接得到Adaptee

对象适配器:

  • 允许一个Adapter和多个Adaptee同时工作,Adapter也可以一次性给所有Adaptee添加功能
  • 使重写Adaptee的行为困难,这就需要Adaptee的子类,并使Adapter引用这个子类而不是引用Adaptee本身

2.1.8 实现

  • 使用C++实现适配器类,Adapter类应该是Target的子类,但不是Adaptee的子类型
  • 可插入适配器

2.1.9 代码

2.2 桥接模式

2.2.1 目的

将抽象部分和它的实现分离,使他们可以独立的变化

2.2.2 动机

当一个抽象有多个实现时,通常使用继承抽象类来实现。继承使得抽象部分和实现部分固定,无法对抽象和实现单独修改

2.2.3 适用性

  • 分离抽象和实现
  • 类的抽象以及它的实现都可以通过生成子类的方法来扩充
  • 对一个抽象的实现部分修改,客户代码不用重新编译
  • 你想对客户端完全隐藏抽象的实现部分

2.2.4 结构

在这里插入图片描述

2.2.5 参与者

  • 抽象Abstraction,(例如,Window)
    • 定义一个接口
    • 维护一个指向Implementor的指针
  • 精确抽象RefinedAbstraction,(例如,IconWindow)
    • 扩充由Abstraction定义的接口
  • 实现Implementor,(例如,WindowImp)
    • 定义实现类的接口,该接口不一定与Abstraction接口完全一致
  • 具体实现ConcreteImplementor,(例如,XWindowImp,PMWindowImp)
    • 实现Implementor接口并定义它的具体实现

2.2.6 协作

Abstraction将client的请求转发给它的Implementor对象

2.2.7 效果

  • 分离接口及其实现部分
  • 提高可扩充性
  • 实现细节对客户透明

2.2.8 实现

  • Implementor仅有一个实现的时候,没有必要创建一个抽象的Implementor类
  • 创建正确的Implementor对象
  • 共享Implementor对象
  • 采用多重继承机制

2.2.9 代码

2.3 组合模式

2.3.1 目的

将对象组合成树状结构,以表示部分-整体层次结构,让客户端统一对待单个对象和对象的组合

2.3.2 动机

对于图像编辑程序,一般情况,为Text 和 Line这样的图元定义一些类,另外定义一些类作为图元的容器

这种方法存在问题:需要区别对待图元对象和容器对象,对这些类的区别使用,使程序更加复杂

Composite模式描述了如何使用递归组合,使用户不必对这些类进行区分

Composite模式的关键是,代表图元和容器的抽象类

2.3.3 适用性

  • 想表示对象的整体与部分层次结构
  • 希望客户端忽略组合对象与单个对象的不同,统一使用组合对象中的所有对象

2.3.4 结构

在这里插入图片描述

2.3.5 参与者

  • 组件Component,(Graphic)
    • 为组合中的对象声明接口
    • 实现所有类共有接口的默认行为
    • 声明一个接口用于访问和管理它的子组件
    • (可选)在递归结构中定义一个接口,用于访问一个父组件,并在合适情况下实现它
  • 叶节点Leaf,(例如,Line,Text等)
    • 组合中表示叶节点对象,叶节点没有子节点
    • 在组合中定义图元对象的行为
  • 组合Composite,(例如,Picture)
    • 定义有子组件的组件的行为
    • 存储子组件
    • 在Component接口中实现与子部件有关的操作
  • 客户端Client
    • 通过Component接口操作组合中的对象

2.3.6 协作

客户端使用Component接口与组合中的对象交互,如果接收的是叶节点,直接处理请求。如果接收的是Composite,将请求发给它的子组件。

2.3.7 效果

  • 定义了包含基本对象和组合对象的类层次结构
  • 简化客户端代码
  • 使容易增加新类型的组件
  • 使你的设计变得更加一般化

2.3.8 实现

2.3.9 代码

2.4 装饰器模式

2.4.1 目的

为对象动态的追加额外的职责,为扩展功能提供过了灵活的选择,而不是子类化

2.4.2 动机

动机就是,给个别对象追加职责,而不是给整个类时,如何做?

第一(不灵活),继承一个边框类是静态的,不能控制如何什么时候用边框装饰组件

第二,更灵活的方法是将组件包含在另一个添加边框的对象中,包含对象就叫做装饰器

2.4.3 适用性

  • 动态地、透明地向单个对象添加职责,也就是说,不影响其他对象
  • 职责可以被撤销
  • 子类化扩展不可行时

2.4.4 结构

在这里插入图片描述

2.4.5 参与者

  • Component组件,(例,VisualComponent),为对象定义一个接口,对象可以动态的添加职责
  • ConcreteComponen具体组件,(例,TextView),定义一个可以附加额外职责的对象
  • Decorator装饰器,包含组件对象的引用,并且定义一个接口(符合组件接口)
  • ConcreteDecorator具体装饰器,(例,BorderDecorator, ScrollDecorator),追加职责到组件上

2.4.6 协作

装饰器转发请求到组件对象,它可以选择性的在转发请求的前后追加额外的操作

2.4.7 效果

装饰器模式有两个有点和两个缺点:

  • 比静态继承更灵活
  • 避免在层次结构中使用高负载特性的类。
  • 装饰器和它的组件不同
  • 会有很多小对象

2.4.8 实现

应用装饰器应该给考虑的几个问题:

  • 接口一致性,装饰器对象的接口必须与它所装饰的组件的接口一致
  • 省略了抽象的Decorator类,
  • 保持组件类的轻量级,组件和装饰器必须从一个公共的Component类派生
  • 改变一个对象的外表还是改变它的内在,我们可以把decorator看作是对象上的一层皮肤,它可以改变对象的行为

2.4.9 代码

2.5 外观模式

2.5.1 目的

为子系统中的一组接口提供统一的接口

2.5.2 动机

2.5.3 适用性

  • 希望为复杂的子系统提供统一的接口
  • 引入facade来将子系统与客户端和其他子系统分离,从而提高子系统的独立性和可移植性
  • 你想给你的子系统分层

2.5.4 结构

在这里插入图片描述

2.5.5 参与者

  • Facade外观,(例,Compiler),
    • 知道子系统类是负责请求
    • 委托客户端请求合适的子系统对象
  • subsystem classes,(例,Scanner, Parser, ProgramNode, etc)
    • 实现子系统功能
    • 处理由外观对象分配的任务
    • 对facade一无所知,不引用它

2.5.6 协作

客户端发送请求给Facade和子系统通讯

客户端不直接访问它的子系统对象

2.5.7 效果

  • 它将客户端与子系统组件隔离开来,从而减少了客户端处理的对象数量,并使子系统更易于使用
  • 它促进了子系统与其客户端之间的弱耦合。子系统中的组件通常是强耦合的。弱耦合允许您在不影响其客户机的情况下改变子系统的组件
  • 如果需要的话,它不会阻止应用程序使用子系统类

2.5.8 实现

  • 客户端和子系统之间的耦合可以通过使Facade成为一个抽象类来进一步减少,该抽象类具有用于子系统不同实现的具体子类。然后客户端可以通过抽象接口与子系统进行通信
  • 公共与私有子系统类

2.6 享元模式

2.6.1 目的

使用共享来有效地支持大量细粒度对象

2.6.2 动机

文档编辑器不可能为每个字符设计一个对象,这样即使中等大小的文档可能也需要十万个字符对象,Flyweight模式描述了如何共享对象

2.6.3 适应性

  • 一个应用需要使用大量对象时
  • 使用大量对象,造成内存开销大
  • 对象的多数状态可以变为外部的
  • 一旦外部对象移除,可以用相对少的共享对象代替很多组对象
  • 应用程序不依赖于对象标识

2.6.4 结构

在这里插入图片描述

2.6.5 参与者

  • Flyweight,(例,Glyph),声明一个接口,通过这个接口Flyweight可以接受并执行在外部状态
  • ConcreteFlyweight,(例,Character),实现Flyweight接口并且添加一个内部状态的存储。ConcreteFlyweight对象是可分享的
  • UnsharedConcreteFlyweight,(例,Row ,Column),不是所有的Flyweight子类需要被分享。
  • FlyweightFactory,创建并管理Flyweight对象
  • Client,包含Flyweight的引用,计算或存储Flyweight的外部状态

2.6.6 协作

  • Flyweight的状态需要运作,必须被称为内部的或外部的,内部状态被存储在ConcreteFlyweight中。外部状态被存储或被计算在客户端对象中。当他们调用他们的operation时,客户端传递这个状态给flyweight
  • 客户端不应该直接实例化ConcreteFlyweights,客户端获得对象仅仅从FlyweightFactory对象

2.6.7 效果

节省存储空间

2.6.8 实现

  • 移除外部状态
  • 管理分享对象

2.7 代理模式

2.7.1 目的

为另一个对象提供代理或占位符以控制对它的访问

2.7.2 动机

文档编辑器加载图片时,不可能全部加载,就要使用代理对象来代替图像,等需要加载时,再从磁盘中加载到内存中

2.7.3 适用性

  • 远程代理为不同地址空间中的对象提供本地代表
  • 虚拟代理根据需要创建昂贵的对象
  • 保护代理控制对原始对象的访问
  • 智能引用取代了简单指针,它在访问对象时执行一些附加操作

2.7.4 结构

在这里插入图片描述

2.7.5 参与者

  • Proxy代理,(例,ImageProxy)
    • 维护一个引用,让代理访问真实Subject
    • 提供了一个与Subject相同的接口,这样代理就可以代替真实的Subject
    • 控制对真实Subject的访问,并可能负责创建和删除它
  • Subject实体,(例,Graphic)
    • 定义了RealSubject和Proxy的公共接口,这样Proxy就可以在任何需要RealSubject的地方使用
  • RealSubject真实实体,(例,Image)
    • 定义代理所代表的真实对象

2.7.6 协作

代理在适当的时候将请求转发给RealSubject,这取决于代理的类型

2.7.7 效果

2.7.8 实现

  • 重载C++中的成员访问操作符
  • 代理不必要知道真实实体的类型

2.8 模式对比

2.8.1 适配器和桥接

  • 相同点

    • 两个都是通过为另外一个对象提供间接性来促进灵活性

    • 两个都涉及到从自身以外的接口转发请求到这个对象

  • 不同点

    • 适配器模式主要解决两个存在接口的不兼容
    • 桥接模式连接抽象和它的实现
    • 当您发现两个不兼容的类不能一起工作时,适配器通常是必要的,通常是为了避免复制代码
    • 桥接的用户预先理解抽象必须有几个实现,并且两者可以独立发展
    • 适配器模式使事物在设计完成后工作,而桥接模式相反
    • 外观模式定义了新的接口
    • 适配器模式复用一个原有的接口

2.8.1 组合,装饰器,代理模式

  • 相同
    • 组合和装饰器基于递归组合来组织无限数量的对象
    • 装饰器和代理都描述如何提供间接性的对象
  • 不同
    • 装饰器让你在不创建子类的情况下为对象添加职责
    • 组合模式专注于结构化类,以便可以统一处理相关对象,并且可以将多个对象视为一个对象
    • 组合和装饰器一般协同使用
    • 从装饰器看,组合就是ConcreteComponent
    • 从组合看,装饰器就是Leaf

3、行为型

3.1 责任链模式

3.1.1 目的

通过给多个对象处理请求的机会,避免将请求的发送方与其接收方耦合在一起,链接接收对象并沿着链传递请求,直到一个对象处理它

3.1.2 动机

图像编辑系统中的帮助信息的按钮,责任链模式

这种模式的思想是通过给多个对象处理请求的机会来解耦发送方和接收方,请求沿着对象链传递,直到其中一个对象处理它。

发出请求的对象不知道谁会处理它,所以叫他隐式接收器(impli citreceiver)

3.1.3 适用性

  • 多个对象处理请求
  • 您希望在不显式指定接收者的情况下向几个对象之一发出请求
  • 可以处理请求的对象集应该动态指定

3.1.4 结构

在这里插入图片描述

3.1.5 参与者

  • Handler处理器,(例,HelpHandler)
    • 定义一个接口处理请求
    • 实现继承者链接(可选)
  • ConcreteHandler具体处理器,(例,PrintButton, PrintDialog)
    • 处理它负责的请求
    • 能访问它的继承者
    • 如果ConcreteHandler能处理请求,就处理,否则传递请求给它的继承者
  • Client客户端
    • 将请求初始化为链上的ConcreteHandler对象

3.1.6 协作

当客户端发出请求时,该请求沿着链传播,直到ConcreteHandler对象负责处理它

3.1.7 效果

  • 减少耦合
    • 责任链可以简化对象的相互连接。对象不维护对所有候选接收者的引用,而是保留对其后继接收者的单个引用
  • 增加了为对象分配职责的灵活性
    • 您可以通过在运行时添加或以其他方式更改链来添加或更改处理请求的职责
  • 不确保可以接收
    • 由于请求没有明确的接收者,所以不一定会被处理,请求可以从链的末端掉下来而不被处理

3.1.8 实现

  • 实现继承者链
    • 方法一:定义新链
    • 方法二:使用存在的链
  • 连接继承者
  • 表示请求
  • 自动传递

3.2 命令模式

3.2.1 目的

将请求封装为对象,允许使用不同的请求对客户端进行参数化,对请求进行排队或记录请求,并支持撤销操作

3.2.2 动机

3.2.3 适用性

有如下需求时,可以使用命令模式:

  • 通过执行行为,参数化对象。命令模式是回调机制的一个面向对象的替代品
  • 在不同的时刻指定、排列和执行请求。
  • 支持取消操作
  • 支持修改日志
  • 围绕基于基本操作的高级操作构建系统。支持事务的系统中

3.2.4 结构

在这里插入图片描述

3.2.5 参与者

  • 命令Command,声明一个接口,执行一个操作
  • 具体命令ConcreteCommand,(例如,PasteCommand, OpenCommand)
    • 定义一个接收者和动作的绑定
    • 调用接收者相应的操作,以实现Execute执行
  • 客户端Client,(例如,Application)
    • 创建一个具体的命令对象,并设置它的接收者
  • 调用者Invoker,(例如,Menultem)
    • 请求命令,并且执行请求
  • 接收者Receiver,(例如,Document,Application)
    • 知道如何实施和执行一个请求相关的操作,任何类都可能作为一个接收者
    • 实际要执行命令的对象

3.2.6 协作

  • 客户端创建具体命令对象,并指定它的接收器
  • 调用者对象存储具体命令对象
  • 调用者通过调用命令类的执行方法,来发送请求
  • 具体命令对象调用它接收器中的操作方法去执行请求

3.2.7 效果

  • 命令模式把调用操作的对象与知道怎样去执行它的对象之间解耦了
  • 命令是头等的对象,他们可以像其他对象一样被操作和扩展
  • 可以将多个命令装配成一个组合命令
  • 增加新的命令类很容易,无需改变已有类

3.2.8 实现

  • 一个命令对象应该是如何智能的

  • 支持撤销和重做

  • 避免撤销操作过程中的错误积累

  • 使用C++模板

3.3 解释器模式

3.3.1 目的

给定一个语言,为它的语法定义一种表示,并有一个解释器用表示去解释语言中的句子

3.3.2 动机

3.3.3 适用性

  • 语法是简单的
  • 效率不是关键问题

3.3.4 结构

在这里插入图片描述

3.3.5 参与者

  • 抽象表达式AbstractExpression,(例如,(RegularExpression)
    • 声明一个抽象的解释操作,该操作对抽象语法树中的所有节点都是通用的
  • 终端表达式TerminalExpression,(例如,LiteralExpression)
    • 实现与语法中的终端符号关联的解释操作
    • 句子中的每个终端符号都需要一个实例
  • 非终端表达式NonterminalExpression,(例如,(AlternationExpression, RepetitionExpression, SequenceExpressions)
    • 每个规则都需要一个这样的类
  • 上下文Context
    • 包含解释器全局的信息
  • 客户端Client
    • 构建(或给定)抽象语法树
    • 调用解释操作

3.3.6 协作

  • 客户端构建语法树,然后初始化上下文和调用解释操作
  • 每一个非终端表达式节点,定义相应的子表达式的解释操作,而终端表达式的解释操作构成递归的基础
  • 每一个节点的解释操作,使用上下文存储和访问解释器的状态

3.3.7 效果

  • 容易改变和扩展语法
    • 使用类表示了语法规则,所以使用继承可以实现这一点
  • 容易实现语法
  • 复杂的语法难以维护
  • 增加新的解释表达式方式

3.3.8 实现

  • 创建抽象语法树

  • 定义解释操作

  • 和享元模式共享终端标志

3.4 迭代器模式

3.4.1 目的

提供一种方式按顺序访问一个聚合对象的元素,而不需要暴露该对象的内部表示

3.4.2 动机

优雅的遍历一个聚合对象

3.4.3 适用性

  • 访问一个聚合对象的元素,而不暴露其内部表示
  • 支持聚合对象的多种遍历
  • 提供一个唯一的接口去遍历不同的聚合对象

3.4.4 结构

在这里插入图片描述

3.4.5 参与者

  • 迭代器Iterator
    • 定义一个接口,访问和遍历元素
  • 具体迭代器
    • 实现迭代器接口
    • 保持跟踪当前聚合对象遍历的位置
  • 聚合对象
    • 定义一个接口,创建迭代器对象
  • 具体聚合对象
    • 实现迭代器,返回一个真正具体迭代器

3.4.6 协作

当遍历时,具体迭代器保持跟踪聚合对象的当前对象和计算出下一个对象

3.4.7 效果

  • 支持不同方式遍历聚合
  • 简化了聚合的接口
  • 在同一个聚合上,可以有多个遍历

3.4.8 实现

  • 谁控制迭代
    • 迭代器控制,叫内部迭代器
    • 客户端控制,叫外部迭代器
    • 外部迭代器比内部灵活。例如,用外部迭代器比较两个集合是否相等很容易,但用内部迭代器实际上是不可能的
  • 谁定义遍历算法
    • 迭代器中可以定义算法,聚合中也可以定义算法
  • 迭代器健壮程度如何
    • 遍历聚合时,操作修改聚合是危险的
    • 健壮的迭代器确保插入和删除不会干扰遍历
  • 附加的迭代器操作
  • 在c++中使用多态的迭代器
    • 它们要求迭代器对象由工厂方法动态分配
  • 迭代器可有特权访问
  • 用于组合对象的迭代器
  • 空迭代器

3.5 中介者模式

3.5.1 目的

定义一个对象,该对象封装了一系列对象的交互。终结者模式使对象引用的耦合更松散

3.5.2 动机

系统中,相互引用的对象太多,导致系统耦合增加。使用一个中介来传递对象之间的操作

3.5.3 适用性

  • 一系列对象定义好,但是之间通讯起来复杂。结果错综复杂凌乱,难以理解
  • 复用一个对象困难,因为它和太多对象之间引用和通讯
  • 在几个类之间分布的行为应该是可定制的,而不需要大量的子类化

3.5.4 结构

在这里插入图片描述

3.5.5 参与者

  • 中介者Mediator,(例,DialogDirector)
    • 定义一个接口为了和同事对象通讯
  • 具体中介者ConcreteMediator,(例,(FontDialogDirector)
    • 通过协调同事对象,来实现合作行为
    • 了解并且维护它的同事
  • 同事类Colleague classes,(例,ListBox, EntryField)
    • 每一个同事类了解它的中介者类
    • 每一个同事和其他同事的通讯,都需和中介者进行通讯

3.5.6 协作

同事发送和接受来自中介者的请求。中介者实现合作行为,在各同事间转发合适的请求

3.5.7 效果

  • 减少子类的生成
  • 解耦同事类
  • 简化了对象协议
  • 将对象协作进行抽象
  • 使用控制集中化

3.5.8 实现

  • 忽略抽象的中介者类
    • 当同事仅仅与一个中介者一起工作时,没有必要定义一个抽象的中介者类
  • 同事-中介者通讯
    • 使用观察者模式,另一种是在观察者中定义一个特殊的通知接口,各同事通讯时直接调用该接口

3.6 备忘录模式

3.6.1 目的

在不违背封装的前体下,捕捉并具体化一个对象的内部状态,以便后期可以恢复到这个状态

3.6.2 动机

对象的内部状态封装在其内部,那么不可能在外部保存其状态。若暴露其内部状态有违反封装原则。

一个备忘录是一个对象,它存储另一个对象在某各瞬间的内部状态。

3.6.3 适用性

  • 一个对象状态的快照必须被存储,以便后期恢复这个状态。
  • 获取状态的直接接口将暴露实现细节并破坏对象的封装

3.6.4 结构

在这里插入图片描述

3.6.5 参与者

  • 备忘录Memento,(例,SolverState)
    • 存储原发对象的内部状态
    • 防止原发器以外的对象访问
  • 原发器Originator,(例,ConstraintSolver)
    • 创建一个备忘录,包含其当前内部状态快照
    • 使用备忘录去存储它的内部状态
  • 管理者Caretaker,(例,undo mechanism)
    • 为备忘录的维护负责
    • 永远不要操作和检查备忘录的内容

3.6.6 协作

  • 管理员从一个原发器请求一个备忘录,持有一段时间,然后传递回给原发器。
  • 备忘录是被动的,只有创建备忘录的原发器才可以分配和恢复它的状态

3.6.7 效果

  • 保留封装边界
  • 简化的原发器
  • 使用备忘录可能代价高
  • 定义窄和宽的接口
  • 维护备忘录的潜在代价

3.6.8 实现

3.7 观察者模式

3.7.1 目的

定义了对象之间一对多的依赖,以便于一个对象改变状态,其他依赖的对象被通知,并自动更新

3.7.2 动机

表格数据更新,表格的柱状图和饼状图也跟着更新。

3.7.3 适用性

  • 当一个抽象有两个方面时,其中一个依赖于另一个。将这些方面封装在单独的对象中,可以独立地更改和重用它们
  • 当对一个对象的更改需要更改其他对象时,您不知道需要更改多少对象
  • 一个对象应该能够通知其他对象,而不需要假设这些对象是谁。换句话说,您不希望这些对象紧密耦合

3.7.4 结构

在这里插入图片描述

3.7.5 参与者

  • 主体Subject
    • 知道它的观察者。多个观察者对象可以观察一个主体
    • 提供了一个连接和分离观察者对象的接口
  • 观察者Observer
    • 为对象定义一个更新接口,在主体发生更改时应通知该接口
  • 具体主体ConcreteSubject
    • 将感兴趣的状态存储到concreteobserver对象
    • 当它的状态发生变化时,向它的观察者发送通知
  • 具体观察者ConcreteObserver
    • 维护一个对ConcreteSubject对象的引用
    • 存储一个和主体保持一致的状态
    • 实现观察者更新接口,以保持其状态与主体的状态一致

3.7.6 协作

  • 当发生可能使其观察者的状态与自己的状态不一致的变化时,ConcreteSubject会通知观察者
  • 在被告知具体主体发生变化后,ConcreteObserver对象可以向主体查询信息,并使用这些信息来协调自己和主体的状态

3.7.7 效果

  • 主体与观察者之间的抽象耦合
  • 支持广播通信
  • 意想不到的更新

3.7.8 实现

  • 将对象映射到他们的观察者
    • subject中显式存储observer的引用,当有多个subject和少量observer时,这样存储代价很大
    • 使用map存储subject和observer的映射,这种方法增加了访问观察者的成本
  • 观察多个主体对象
    • 主体可以简单地将自己作为Update操作中的参数传递,从而让观察者知道要检查哪个主体。
  • 谁触发更新(谁调用Notify)?
    • 第一种,主体中的state-setting改变state后,调用Notify
      • 优点客户端不必调用
      • 缺点连续操作会连续调用,效率低
    • 第二种,让客户端负责在正确的时间调用Notify
      • 优点客户端可以等到进行了一系列状态更改之后才触发更新,从而避免了不必要的中间更新
      • 缺点是客户机有额外的责任来触发更新,客户端可能会忘记调用Notify
  • 空引用已删除的主体
    • 删除主体不应该在其观察者中产生悬空引用
  • 在通知之前确保Subject状态是自一致的
    • 在调用Notify之前确保Subject状态是自一致的是很重要的,因为观察者在更新自己的状态的过程中会查询主题的当前状态。
    • 当Subject子类操作调用继承操作时,这种自一致性规则很容易在无意中被违反
  • 避免特定于观察者的更新协议:推和拉模型
  • 明确指定感兴趣的修改
  • 封装复杂的更新语义
  • 结合Subject类和Observer类

3.8 状态模式

3.8.1 目的

允许对象在其内部状态改变时改变其行为。对象似乎改变了它的类

3.8.2 动机

考虑网络连接类,该对象的状态处于若干各不同的状态之一,之后可能会根据该对象的不同状态来执行不同的行为

3.8.3 适用性

  • 一个对象行为依赖于它的状态,它必须在运行时根据这种状态改变自己的行为。
  • 一个多分支的条件语句,依赖于某个对象的状态

3.8.4 结构

在这里插入图片描述

3.8.5 参与者

  • 上下文Context,(例,TCPConnection)
    • 定义客户端感兴趣的接口
    • 维护一个定义当前状态的ConcreteState子类的实例
  • 状态State,(例如,(TCPState)
    • 定义了一个接口,用于封装与上下文的特定状态相关的行为
  • 具体状态子类ConcreteState subclasses,(例,(TCPEstablished, TCPListen, TCPClosed)
    • 每一个子类都实现了和上下文相关的行为

3.8.6 协作

  • 上下文委托指定状态的请求给当前ConcreteState对象
  • 上下文可以传递它自己给state,来处理请求
  • 上下文是客户端的主要接口,客户端可以配置上下文状态对象。一旦配置了上下文,它的客户端就不必直接处理State对象
  • Context或ConcreteState子类都可以决定在什么情况下哪个状态继承另一个状态

3.8.7 效果

  • 本地化特定状态行为,并为不同的状态划分行为
    • 新state通过添加新的子类可以很容易添加
  • 使状态转换显式
  • 状态对象可以被分享

3.8.8 实现

  • 谁定义状态转变?
    • state的子类指定转换,则更加灵活。这需要context设置一个接口,让state对象显式地设置context的当前状态
  • 基于表的替代方案
  • 创建和销毁状态对象
    • 方式一,需要state时才创建并随后销毁。状态不经常改变时使用
    • 方式二,提前创建他们且始终不销毁。状态改变频繁时使用
  • 使用动态继承

3.9 策略模式

3.9.1 目的

定义一系列算法,并封装他们,使他们使可交换的

策略模式使算法独立于客户端使用

3.9.2 动机

文本流中”换行“策略中的问题

3.9.3 适用性

  • 许多相关的类只是在行为上有所不同,策略模式提供一个方法,使用多个行为中的一个去配置类
  • 你需要不同的算法变量
  • 算法使用客户端不应该知道的数据,使用Strategy模式可以避免暴露复杂的、特定于算法的数据结构
  • 一个类定义了许多行为,这些行为在它的操作中表现为多个条件语句,将相关的条件分支移到它们自己的Strategy类中,而不是许多条件

3.9.4 结构

在这里插入图片描述

3.9.5 参与者

  • 策略接口Strategy,(例,Compositor)
    • 声明对所有支持的算法通用的接口,context使用这个接口去调用通过ConcreteStrategy定义的算法
  • 具体策略类ConcreteStrategy,(例,SimpleCompositor, TeXCompositor,Array Compositor)
    • 使用策略接口实现算法
  • 上下文Context,(例,Composition)
    • 由具体的策略对象组成
    • 维护一个策略对象的引用
    • 定一个让Strategy访问它的数据的接口

3.9.6 协作

  • Strategy和Context交互,实现选择的算法。当调用算法时,Context可以将算法所需的所有数据传递给Strategy。或者,Context可以将自身作为参数传递给Strategy操作。这使得Strategy可以根据需要回调Context
  • Context将来自其客户端的请求转发到其Strategy。客户端通常创建一个ConcreteStrategy对象并将其传递给Context。客户端只和Context交互

3.9.7 效果

  • 相关算法集
    • 策略类的层次结构定义了一系列算法或行为,以供上下文重用
    • 继承可以帮助分解出算法的公共功能
  • 子类的替代物
    • 将算法封装在单独的Strategy类中,使您可以独立于上下文更改算法,从而使其更易于切换、理解和扩展。
  • 消除了条件语句
    • 包含许多条件语句的代码通常表明需要应用Strategy模式
  • 实现的选择
    • 策略可以提供相同行为的不同实现。
  • 客户端必须了解不同的策略
    • 这种模式有一个潜在的缺点,即客户必须先了解策略的不同,然后才能选择合适的策略
    • 因此,只有当行为变化与客户端相关时,您才应该使用策略模式
  • 策略和上下文之间的通讯开销
  • 增加对象的数量

3.9.8 实现

  1. 定义Strategy和Context接口
    1. 使ConcreteStrategy从context有效访问数据,反之亦然
    2. 一种是context以参数形式把数据传递给Strategy,但是这样context可能传递Strategy不需要的数据
    3. 另一种是将context本身作为参数传递,Strategy显式地从context请求数据。或者,该策略可以存储对其上下文的引用
  2. 策略作为模板参数
  3. 使策略对象可选
    1. 没有策略对象,context类可以被简化
    2. Context在访问策略对象之前检查它是否有一个策略对象。如果有,那么Context会正常使用它。如果没有策略,那么Context执行默认行为

3.10 模板方法模式

3.10.1 目的

在操作中定义算法的框架,将一些步骤推迟到子类,在不改变算法框架的情况下,子类重新定义算法的步骤

3.10.2 动机

3.10.3 适用性

  • 实现算法不变的部分,并交给子类去实现可改变的行为
  • 将子类的公共代码提取到公共部分,避免重复代码
  • 控制子类扩展

3.10.4 结构

在这里插入图片描述

3.10.5 参与者

  • 抽象类(Application),
    • 定义抽象的基本操作(子类应该去实现算法的)
    • 定义模版方法的算法框架
  • 具体类(MyApplication),
    • 实现基本操作,以执行特定于子类的步骤这个算法。

3.10.6 协作

具体类依赖于抽象类,实现不变的算法步骤

3.10.7 效果

代码重用的基本技巧

3.10.8 实现

三个实现问题需要注意:

  • 使用访问控制 ,可以将方法调用声明为受保护成员
  • 最小化进本操作,模板设计的一个重要目标方法的目的是尽量减少子类的基本操作次数必须重写以充实算法。需要的操作越多重写,对客户来说越繁琐
  • 命名约定,您可以通过在其名称中添加前缀来标识应该被重写的操作

3.11 访问者模式

3.11.1 目的

表示要对对象结构的元素执行的操作。Visitor允许您定义一个新操作,而无需更改其操作的元素的类。

3.11.2 动机

将程序表示为抽象语法树的编译器

通过将每个类中的相关操作打包到一个单独的类中,我们可以同时拥有这两个类对象,称为访问者

3.11.3 适用性

  • 对象结构包含许多具有不同接口的对象类;你想在这些对象上执行操作依赖于它们具体类。
  • 需要对对象执行许多不同且不相关的操作对象结构,并且您希望避免用这些“污染”它们的类操作
  • 定义对象结构的类很少改变,但你经常需要在结构上定义新的操作

3.11.4 结构

在这里插入图片描述

3.115 参与者

  • 访问者(NodeVisitor)
    • 声明一个访问操作
  • 具体访问者(TypeCheckingVisitor)
    • 实现每一个访问者声明的操作
  • 元素(Node)
    • 定义一个让访问者作为 参数的接收操作
  • 具体元素(AssignmentNode,VariableRefNode)
    • 实现一个让访问者作为参数的接收操作
  • 对象结构(Program)

3.11.6 协作

3.11.7 效果

3.11.8 实现

http://www.lryc.cn/news/605486.html

相关文章:

  • 微软发布Microsoft Sentinel数据湖国际版
  • JVM之【Java虚拟机概述】
  • Python实现调整矩阵维度: view
  • 【13】大恒相机SDK C#开发 —— Fom1中实时处理的8个图像 实时显示在Form2界面的 pictureBox中
  • 磁盘坏道检测工具在美国服务器硬件维护中的使用规范
  • MVS相机+YOLO检测方法
  • 【03】大恒相机SDK C#开发 —— 回调采集图像,关闭相机
  • Java WEB技术-序列化和反序列化认识(SpringBoot的Jackson序列化行为?如何打破序列化过程的驼峰规则?如何解决学序列化循环引用问题?)
  • 学习笔记《区块链技术与应用》第三天 网络 难度
  • 详解分布式数据库缓存技术:高性能数据访问的基石
  • 如何在 macOS 上使用 dnsmasq 搭建本地 DNS 缓存/转发
  • 深度解析:基于Python构建的闲鱼自动化营销与信息发送机器人
  • IO流专题
  • linux运维学习第十三周
  • Linux 服务器性能优化:性能监控,系统性能调优,进程优先级,内核升级全解析
  • 前端框架Vue3(二)——Vue3核心语法之OptionsAPI与CompositionAPI与setup
  • AWS云安全审计终极实战指南:构建自动化安全防线
  • 数字化应急预案:构筑现代安全防线
  • Web3:在 VSCode 中使用 Vue 前端与已部署的 Solidity 智能合约进行交互
  • 从渠道渗透到圈层渗透:开源链动2+1模式、AI智能名片与S2B2C商城小程序的协同创新路径研究
  • 【09】大恒相机SDK C#开发 ——库函数 IntPtr ConvertToRGB24详细解释 及 示例
  • 【JEECG】JVxeTable表格拖拽排序功能
  • 动态规划Day5学习心得
  • python的异步、并发开发
  • (C++)C++类和类的方法(基础教程)(与Python类的区别)
  • C++提高编程学习--模板
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-50,(知识点:TCP/IP 模型)
  • 磁盘IO优先级控制对美国服务器存储子系统的调优验证
  • 02 基于sklearn的机械学习-KNN算法、模型选择与调优(交叉验证、朴素贝叶斯算法、拉普拉斯平滑)、决策树(信息增益、基尼指数)、随机森林
  • 【动态规划 | 多状态问题】动态规划求解多状态问题