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
接口,RealImage
是 RealSubject
类,负责实际加载和显示图片。ImageProxy
是代理类,用于延迟加载和控制对 RealImage
对象的访问。客户端通过代理类使用图片,代理类在需要时创建或使用实际图片对象。
分布式系统中代理模式的使用
在分布式系统中,代理模式被广泛应用以解决各种问题。以下是一些在分布式系统中常见的代理模式应用:
-
远程代理(Remote Proxy): 远程代理用于在不同的地址空间中代表对象。在分布式系统中,对象可能存在于不同的节点或服务器上,远程代理允许客户端通过代理访问远程对象,就像访问本地对象一样。
-
虚拟代理(Virtual Proxy): 虚拟代理用于延迟对象的创建和初始化,特别是在对象很大或需要耗费大量资源的情况下。在分布式系统中,虚拟代理可以用于延迟加载远程对象,避免一开始就加载所有的远程资源。
-
安全代理(Protection Proxy): 安全代理用于控制对对象的访问,包括对某些操作的权限验证。在分布式系统中,安全代理可以用于对远程服务的访问进行身份验证和权限控制。
-
缓存代理(Caching Proxy): 缓存代理用于缓存一些开销较大的操作的结果,以提高性能。在分布式系统中,可以使用缓存代理来缓存远程服务的响应,减少不必要的网络开销。
-
负载均衡代理(Load Balancing Proxy): 负载均衡代理用于分发请求到多个服务器,以平衡系统的负载。在分布式系统中,负载均衡代理可以用于在多个节点之间分发请求,提高系统的整体性能和可扩展性。
-
日志记录代理(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 (同事类)
— 每一个同事类都知道它的中介者对象。
— 每一个同事对象在需与其他的同事通信的时候,与它的中介者通信。
一个可能的对象结构:
代码示例
中介者模式是一种行为型设计模式,其主要目的是减少对象之间的直接通信,而是通过一个中介者对象进行协调。这可以降低对象之间的耦合度,使系统更易于维护和扩展。
在中介者模式中,有以下几个主要参与者:
-
Mediator (中介者): 定义一个接口用于与各同事(Colleague)对象通信。中介者负责协调各同事之间的交互,通过中介者进行通信而不是直接相互引用。
-
Concrete Mediator (具体中介者): 实现中介者接口,通过协调各同事对象来实现协作行为。具体中介者了解并维护它的各个同事。
-
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 模式)是两种不同的设计模式,它们解决了不同的问题,并在实现上有一些区别。
-
中介者模式(Mediator Pattern):
- 目的: 中介者模式的主要目的是通过减少对象之间的直接通信,来解耦对象之间的关系。中介者模式通过引入一个中介者对象,将对象之间的通信集中到中介者对象,从而降低对象之间的耦合度。
- 实现: 中介者模式通常包括中介者接口和具体中介者类,同时每个对象都持有中介者的引用。对象通过中介者进行通信,而不直接与其他对象通信。
- 例子: 聊天室是一个经典的中介者模式的例子,其中聊天室充当中介者,聊天室成员通过聊天室进行消息的发送和接收。
-
外观模式(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
函数中,我们创建了一个文档对象,并在不同状态下调用了 Edit
、Review
和 Publish
方法。通过改变文档的状态,我们可以看到文档对象的行为随之改变,符合状态模式的设计思想。
要点总结
State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换相应的对象;但同时维持State 的接口,这样实现了具体操作与状态转换之间的解耦。
为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的——即要么彻底转换过来,要么不转换。
如果State对象没有实例变量,那么各个上下文可以共享同一个State对象,从而节省对象开销。
以下是状态模式和策略模式之间的区别:
- 关注点:
- 状态模式: 侧重于允许对象在其内部状态发生变化时更改其行为。它关注于表示对象的状态以及状态之间的转换。
- 策略模式: 侧重于定义一组算法,封装每个算法,并使它们可以互换。它允许客户端选择适当的算法而无需更改客户端代码。
- 状态/策略转换的责任:
- 状态模式: 状态之间的转换通常由上下文对象内部控制。上下文对象通过更改其内部状态来改变其行为。
- 策略模式: 特定策略(算法)的选择通常由客户端或外部实体控制。客户端决定使用哪种策略,并可以在策略之间动态切换。
备忘录模式(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。在主函数中,我们创建了 Originator
和 Caretaker
,并演示了如何设置状态、创建 Momento、保存 Momento、设置新状态以及通过 Momento 恢复到先前状态。
后记
截至2024年1月17日20点27分,完成Facade, Proxy, Adapter, Mediator, State, Memento模式的学习。后面还有6个模式需要跟进。
相关文章:

C++设计模式(李建忠)笔记3
C设计模式(李建忠) 本文是学习笔记,如有侵权,请联系删除。 参考链接 Youtube: C设计模式 Gtihub源码与PPT:https://github.com/ZachL1/Bilibili-plus 豆瓣: 设计模式–可复用面向对象软件的基础 文章目录 C设计模…...
计算机考研408的准备
计算机考研408的准备 一:专硕和学硕 计算机的学硕叫做计算机科学与技术,而计算机的专硕叫计算机技术。这么区分的意义就在于我们的就业形势和科研形式。 二:就业形势 由于本科的严重扩招以及课程设置的问题,相当大量的人在毕业…...

2.【Linux】(进程的状态||深入理解fork||底层剖析||task_struct||进程优先级||并行和并发||详解环境变量)
一.进程 1.进程调度 Linux把所有进程通过双向链表的方式连接起来组成任务队列,操作系统和cpu通过选择一个task_struct执行其代码来调度进程。 2.进程的状态 1.运行态:pcb结构体在运行或在运行队列中排队。 2.阻塞态:等待非cpu资源就绪&am…...
【管理篇 / 升级】❀ 13. FortiOS 7.4固件升级新规则 ❀ FortiGate 防火墙
【简介】飞塔防火墙的固件升级一直是所有厂家中最好的。只要有注册官方帐号,有注册设备,并且只要有一台设备在服务期内,即可下载所有型号的所有版本的固件。即使其它设备服务期已过,也可以通过固件文件手动升级,避免防…...

【前端】vue.js从入门到项目实战笔记
文章目录 第三章3.1 插值绑定({{}}, v-html)3.1.1 文本插值3.1.2 HTML插值 3.2 属性绑定 v-bind3.2.1 指令v-bind3.2.3 类名和样式绑定 【前端目录贴】 第三章 3.1 插值绑定({{}}, v-html) 文本插值中的代…...

flex布局(3)
九、骰子 *{margin:0;padding: 0;box-sizing: border-box; } .flex{display: flex;flex-flow: row wrap;justify-content: space-between;align-items: center;align-content: space-between;padding:20px; } .touzi{width: 120px;height: 120px;background-color: aliceblue;…...

JVM知识总结
1.概述 JVM指的是Java虚拟机,本质上是一个运行在计算机上的程序,他的职责是运行Java字节码文件,作用是为了支持跨平台特性。 功能: 装载字节码,解释/编译为机器码 管理数据存储和垃圾回收 优化热点代码提升效率 …...

读书笔记-《数据结构与算法》-摘要8[桶排序]
桶排序和归并排序有那么点点类似,也使用了归并的思想。大致步骤如下: 设置一个定量的数组当作空桶。Divide - 从待排序数组中取出元素,将元素按照一定的规则塞进对应的桶子去。对每个非空桶进行排序,通常可在塞元素入桶时进行插入…...

【STM32调试】寄存器调试不良问题记录持续版
STM32寄存器调试不良问题记录 NVIC(内嵌的中断向量控制器)EXTI(外部中断/事件) 记录一些stm32调试过程中:不易被理解、存在使用误区、不清不楚、是坑、使用常识等方面的一些记录。本记录只包含stm32的内核以及外设等寄…...

centos7 arm服务器编译升级安装动态库libstdc++.so.6,解决GLIBC和CXXABI版本低的问题
前言 由于centos7内置的libstdc.so.6版本太低,导致安装第三方包的时候,会报“CXXABI_1.3.8”不存在等问题。 自带的打印如下: strings /usr/lib64/libstdc.so.6 | grep GLIBC strings /usr/lib64/libstdc.so.6 | grep CXXABI 如图 升级 注…...

自动驾驶轨迹规划之碰撞检测(三)
欢迎大家关注我的B站: 偷吃薯片的Zheng同学的个人空间-偷吃薯片的Zheng同学个人主页-哔哩哔哩视频 (bilibili.com) 目录 1.基于圆覆盖 2.BVH 3.MATLAB自动驾驶工具箱 4 ROS内置的模型 自动驾驶轨迹规划之碰撞检测(一)-CSDN博客 自动驾…...
如何用pandas处理财报数据删除金融行业数据
要删除财报数据中的金融行业数据,您可以按照以下步骤使用pandas进行处理: 导入pandas库: import pandas as pd读取财报数据文件: df pd.read_csv(财报数据.csv)查看数据中的行业分类列: print(df[行业分类])确定金…...
oracle 19c容器数据库data dump数据泵传输数据(4)---网络传输
Transporting a Database Over the Network: Example 这个的方式导入可以不需要传输dmp文件,我原本是想从11g导入到pdb2的,但是因为版本的原因,就直接实验从pdb1导入到pdb2吧。 这种方式和前面完全传输的方式类似,不需要事先在目…...
IP 网络分为接入网、城域网和骨干网
根据前述的IP 网络设计思想,结合算力网络对 正网络的需求分析,卫网络的具体实现可以从架构设计利网络技术两个方面进行总体设计。 首先从架构设计上考虑,架构应尽量简化,做到“以简应繁”。因此,整体网络架构不宜设计…...

web3.0基本概念简析
web3.0概念简析 web3.0的发展史 web1.0 仅用于展示,无法进行点赞评论等交互 web2.0 不仅可以展示,还可以上传视频、图片等,用户可以参与创作内容并获取收益。但还是中心化的模型 缺点 1 机械化的人机验证 2 账户安全无法保证 多年未登陆…...

Linux/Traceback
Enumeration nmap 使用nmap初步扫描发现只开放了22和80端口,端口详细扫描情况如下 先看看web是什么样子的,打开网站发现有一条留言,显示该站点已经被黑了, 并且留下了后门 查看源代码,可以看到下面的注释 <!--So…...

陶瓷碗口缺口检测-图像分割
图像分割 由于对碗口进行缺口检测,因此只需要碗口的边界信息。得到陶瓷碗区域填充后的图像,对图像进行边缘检测。这是属于图像分割中的内容,在图像的边缘中,可以利用导数算子对数字图像求差分,将边缘提取出来。 本案…...

2023年第十四届蓝桥杯软件赛省赛总评
报名明年4月蓝桥杯软件赛的同学们,如果你是大一零基础,目前懵懂中,不知该怎么办,可以看看本博客系列:备赛20周合集 20周的完整安排请点击:20周计划 每周发1个博客,共20周。 在QQ群上交流答疑&am…...
Redis面试大全
1、什么是Redis? Redis是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。 Redis与其他key-value缓存产品有以下三个特点: Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次…...

MFC为资源对话框添加消息处理函数和初始化控件
现在我VC6新建了一个对话框工程;又在资源添加了一个新的对话框,并为新的对话框添加了名为CTestDlg的类; 在主对话框的cpp文件包含#include "TestDlg.h"; 在主对话框的cpp文件的OnInitDialog()成员函数中,添…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...
提升移动端网页调试效率:WebDebugX 与常见工具组合实践
在日常移动端开发中,网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时,开发者迫切需要一套高效、可靠且跨平台的调试方案。过去,我们或多或少使用过 Chrome DevTools、Remote Debug…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...