C++设计模式(李建忠)笔记3

C++设计模式(李建忠)

本文是学习笔记,如有侵权,请联系删除。

参考链接

Youtube: C++设计模式

Gtihub源码与PPT:https://github.com/ZachL1/Bilibili-plus

豆瓣: 设计模式–可复用面向对象软件的基础

文章目录

  • C++设计模式(李建忠)
    • 14 外观模式(Facade)
      • Motivation
      • Facade模式定义
      • Facade结构
    • 15 代理模式(Proxy)
      • Motivation
      • Proxy模式定义
      • Proxy结构
    • 16 适配器(Adapter)
      • Motivation
      • 适配器模式定义
      • 适配器模式结构
    • 17 中介者(Mediator)
      • Motivation
      • Mediator定义
      • Mediator结构
      • 代码示例
      • Mediator和Facade的区别
    • 18 状态模式(State)
      • Motivation
      • 状态模式定义
      • 状态模式结构
      • 代码举例
    • 备忘录模式(Memento)
      • Motivation
      • Momento定义
      • Momento结构
    • 后记

“接口隔离”模式

在组件构建过程中,某些接口之间直接的依赖常常会带来很多问题、甚至根本无法实现。采用添加一层间接(稳定)接口,来隔离本来互相紧密关联的接口是一种常见的解决方案。

典型模式

·Façade

·Proxy

·Adapter

·Mediator

14 外观模式(Facade)

Motivation

下图中左边方案的问题在于组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。

如何简化外部客户程序和系统间的交互接口?如何将外部客户程 序的演化和内部子系统的变化之间的依赖相互解耦?

在这里插入图片描述

将一个系统划分成为若干个子系统有利于降低系统的复杂性。一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小。达到该目标的途径之一是就是引入一个 外观(facade)对象,它为子系统中较一般的设施提供了一个单一而简单的界面。

例如有一个编程环境,它允许应用程序访问它的编译子系统。这个编译子系统包含了若干个类,如 Scanner、Parser、ProgramNode、BytecodeStream 和 ProgramNodeBuilder,用于实现这一编译器。有些特殊应用程序需要直接访问这些类,但是大多数编译器的用户并不关心语法分析和代码生成这样的细节;他们只是希望编译一些代码。对这些用户,编译子系统中那些功能强大但层次较低的接口只会使他们的任务复杂化。

为了提供一个高层的接口并且对客户屏蔽这些类,编译子系统还包括一个 Compiler 类。这个类定义了一个编译器功能的统一接口。 Compiler 类是一个外观,它给用户提供了一个单一而简单的编译子系统接口。它无需完全隐藏实现编译功能的那些类,即可将它们结合在一起。编译器的外观可方便大多数程序员使用,同时对少数懂得如何使用底层功能的人,它并不隐藏这些功能,如下图所示。

在这里插入图片描述

Facade模式定义

为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

Facade结构

在这里插入图片描述

参与者
• Facade (Compiler)
— 知道哪些子系统类负责处理请求。
— 将客户的请求代理给适当的子系统对象。
• Subsystem classes (Scanner、Parser、ProgramNode 等)
— 实现子系统的功能。
— 处理由 Facade 对象指派的任务。
— 没有 facade 的任何相关信息;即没有指向 facade 的指针。

协作
• 客户程序通过发送请求给 Facade 的方式与子系统通讯, Facade 将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但 Facade 模式本身也必须将它的接口转换成子系统的接口。
• 使用 Facade 的客户程序不需要直接访问子系统对象。

要点总结

从客户程序的角度来看,Façade模式简化了整个组件系统的接口, 对于组件内部与外部客户程序来说,达到了一种“解耦”的效 果——内部子系统的任何变化不会影响到Façade接口的变化。

Façade设计模式更注重从架构的层次去看整个系统,而不是单个类的层次。Façade很多时候更是一种架构设计模式。

Façade设计模式并非一个集装箱,可以任意地放进任何多个对象。Facade模式中组件的内部应该是“相互耦合关系比较大的一系列 组件”,而不是一个简单的功能集合。

15 代理模式(Proxy)

Motivation

在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者、或者系统结构带来很多麻烦。

如何在不失去透明操作对象的同时来管理/控制这些对象特有的复 杂性?增加一层间接层是软件开发中常见的解决方式。

代码示例

