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适配为NewInterface
    Adapter 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 Draft
    document.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 UnderReview
    document.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 Published
    document.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() {
    // 创建 Originator
    Originator originator;

    // 创建 Caretaker
    Caretaker caretaker;

    // 设置状态并保存 Momento
    originator.SetState("State1");
    caretaker.AddMemento(originator.CreateMemento());

    // 设置新状态并保存 Momento
    originator.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.mfbz.cn/a/331368.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STL常用容器—vector容器

STL常用容器—vector容器 vector基本概念容器的基本操作容器的常见方法容器迭代器&#xff08;遍历&#xff09;容器的插入与删除容器的嵌套及存放自定义数据容器的嵌套容器存放自定义数据 vector基本概念 功能&#xff1a; vector数据结构和数组非常相似&#xff0c;也称为单…

Chrome 开发者工具

Chrome 开发者工具 介绍控制面板时间线下载信息概要请求列表单个请求时间线优化时间线上耗时项 lighthouse 插件Performance&#xff08;性能指标&#xff09;Accessibility&#xff08;可访问性&#xff09;Best Practices&#xff08;最佳实践&#xff09;SEO&#xff08;搜索…

Iris微服务框架_golang web框架_完整示例Demo

Iris简介 Iris是一款Go语言中用来开发web应用的框架&#xff0c;该框架支持编写一次并在任何地方以最小的机器功率运行&#xff0c;如Android、ios、Linux和Windows等。该框架只需要一个可执行的服务就可以在平台上运行了。 Iris框架以简单而强大的api而被开发者所熟悉。iris…

寒武纪显卡实现softmax的pingpong流水并行

在上一篇文章添加链接描述中我们介绍了寒武纪显卡实现基本的softmax代码&#xff0c;这里我们借助于寒武纪的流水并行来编写进一步的策略。 pingpongGDRAM2NRAM流水 仅仅计算max和sum使用流水 我们先考虑不使用SRAM的流水&#xff0c;我们设置两个NRAM上的长度为maxNum上的数…

STM32标准库开发——USART串口外设

USART外设介绍 USART (Universal Synchronous/AsynchronousReceiver/Transmitter&#xff09;通用同步/异步收发器USART是STM32内部集成的硬件外设&#xff0c;可根据数据寄存器的一个字节数据自动生成数据帧时序&#xff0c;从TX引脚发送出去&#xff0c;也可自动接收RX引脚的…

WebGL中开发AR应用

WebGL在本质上是用于在浏览器中进行3D和2D图形渲染的技术&#xff0c;而增强现实&#xff08;AR&#xff09;通常需要与现实世界的环境进行交互。要在WebGL中开发AR应用&#xff0c;您可以采取以下步骤&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专…

Arm Generic Interrupt Controller v3 and v4(GICv3v4)学习(一)

提示 该博客主要为个人学习&#xff0c;通过阅读官网手册整理而来&#xff08;个人觉得阅读官网的英文文档非常有助于理解各个IP特性&#xff09;。若有不对之处请参考参考文档&#xff0c;以官网参考文档为准。 Arm Generic Interrupt Controller v3 and v4学习一共分为三章&…

RHEL8 Samba服务器详细配置用户模式

任务&#xff1a; 配置server01为samba服务器&#xff0c;samba服务器的/companydata/sales为共享目录&#xff0c;共享名为sales&#xff0c;里面创建测试文件test_share.tar&#xff0c;创建用户组sales&#xff0c;创建组内用户sale1&#xff0c;要求配置用户模式访问&#…

Uniapp多选Popup(弹出层)

uniapp中多选组件很少&#xff0c;故个人简单开发了一个&#xff0c;可简单使用&#xff0c;也可根据个人需求稍微改进 支持的功能 单选多选&#xff08;默认&#xff09;限制选择数量默认选中禁用选项 属性说明 属性默认值说明singlefalsetrue为开启单选&#xff0c;否则为…

无需信用卡注册美区Apple ID指南

第一步 准备工作 1、一个没有注册过AppleID的邮箱&#xff0c;建议最好是Gmail邮箱 2、一个苹果手机&#xff0c;当然这个是必须的 3、需要科学上网 第二步 苹果网站注册 为了避免cookie的干扰&#xff0c;最好是在无痕模式下打开以上网页&#xff0c;创建你的AppleID&#…

rabbitmq-java基础详解

一、rabbitmq是什么&#xff1f; 1、MQ定义 MQ&#xff08;Message Queue&#xff09;消息队列 主要解决&#xff1a;异步处理、应用解耦、流量削峰等问题&#xff0c;是分布式系统的重要组件&#xff0c;从而实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性的架…

Spring+SpringMVC+Mybatis进行项目的整合

Spring SpringMVCM Mybatis 整合 一、 通过idea创建maven工程 二、 引入依赖项以及导入mybatis逆向工程的插件 将如下的文件替换所在工程的pom文件 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4…

HCIA的访问控制列表ACL

ACL -----access control-list 允许/拒绝 ACL作用&#xff1a; 1.实现访问控制 2.定义感兴趣流量 ACL分类&#xff1a; 标准ACL 2000-2999&#xff08;只关注源IP地址&#xff0c;使用时应该尽量靠近目标&#xff09; 扩展ACL 3000-3999&#xff1a;写ACL不能写在源上&…

反射计数 - 华为OD统一考试

OD统一考试 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 给定一个包含 0 和 1 的二维矩阵, 给定一个初始位置和速度。 一个物体从给定的初始位置触发, 在给定的速度下进行移动, 遇到矩阵的边缘则发生镜面反射无论物体经过 0 还是 1&#xff0c;都不…

2024美赛数学建模思路 - 案例:异常检测

文章目录 赛题思路一、简介 -- 关于异常检测异常检测监督学习 二、异常检测算法2. 箱线图分析3. 基于距离/密度4. 基于划分思想 建模资料 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 一、简介 – 关于异常…

宠物空气净化器真的有用吗?五款猫用宠物空气净化器测评!

作为一个养猫四年的铲屎官&#xff0c;我不得不说&#xff0c;宠物空气净化器是21世纪养猫人最伟大的神器之一&#xff01; 当我刚开始养猫的时候&#xff0c;我并没有意识到猫毛会成为一个如此头疼的问题。虽然朋友们告诉我要做好心理准备&#xff0c;但我并没有想到家里的猫毛…

Apache Zeppelin学习记录2

Apache Zeppelin学习记录2 文章目录 Apache Zeppelin学习记录2前言一、基础调用二、带参数调用1.代码块要增加一行z.textbox("folder_path", "input")2.读取result 总结 前言 上一章讲了如何使用zeppelin来接入python&#xff0c;本节我们来看看如何使用R…

ArcGIS初始化软件界面Normal.mxt

ArcGIS有时候永久了&#xff0c;或者呢突然不自觉软件界面乱了&#xff0c;或者一些窗口打开却找不到&#xff01; 这时候可以去删除arcgis的界面配置文件&#xff0c;Normal.mxt 删除后再打开软件&#xff0c;软件界面就会回到初始化设置了&#xff01; 文件所在的路径&…

3d音响按键怎么建立模型---模大狮模型网

要建立3D音响按键的模型&#xff0c;您可以按照以下步骤进行&#xff1a; 选择建模软件&#xff1a;首先&#xff0c;选择一个三维建模软件&#xff0c;如Blender、3ds Max或Maya。这些软件都提供了丰富的建模工具和功能&#xff0c;适合用于创建复杂的三维模型。 参考图像&am…

IPv6自动隧道---ISATAP隧道

ISATAP隧道 ISATAP(Intra-Site Automatic Tunnel Addressing Protocol)是另外一种自动隧道技术。ISATAP隧道同样使用了内嵌IPv4地址的特殊IPv6地址形式,只是和6to4不同的是,6to4是使用IPv4地址做为网络前缀,而ISATAP用IPv4地址做为接口标识。 站点内自动隧道寻址协议(I…
最新文章