【重走编程路】设计模式概述(十一) -- 备忘录模式、状态模式


前言

行为型模式关注对象之间的交互以及如何分配职责,提供了一种定义对象之间的行为和职责的最佳方式。本章介绍创建型模式中的备忘录模式和状态模式。


19. 备忘录模式(Memento)

定义

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

问题

在软件开发中,有时需要保存一个对象在某个时刻的状态,以便在将来某个时刻能够恢复到这个状态。直接保存对象的状态可能会导致对象内部结构的暴露,从而破坏封装性。

解决方案

备忘录模式通过以下步骤来解决问题:

  1. 定义备忘录接口(Memento):创建一个接口,用于保存原发器(Originator)对象的内部状态。这个接口通常只包含一个方法来获取备忘录中的状态信息,但实际的保存状态信息操作由原发器类负责。
  2. 具体备忘录类(Concrete Memento):实现备忘录接口,负责存储原发器的内部状态。具体备忘录类通常包含一些私有字段来保存原发器的状态信息。
  3. 原发器类(Originator):需要保存其内部状态的对象。原发器类创建一个包含当前内部状态的备忘录对象,并使用一个公共的接口将这个备忘录对象传递给其他的对象(如负责人)。同时,原发器类还提供一个恢复状态的方法,该方法使用备忘录对象来恢复原发器的内部状态。
  4. 负责人(Caretaker):负责保存备忘录对象,但不检查备忘录对象的内容。负责人可以根据需要将备忘录对象回传给原发器。
#include <iostream>  
#include <string>  
#include <memory>  
#include <stack>  

// 备忘录接口  
class Memento {
public:
    virtual ~Memento() {}
    // 通常这里会有一个方法来获取状态,但在这个例子中,我们直接在ConcreteMemento中处理  
};

// 具体备忘录类,存储编辑器的内容状态  
class TextMemento : public Memento {
private:
    std::string content;
public:
    TextMemento(const std::string& content) : content(content) {}

    // 获取内容的方法,这里直接返回内容,实际中可能通过接口返回  
    std::string GetContent() const {
        return content;
    }
};

// 原发器类,编辑器  
class TextEditor {
private:
    std::string content;
public:
    void Type(const std::string& text) {
        content += text;
        std::cout << "Typed: " << text << std::endl;
    }

    // 创建备忘录  
    std::shared_ptr<Memento> CreateMemento() {
        return std::make_shared<TextMemento>(content);
    }

    // 从备忘录恢复状态  
    void RestoreMemento(const std::shared_ptr<Memento>& memento) {
        auto textMemento = std::dynamic_pointer_cast<TextMemento>(memento);
        if (textMemento) {
            content = textMemento->GetContent();
            std::cout << "Restored content: " << content << std::endl;
        }
    }

    // 获取当前内容  
    std::string GetContent() const {
        return content;
    }
};

// 负责人,管理备忘录  
class Caretaker {
private:
    std::stack<std::shared_ptr<Memento>> mementos;
public:
    void AddMemento(const std::shared_ptr<Memento>& memento) {
        mementos.push(memento);
    }

    std::shared_ptr<Memento> GetMemento() {
        if (!mementos.empty()) {
            std::shared_ptr<Memento> top = mementos.top();
            mementos.pop();
            return top;
        }
        return nullptr;
    }
};

// 客户端代码  
int main() {
    TextEditor editor;
    Caretaker caretaker;

    editor.Type("Hello, ");
    caretaker.AddMemento(editor.CreateMemento()); // 保存状态  

    editor.Type("World!");
    std::cout << "Current content: " << editor.GetContent() << std::endl; // 输出: Hello, World!  

    // 撤销操作  
    editor.RestoreMemento(caretaker.GetMemento());
    std::cout << "Restored content: " << editor.GetContent() << std::endl; // 输出: Hello,   

    return 0;
}

应用场景

  1. 需要保存和恢复对象状态的场景。
  2. 提供一个可撤销操作的场景。
  3. 需要防止外部对象直接访问对象内部状态的场景。

优缺点

优点:

  • 封装性好:备忘录模式通过将状态信息封装在备忘录对象中,使得原发器对象不需要将状态信息暴露给外部对象,从而保持了良好的封装性。
  • 灵活性高:用户可以根据需要保存和恢复对象的状态多次,增加了软件的灵活性。特别是当需要实现复杂的撤销/重做功能时,备忘录模式提供了很好的支持。
  • 符合单一职责原则:备忘录对象负责管理原发器的内部状态,使得原发器类更加简洁,符合单一职责原则。