class ISubject{
public:virtual void process();
};//Proxy的设计
class SubjectProxy: public ISubject{public:virtual void process(){//对RealSubject的一种间接访问//....}
};class ClientApp{ISubject* subject;public:ClientApp(){subject=new SubjectProxy();}void DoTask(){//...subject->process();//....}
};

Proxy模式定义

为其他对象提供一种代理以控制对这个对象的访问。

Proxy结构

在这里插入图片描述

参与者
• Proxy
— 保存一个引用使得代理可以访问实体。若 RealSubject 和 Subject 的接口相同, Proxy 会引用 Subject。
— 提供一个与 Subject 的接口相同的接口,这样代理就可以用来替代实体。
— 控制对实体的存取,并可能负责创建和删除它。

• Subject
— 定义 RealSubject 和 Proxy 的共用接口,这样就在任何使用 RealSubject 的地方都可以使用Proxy。

• RealSubject
— 定义 Proxy 所代表的实体。

chatGPT给出的proxy例子

以下是一个简单的 C++ 代理模式的例子,其中实现了一个图片加载的代理。

#include <iostream>
#include <string>// Subject 接口
class Image {
public:virtual void display() const = 0;
};// RealSubject 类
class RealImage : public Image {
private:std::string filename;public:RealImage(const std::string& filename) : filename(filename) {loadImage();}void loadImage() {std::cout << "Loading image: " << filename << std::endl;}void display() const override {std::cout << "Displaying image: " << filename << std::endl;}
};// Proxy 类
class ImageProxy : public Image {
private:RealImage* realImage;std::string filename;public:ImageProxy(const std::string& filename) : realImage(nullptr), filename(filename) {}void display() const override {if (realImage == nullptr) {realImage = new RealImage(filename);}realImage->display();}
};// Client 代码
int main() {// 使用代理加载图片,实际图片加载在需要显示时进行Image* image = new ImageProxy("sample.jpg");// 第一次显示图片,会触发加载image->display();// 第二次显示图片,直接使用已加载的图片image->display();return 0;
}

在这个例子中,Image 是代理模式的 Subject 接口,RealImageRealSubject 类,负责实际加载和显示图片。ImageProxy 是代理类,用于延迟加载和控制对 RealImage 对象的访问。客户端通过代理类使用图片,代理类在需要时创建或使用实际图片对象。

分布式系统中代理模式的使用

在分布式系统中,代理模式被广泛应用以解决各种问题。以下是一些在分布式系统中常见的代理模式应用:

  1. 远程代理(Remote Proxy): 远程代理用于在不同的地址空间中代表对象。在分布式系统中,对象可能存在于不同的节点或服务器上,远程代理允许客户端通过代理访问远程对象,就像访问本地对象一样。

  2. 虚拟代理(Virtual Proxy): 虚拟代理用于延迟对象的创建和初始化,特别是在对象很大或需要耗费大量资源的情况下。在分布式系统中,虚拟代理可以用于延迟加载远程对象,避免一开始就加载所有的远程资源。

  3. 安全代理(Protection Proxy): 安全代理用于控制对对象的访问,包括对某些操作的权限验证。在分布式系统中,安全代理可以用于对远程服务的访问进行身份验证和权限控制。

  4. 缓存代理(Caching Proxy): 缓存代理用于缓存一些开销较大的操作的结果,以提高性能。在分布式系统中,可以使用缓存代理来缓存远程服务的响应,减少不必要的网络开销。

  5. 负载均衡代理(Load Balancing Proxy): 负载均衡代理用于分发请求到多个服务器,以平衡系统的负载。在分布式系统中,负载均衡代理可以用于在多个节点之间分发请求,提高系统的整体性能和可扩展性。

  6. 日志记录代理(Logging Proxy): 日志记录代理用于记录对象的操作,以便进行调试、分析或审计。在分布式系统中,日志记录代理可以用于记录远程服务的请求和响应,帮助排查问题和监控系统状态。

这些代理模式的应用有助于提高分布式系统的灵活性、安全性、性能和可维护性。代理模式在分布式系统中的使用可以根据具体的需求和架构进行灵活选择和组合。

要点总结

“增加一层间接层”是软件系统中对许多复杂问题的一种常见解 决方法。在面向对象系统中,直接使用某些对象会带来很多问题, 作为间接层的proxy对象便是解决这一问题的常用手段。

具体proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如copy-on-write技术,有些可能对组件模块提供抽象代理层,在架构层次对对象做proxy。

Proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。

16 适配器(Adapter)

Motivation

在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。

如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?

代码示例

//目标接口(新接口)
class ITarget{
public:virtual void process()=0;
};//遗留接口(老接口)
class IAdaptee{
public:virtual void foo(int data)=0;virtual int bar()=0;
};//遗留类型
class OldClass: public IAdaptee{//....
};//对象适配器
class Adapter: public ITarget{ //继承
protected:IAdaptee* pAdaptee;//组合public:Adapter(IAdaptee* pAdaptee){this->pAdaptee=pAdaptee;}virtual void process(){int data=pAdaptee->bar();pAdaptee->foo(data);        }     
};//类适配器
class Adapter: public ITarget,protected OldClass{ //多继承}int main(){IAdaptee* pAdaptee=new OldClass();ITarget* pTarget=new Adapter(pAdaptee);pTarget->process();    
}class stack{deque container;};class queue{deque container;};

适配器模式定义

将一个类的接口转换成客户希望的另外一个接口。 Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适配器模式结构

在这里插入图片描述

参与者
• Target
— 定义 Client 使用的与特定领域相关的接口。

• Client
— 与符合 Target 接口的对象协同。

• Adaptee
— 定义一个已经存在的接口,这个接口需要适配。以前的、遗留的接口。

• Adapter
— 对 Adaptee 的接口与 Target 接口进行适配。

协作
• Client 在 Adapter 实例上调用一些操作。接着适配器调用 Adaptee 的操作实现这个请求。

要点总结

Adapter模式主要应用于“希望复用一些现存的类,但是接口又与复用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。

GoF23定义了两种Adapter模式的实现结构:对象适配器和类适配器。但类适配器采用多继承”的实现方式,一般不推荐使用。对象适配器采用”对象组合”的方式,更符合松耦合精神。

Adapter模式可以实现的非常灵活,不必拘泥于Gof23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。

chatGPT给出的adapter例子

下面是一个简单的C++ Adapter 模式的例子,假设有一个旧的类 OldClass,其接口不符合新的需求,然后通过适配器 Adapter 将其适配为符合新需求的接口:

#include <iostream>// 旧的类,接口不符合新需求
class OldClass {
public:void legacyMethod() {std::cout << "Legacy method of OldClass" << std::endl;}
};// 新的接口,符合新需求
class NewInterface {
public:virtual void newMethod() = 0;virtual ~NewInterface() {}
};// 适配器,将OldClass适配为NewInterface
class Adapter : public NewInterface {
private:OldClass oldInstance;public:void newMethod() override {// 在这里调用OldClass的方法,以适应新的接口oldInstance.legacyMethod();}
};// 客户端代码,使用新的接口
void clientCode(NewInterface* newObject) {newObject->newMethod();
}int main() {// 使用适配器将OldClass适配为NewInterfaceAdapter adapter;// 客户端代码使用新的接口clientCode(&adapter);return 0;
}

在这个例子中,OldClass 是一个旧的类,具有 legacyMethod 方法,但其接口不符合 NewInterface 的新需求。通过创建一个适配器类 Adapter,它继承了 NewInterface 并持有一个 OldClass 的实例,将 legacyMethod 适配为 newMethod。最后,在客户端代码中,我们可以使用 NewInterface 的接口,而实际上调用了 OldClass 的方法。这就是 Adapter 模式的作用。

17 中介者(Mediator)

Motivation

在软件构建过程中,经常会出现多个对象互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。

在这种情况下,我们可使用一个“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化。

Mediator定义

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

Mediator结构

在这里插入图片描述

参与者
• Mediator (中介者)
— 中介者定义一个接口用于与各同事(Colleague)对象通信。

• Concrete Mediator (具体中介者)
— 具体中介者通过协调各同事对象实现协作行为。
— 了解并维护它的各个同事。

• Colleague class (同事类)

— 每一个同事类都知道它的中介者对象。

— 每一个同事对象在需与其他的同事通信的时候,与它的中介者通信。

一个可能的对象结构:

在这里插入图片描述

代码示例

中介者模式是一种行为型设计模式,其主要目的是减少对象之间的直接通信,而是通过一个中介者对象进行协调。这可以降低对象之间的耦合度,使系统更易于维护和扩展。

在中介者模式中,有以下几个主要参与者:

  1. Mediator (中介者): 定义一个接口用于与各同事(Colleague)对象通信。中介者负责协调各同事之间的交互,通过中介者进行通信而不是直接相互引用。

  2. Concrete Mediator (具体中介者): 实现中介者接口,通过协调各同事对象来实现协作行为。具体中介者了解并维护它的各个同事。

  3. Colleague class (同事类): 每一个同事类都知道它的中介者对象,而不知道其他同事的存在。每一个同事对象在需要与其他同事通信的时候,通过它的中介者来实现通信。

中介者模式的典型应用场景是当一个系统中对象之间的交互复杂且对象之间相互依赖性较高时。通过引入中介者,可以将系统从多对多的关系简化为多对一的关系,降低了系统的复杂性。

以下是一个简单的中介者模式的示例,假设有一个聊天室系统:

#include <iostream>
#include <string>class Mediator;// 同事类
class Colleague {
protected:Mediator* mediator;public:Colleague(Mediator* mediator) : mediator(mediator) {}virtual void send(const std::string& message) = 0;virtual void receive(const std::string& message) = 0;
};// 具体同事类
class ConcreteColleague : public Colleague {
public:ConcreteColleague(Mediator* mediator) : Colleague(mediator) {}void send(const std::string& message) override {std::cout << "Sending message: " << message << std::endl;mediator->mediate(this, message);}void receive(const std::string& message) override {std::cout << "Received message: " << message << std::endl;}
};// 中介者接口
class Mediator {
public:virtual void mediate(Colleague* sender, const std::string& message) = 0;
};// 具体中介者
class ConcreteMediator : public Mediator {
private:Colleague* colleague1;Colleague* colleague2;public:void setColleague1(Colleague* colleague) {colleague1 = colleague;}void setColleague2(Colleague* colleague) {colleague2 = colleague;}void mediate(Colleague* sender, const std::string& message) override {if (sender == colleague1) {colleague2->receive(message);} else if (sender == colleague2) {colleague1->receive(message);}}
};int main() {ConcreteMediator mediator;ConcreteColleague colleague1(&mediator);ConcreteColleague colleague2(&mediator);mediator.setColleague1(&colleague1);mediator.setColleague2(&colleague2);colleague1.send("Hello from Colleague 1");colleague2.send("Hi from Colleague 2");return 0;
}

在这个例子中,Colleague 表示同事类的抽象,ConcreteColleague 是具体的同事类,Mediator 是中介者的抽象,而 ConcreteMediator 是具体的中介者。同事对象通过中介者来进行通信。

要点总结

将多个对象间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相关联”为“多个对象和一个中介者关联”,简化了系统的维护,抵御了可能的变化。

随着控制逻辑的复杂化,Mediator具体对象的实现可能相当复杂。这时候可以对Mediator对象进行分解处理。

Façade模式是解耦系统间(单向)的对象关联关系;Mediator模式是解耦系统内各个对象之间(双向)的关联关系。

Mediator和Facade的区别

中介者模式和外观模式(Facade 模式)是两种不同的设计模式,它们解决了不同的问题,并在实现上有一些区别。

  1. 中介者模式(Mediator Pattern):

    • 目的: 中介者模式的主要目的是通过减少对象之间的直接通信,来解耦对象之间的关系。中介者模式通过引入一个中介者对象,将对象之间的通信集中到中介者对象,从而降低对象之间的耦合度。
    • 实现: 中介者模式通常包括中介者接口和具体中介者类,同时每个对象都持有中介者的引用。对象通过中介者进行通信,而不直接与其他对象通信。
    • 例子: 聊天室是一个经典的中介者模式的例子,其中聊天室充当中介者,聊天室成员通过聊天室进行消息的发送和接收。
  2. 外观模式(Facade Pattern):

    • 目的: 外观模式的主要目的是提供一个简化的接口,用于访问系统的复杂子系统。外观模式通过引入一个外观类,将客户端与子系统的复杂性隔离,使客户端只需与外观类进行交互,而不需要直接了解子系统的细节。
    • 实现: 外观模式包括一个外观类,该类封装了子系统的接口,并为客户端提供一个简化的接口。客户端只需通过外观类来与系统交互,而不需要了解系统内部的复杂结构。
    • 例子: 电脑启动过程中的 BIOS、操作系统加载、应用程序启动等过程可以使用外观模式来简化客户端与这些复杂子系统的交互。

区别总结:

  • 目的不同: 中介者模式的主要目的是解耦对象之间的关系,降低耦合度;外观模式的主要目的是提供一个简化的接口,隔离客户端与子系统的复杂性。
  • 涉及对象数量: 中介者模式通常涉及多个对象之间的通信,而外观模式通常涉及对多个子系统的封装。
  • 关注点: 中介者模式关注对象之间的通信和解耦,而外观模式关注对复杂系统的简化接口。

在实际应用中,选择使用中介者模式还是外观模式取决于问题的性质和需求。

“状态变化”模式

在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模块的稳定? “状态变化” 模式为这一问题提供了一种解决方案。

典型模式

·State

·Memento

18 状态模式(State)

Motivation

在软件构建过程中,某些对象的状态如果改变,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。

如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?

考虑一个表示网络连接的类TCPConnection。一个TCPConnection对象的状态处于若干不同状态之一:连接已建立(Established)、正在监听(Listening)、连接已关闭(Closed)。当一个TCPConnection对象收到其他对象的请求时,它根据自身的当前状态作出不同的反应。例如,一个Open请求的结果依赖于该连接是处于连接已关闭状态还是连接已建立状态。State模式描述了TCPConnection如何在每一种状态下表现出不同的行为。

这一模式的关键思想是引入了一个称为TCPState的抽象类来表示网络的连接状态。TCPState类为各表示不同的操作状态的子类声明了一个公共接口。TCPState的子类实现与特定状态相关的行为。例如,TCPEstablished和TCPClosed类分别实现了特定于TCPConnection的连接已建立状态和连接已关闭状态的行为。

TCPConnection类维护一个表示TCP连接当前状态的状态对象(一个TCPState子类的实例)。TCPConnection类将所有与状态相关的请求委托给这个状态对象。TCPConnection使用它的TCPState子类实例来执行特定于连接状态的操作。

一旦连接状态改变,TCPConnection对象就会改变它所使用的状态对象。例如当连接从已建立状态转为已关闭状态时,TCPConnection会用一个TCPClosed的实例来代替原来的TCPEstablished的实例。

在这里插入图片描述

状态模式定义

允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

状态模式结构

在这里插入图片描述

参与者
• Context (环境,如 TCPConnection)
— 定义客户感兴趣的接口。
— 维护一个ConcreteState子类的实例,这个实例定义当前状态。

• State (状态,如 TCPState)
— 定义一个接口以封装与Context 的一个特定状态相关的行为。

• ConcreteState subclasses (具体状态子类,如 TCPEstablished, TCPListen, TCPClosed)
— 每一子类实现一个与Context的一个状态相关的行为。

代码举例

假设我们有一个简单的文档编辑器,其中文档可以处于三种状态:草稿(Draft)、审核中(UnderReview)和已发布(Published)。我们可以使用 State 模式来管理这些不同的文档状态。

首先,定义文档状态的抽象基类 DocumentState

#include <iostream>class Document;class DocumentState {
public:virtual void Edit(Document* document) = 0;virtual void Review(Document* document) = 0;virtual void Publish(Document* document) = 0;
};

接下来,实现具体的文档状态类:

class DraftState : public DocumentState {
public:void Edit(Document* document) override;void Review(Document* document) override;void Publish(Document* document) override;
};class UnderReviewState : public DocumentState {
public:void Edit(Document* document) override;void Review(Document* document) override;void Publish(Document* document) override;
};class PublishedState : public DocumentState {
public:void Edit(Document* document) override;void Review(Document* document) override;void Publish(Document* document) override;
};

然后,定义文档类 Document,该类维护当前文档的状态,并委托状态对象来处理文档的编辑、审核和发布操作:

class Document {
private:DocumentState* currentState;public:Document() : currentState(new DraftState()) {}void ChangeState(DocumentState* newState) {delete currentState;currentState = newState;}void Edit() {currentState->Edit(this);}void Review() {currentState->Review(this);}void Publish() {currentState->Publish(this);}~Document() {delete currentState;}
};

最后,实现具体的文档状态类的方法:

// Implementation of DraftState methods
void DraftState::Edit(Document* document) {std::cout << "Editing the document." << std::endl;
}void DraftState::Review(Document* document) {std::cout << "Reviewing is not applicable in Draft state." << std::endl;
}void DraftState::Publish(Document* document) {std::cout << "Publishing is not applicable in Draft state." << std::endl;
}// Implementation of UnderReviewState methods
void UnderReviewState::Edit(Document* document) {std::cout << "Editing is not allowed during review." << std::endl;
}void UnderReviewState::Review(Document* document) {std::cout << "Reviewing the document." << std::endl;
}void UnderReviewState::Publish(Document* document) {std::cout << "Publishing is not allowed during review." << std::endl;
}// Implementation of PublishedState methods
void PublishedState::Edit(Document* document) {std::cout << "Editing is not allowed for published documents." << std::endl;
}void PublishedState::Review(Document* document) {std::cout << "Reviewing is not allowed for published documents." << std::endl;
}void PublishedState::Publish(Document* document) {std::cout << "The document is already published." << std::endl;
}

在这个例子中,文档的状态包括草稿、审核中和已发布,而 Document 类委托给具体的状态对象来处理不同状态下的操作。这样,文档类的行为可以根据其当前状态的变化而动态改变。

下面是一个简单的 main 函数,演示了如何使用上述定义的文档类和状态类:

int main() {Document document;// Initial state is Draftdocument.Edit(); // Output: Editing the document.document.Review(); // Output: Reviewing is not applicable in Draft state.document.Publish(); // Output: Publishing is not applicable in Draft state.// Change state to UnderReviewdocument.ChangeState(new UnderReviewState());document.Edit(); // Output: Editing is not allowed during review.document.Review(); // Output: Reviewing the document.document.Publish(); // Output: Publishing is not allowed during review.// Change state to Publisheddocument.ChangeState(new PublishedState());document.Edit(); // Output: Editing is not allowed for published documents.document.Review(); // Output: Reviewing is not allowed for published documents.document.Publish(); // Output: The document is already published.return 0;
}

在这个 main 函数中,我们创建了一个文档对象,并在不同状态下调用了 EditReviewPublish 方法。通过改变文档的状态,我们可以看到文档对象的行为随之改变,符合状态模式的设计思想。

要点总结

State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换相应的对象;但同时维持State 的接口,这样实现了具体操作与状态转换之间的解耦。

为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的——即要么彻底转换过来,要么不转换。

如果State对象没有实例变量,那么各个上下文可以共享同一个State对象,从而节省对象开销。

以下是状态模式和策略模式之间的区别:

  1. 关注点:
    • 状态模式: 侧重于允许对象在其内部状态发生变化时更改其行为。它关注于表示对象的状态以及状态之间的转换。
    • 策略模式: 侧重于定义一组算法,封装每个算法,并使它们可以互换。它允许客户端选择适当的算法而无需更改客户端代码。
  2. 状态/策略转换的责任:
    • 状态模式: 状态之间的转换通常由上下文对象内部控制。上下文对象通过更改其内部状态来改变其行为。
    • 策略模式: 特定策略(算法)的选择通常由客户端或外部实体控制。客户端决定使用哪种策略,并可以在策略之间动态切换。

备忘录模式(Memento)

Motivation

在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。

如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏 对象本身的封装性。

代码举例

#include <iostream>
#include <string>// Memento(备忘录)类:用于存储 Originator 内部状态的快照
class Memento {
private:std::string state;  // 内部状态
public:Memento(const std::string& s) : state(s) {}  // 构造函数,初始化内部状态std::string getState() const { return state; }  // 获取内部状态void setState(const std::string& s) { state = s; }  // 设置内部状态
};// Originator(原发器)类:拥有需要存储的内部状态,并能够创建和恢复备忘录
class Originator {
private:std::string state;  // 内部状态
public:Originator() {}  // 默认构造函数Memento createMomento() {Memento m(state);  // 创建备忘录,保存当前内部状态return m;}void setMomento(const Memento& m) {state = m.getState();  // 从备忘录中恢复内部状态}
};int main() {Originator orginator;// 捕获对象状态,存储到备忘录Memento memento = orginator.createMomento();// ... 改变 orginator 状态// 从备忘录中恢复orginator.setMomento(memento);return 0;
}

Momento定义

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

Momento结构

在这里插入图片描述

参与者
• Memento (备忘录)
— 备忘录存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态。
— 防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口,管理者(caretaker)只能看到备忘录的窄接口—它只能将备忘录传递给其他对象。相反,原
发器能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。理想的情况是只允许生成本备忘录的那个原发器访问本备忘录的内部状态。
• Originator (原发器)
— 原发器创建一个备忘录,用以记录当前时刻它的内部状态。
— 使用备忘录恢复内部状态。
• Caretaker (负责人)
— 负责保存好备忘录。
— 不能对备忘录的内容进行操作或检查。

要点总结

备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。

Memento模式的核心是信息隐藏,即Originator需要向外接隐藏信息,保持其封装性。但同时又需要将状态保持到外界(Memento )。

由于现代语言运行时(如C#、Java等)都具有相当的对象序列化支持,因此往往采用效率较高、又较容易正确实现的序列化方案来实现Memento模式。

chatGPT给出的一个备忘录模式的例子

Momento 模式旨在捕获一个对象的内部状态,以便稍后可以将其恢复到此状态。以下是一个简单的 C++ 示例,演示 Momento 模式的基本概念:

#include <iostream>
#include <string>
#include <vector>// Momento:存储原发器的内部状态
class Memento {
public:Memento(const std::string& state) : state_(state) {}std::string GetState() const {return state_;}private:std::string state_;
};// Originator:创建并恢复到 Momento 的状态
class Originator {
public:Originator() : state_("") {}void SetState(const std::string& state) {state_ = state;std::cout << "Set state to: " << state << std::endl;}Memento CreateMemento() {return Memento(state_);}void RestoreMemento(const Memento& memento) {state_ = memento.GetState();std::cout << "Restored to state: " << state_ << std::endl;}private:std::string state_;
};// Caretaker:负责保存和恢复 Momento
class Caretaker {
public:void AddMemento(const Memento& memento) {momentos_.push_back(memento);}Memento GetMemento(int index) const {return momentos_[index];}private:std::vector<Memento> momentos_;
};int main() {// 创建 OriginatorOriginator originator;// 创建 CaretakerCaretaker caretaker;// 设置状态并保存 Momentooriginator.SetState("State1");caretaker.AddMemento(originator.CreateMemento());// 设置新状态并保存 Momentooriginator.SetState("State2");caretaker.AddMemento(originator.CreateMemento());// 恢复到先前状态originator.RestoreMemento(caretaker.GetMemento(0));return 0;
}

在此示例中,Originator 表示拥有内部状态的对象,Memento 表示保存状态的 Momento,而 Caretaker 负责管理 Momento。在主函数中,我们创建了 OriginatorCaretaker,并演示了如何设置状态、创建 Momento、保存 Momento、设置新状态以及通过 Momento 恢复到先前状态。

后记

截至2024年1月17日20点27分,完成Facade, Proxy, Adapter, Mediator, State, Memento模式的学习。后面还有6个模式需要跟进。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/630942.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nodejs 第三十二章(数据库)

MySQL是一种开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;它是最受欢迎的数据库系统之一。MySQL广泛用于Web应用程序和其他需要可靠数据存储的应用程序中。 以下是MySQL数据库的一些重要特点和概念&#xff1a; 数据库&#xff1a;MySQL是一个数据库…

龙腾荆楚 | 软件供应链安全检测中心落地襄阳

1月16日&#xff0c;襄阳市东津新区“园区提质、企业满园”行动暨2024年东津云谷首月重大项目集中签约活动圆满完成&#xff0c;开源网安城市级项目再下一城&#xff0c;分别与襄阳市政府、高校、国投签订战略合作协议&#xff0c;推动荆楚地区数字政府、数字经济、数字社会、数…

【MATLAB源码-第115期】基于matlab的QSM正交空间调制系统仿真,输出误码率曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 正交空间调制&#xff08;QSM&#xff09;是一种先进的无线通信技术&#xff0c;它通过利用发射端的多天线阵列来传输信息&#xff0c;从而提高了数据传输的效率和速率。这种技术的关键在于它使用天线阵列的空间特性来编码额…

情人节专属--html5 canvas制作情人节告白爱心动画特效

💖效果展示 💖html展示 <!doctype html> <html> <head> <meta charset=

maven 配置http私服Since Maven 3.8.1 http repositories are blocked. 报错处理

刷maven报错 com.saas:pdf:pom:0.0.1 failed to transfer from http://0.0.0.0/ during a previous attempt. This failure was cached in the local repository and resolution is not reattempted until the update interval of maven-default-http-blocker has elapsed or …

七陌API对接实战:外呼接口及通话记录推送

通过白码低代码开发平台对接七陌外呼接口&#xff0c;实现选择客户进行外呼&#xff0c;并保存通话记录的功能。 外呼接口实现&#xff1a; 官方接口文档&#xff1a;http://developer.7moor.com/v2docs/dialout/ 1、对接数据查询 向七陌商务索取到七陌用户中心账号密码&a…

7.5 MySQL对数据的增改删操作(❤❤❤)

7.5 MySQL对数据的基本操作 1. 提要2. 数据添加2.1 insert语法2.2 insert 子查询2.3 ignore关键字 3. 数据修改3.1 update语句3.2 update表连接 4. 数据删除4.1 delete语句4.2 delete表连接4.3 快速删除数据表全部数据 1. 提要 2. 数据添加 2.1 insert语法 2.2 insert 子查询 …

Python 一行命令部署http、ftp服务

Python 一行命令部署http服务 文章目录 Python 一行命令部署http服务具体操作命令如下浏览器返回下载Python 一行命令部署FTP服务 具体操作命令如下 这个比nginx相对来说更加简单&#xff0c;可以用于部署特殊场景时如银行等部署时&#xff0c;各种权限控制&#xff0c;内网之间…

使用宝塔面板部署后端项目到服务器

文章目录 前言第一步&#xff1a;安装数据库第二步&#xff1a;打包后端项目第三步&#xff1a;配置数据库第四步&#xff1a;部署后端项目第五步&#xff1a;前后端联调测试总结 前言 在之前我已经写了一篇如何去部署前端项目&#xff0c;虽然能访问网站&#xff0c;但是没有…

Kafka 消息不能正常消费问题排查

订单宽表数据不同步 事情的起因是专员在 ze app 上查不到订单了&#xff0c;而订单数据是从 mysql 的 order_search_info 查询的&#xff0c;order_search_info 表的数据是从 oracel 的 BZ_ORDER_INFO 表同步过来的&#xff0c;查不到说明同步有问题 首先重启&#xff0c;同步…

Modelsim SE 10.5安装教程

ModelSim 是一种功能强大的硬件描述语言 (HDL&#xff0c;Hardware Description Language) 仿真和验证工具&#xff0c;可以单独仿真&#xff0c;也可以联合Quartus/Vivado等软件联合仿真&#xff0c;仿真速度快&#xff0c;广泛应用于数字电路设计和验证领域。 大学老师爱教VH…

图像识别,很强,专业以图搜图小软件!

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; ImageSearch v1.12最新版是一款功能强大且免费开源的工具&#xff0c;专注于通过算法实现本地电脑的图像搜索功能&#xff0c;无需依赖AI技术。它的操…

DP读书:《openEuler操作系统》(七)FSCK与VFS虚拟文件系统

10min速通FSCK、原子操作与VFS 文件系统检查器1.检查inode表1) 遍历所有inode2) 修复多次引用数据块 2.检查目录结构3.检查目录的连接1) 检查根目录确保存在2) 遍历所有目录的inode,有问题的连接到/lostfound 4.检查引用次数5.检查位图一致性 日志1.主要的数据结构1) 原子操作描…

一文了解GeoTrust SSL证书

在当今互联网的高度连接世界中&#xff0c;确保网站安全性至关重要。SSL证书是保护网站和用户数据的关键组成部分。GeoTrust证书在SSL证书市场上享有盛誉&#xff0c;被许多网站所有者和企业所信赖。JoySSL将深入探讨GeoTrust证书的特点&#xff0c;帮助大家了解该品牌并做出更…

lua使用resty.http做nginx反向代理(https请求,docker容器化部署集群),一个域名多项目转发

下载使用 链接&#xff1a;https://pan.baidu.com/s/1uQ7yCzQsPWsF6xavFTpbZg 提取码&#xff1a;htay –来自百度网盘超级会员V5的分享 在根目录下执行: # 从 github 上下载文件 git clone https://github.com/ledgetech/lua-resty-http.git # 将 lua-resty-http/lib/ 下的 r…

计算机网络——数据链路层-媒体接入控制-静态划分信道(频分复用FDM、时分复用TDM、波分复用WDM、码分复用CDM)

目录 频分复用FDM 时分复用TDM 波分复用WDM 码分复用CDM 练习1 码分多址的应用举例 练习2 本篇我们介绍媒体接入控制的其中一类方法——静态划分信道 首先介绍信道复用的基本概念&#xff0c; 复用&#xff08;Multiplexing&#xff09;是通信技术中的一个重要概念&a…