缺点:

  • 资源消耗:如果原发器对象的状态信息较多,那么每次保存状态都需要创建和存储一个完整的备忘录对象,这可能会消耗较多的内存资源。
  • 设计复杂:实现备忘录模式需要定义多个类,包括备忘录接口、具体备忘录类、原发器类和负责人类等,增加了系统的复杂性。
  • 适用范围有限:虽然备忘录模式可以解决需要保存和恢复对象状态的场景,但它并不是所有场景下的最佳选择。在某些情况下,可能需要考虑其他设计模式或解决方案。

20. 状态模式(State)

定义

状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。状态模式将特定状态相关的行为局部化,并将不同状态的行为分割开来,使得每一种状态对应一个类的行为。

问题

在软件开发中,一个对象的行为可能会随着其内部状态的变化而变化。如果直接在对象中嵌入这些状态相关的行为,会导致对象变得庞大且难以维护。

解决方案

状态模式通过以下步骤来解决问题:

  1. 定义状态接口(State):创建一个接口以封装与上下文(Context)的一个特定状态相关的行为。
  2. 具体状态类(Concrete States):实现状态接口,每个类代表一个具体的状态,并在其中实现与状态相关的行为。
  3. 上下文类(Context):维护一个状态对象的实例,这个状态对象定义了当前的状态。上下文类负责状态的切换,并将状态相关的请求委托给当前的状态对象来处理。
#include <iostream>  

class TrafficLight;

// 状态接口  
class TrafficLightState {
public:
    virtual ~TrafficLightState() {}
    virtual void Change(TrafficLight* context) = 0;
};

// 具体状态类:红灯  
class RedLightState : public TrafficLightState {
public:
    void Change(TrafficLight* context) override;
};

// 具体状态类:绿灯  
class GreenLightState : public TrafficLightState {
public:
    void Change(TrafficLight* context) override;
};

// 具体状态类:黄灯  
class YellowLightState : public TrafficLightState {
public:
    void Change(TrafficLight* context) override;
};

// 上下文类:交通信号灯  
class TrafficLight {
private:
    TrafficLightState* currentState;
    RedLightState* redLightState;
    GreenLightState* greenLightState;
    YellowLightState* yellowLightState;

public:
    TrafficLight() {
        redLightState = new RedLightState();
        greenLightState = new GreenLightState();
        yellowLightState = new YellowLightState();
        currentState = redLightState; // 初始状态为红灯  
    }
    ~TrafficLight() {
        delete redLightState;
        delete greenLightState;
        delete yellowLightState;
    }
    void SetState(TrafficLightState* state) {
        currentState = state;
    }
    TrafficLightState* GetRedLightState() {
        return redLightState;
    }
    TrafficLightState* GetGreenLightState() {
        return greenLightState;
    }
    TrafficLightState* GetYellowLightState() {
        return yellowLightState;
    }
    void Change() {
        currentState->Change(this);
    }
};

void RedLightState::Change(TrafficLight* context) {
    std::cout << "Red light is on. Cars must stop." << std::endl;
    context->SetState(context->GetGreenLightState());
}

void GreenLightState::Change(TrafficLight* context) {
    std::cout << "Green light is on. Cars may go." << std::endl;
    context->SetState(context->GetYellowLightState());
}

void YellowLightState::Change(TrafficLight* context) {
    std::cout << "Yellow light is on. Cars must prepare to stop." << std::endl;
    context->SetState(context->GetRedLightState());
}

// 客户端代码  
int main() {
    TrafficLight light;
    for (int i = 0; i < 10; ++i) {
        light.Change(); // 模拟信号灯变化  
    }
    return 0;
}

应用场景

  1. 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
  2. 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。

优缺点

优点:

  • 封装了状态的转换规则:状态模式将状态的转换代码封装在环境类或具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
  • 结构清晰:将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
  • 减少对象间的相互依赖:将不同的状态引入独立的对象中会使得状态转换变得更加明确,同时减少了对象间的依赖关系。
  • 容易扩展:状态模式允许通过定义新的子类来容易地增加新的状态和转换,从而提高了系统的可扩展性。

缺点:

  • 增加类和对象的个数:状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
  • 结构与实现复杂:状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
  • 对开闭原则的支持不佳:对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态。同时,修改某个状态类的行为也需要修改对应类的源代码,这违反了开闭原则(软件实体应当对扩展开放,对修改关闭)。

To be continued.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值