SpringBoot中整合MybatisPlus快速实现Mysql增删改查和条件构造器

场景 Mybatis-Plus(简称MP)是一个Mybatis的增强工具&#xff0c;只是在Mybatis的基础上做了增强却不做改变&#xff0c;MyBatis-Plus支持所有Mybatis原生的特性&#xff0c; 所以引入Mybatis-Plus不会对现有的Mybatis构架产生任何影响。MyBatis 增强工具包&#xff0c;简化 C…

强化学习入门

强化学习是指智能体通过不断试错的方式进行学习&#xff0c;利用与环境进行交互时获得的奖励或惩罚来指导行为 试错学习 尝试&#xff08;决策-decision&#xff09;错误结果&#xff1a;每次尝试无论产生什么样的结果&#xff0c;都会对下一次结果产生影响 奖励&#xff08;…

Springboot 子工程构建完后无法找到springboot依赖

问题: 构建完子工程后无法找到SpringBootTest 解决方案: 最好用这个构建 https://www.cnblogs.com/he-wen/p/16735239.html 1.先观察项目目录 是否正确 2.观察子工程目录 3.看pom.xml中是否引用springboot依赖 4.检查代码 查看父项目是否包含子模块 查看子模块的父项目是否…

vscode 中配置 python 虚拟环境

vscode 中配置 python 虚拟环境 Start 在编写代码的过程中&#xff0c;我们经常会用到一些第三方依赖&#xff0c;帮助我们快速完成功能。在 Python 中&#xff0c;默认情况都是统一安装在全局环境中&#xff0c;但是这样伴随着电脑项目越来越多&#xff0c;不同项目对依赖的…