备忘录设计模式详解C/Java/JS/Go/Python/TS不同语言实现

Posted 刀法如飞-专注算法与设计模式

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了备忘录设计模式详解C/Java/JS/Go/Python/TS不同语言实现相关的知识,希望对你有一定的参考价值。

简介

备忘录模式(Memento Pattern)是一种结构型设计模式。这种模式就是在不破坏封装的条件下,将一个对象的状态捕捉(Capture)住,并放在外部存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一同使用。

备忘录模式的角色有三个:备忘录(Memento)角色、发起人(Originator)角色、负责人(Caretaker)角色

备忘录模式是由发起人(Originator)对象负责生成状态快照,其他对象不可修改状态。再将对象状态的副本存储在一个名为备忘录(Memento)的特殊对象中。除了创建备忘录的对象外,任何对象都不能访问备忘录的内容。其他对象必须使用指定接口与备忘录进行交互,它们可以获取快照的元数据(创建时间和操作名称等),但不能获取快照中原始对象的状态。

这种限制策略允许你将备忘录保存在通常被称为负责人(Caretakers)的对象历史中。由于负责人仅通过受限接口与备忘录互动,故其无法修改存储在备忘录内部的状态。同时,发起人拥有对备忘录所有成员的访问权限,从而能随时恢复其以前的某个状态。

作用

  1. 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
  2. 实现了内部状态的封装,除了创建它的发起人之外,其他对象都不能够访问这些状态信息,也不需要关心状态的保存细节。
  3. 简化了发起人角色,发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由负责人进行管理,符合单一职责原则。

实现步骤

  1. 创建备忘录Memento,用来记录操作状态数据的实体类。
  2. 创建发起人角色Originator,状态的制造者,也是备忘录的生成者,负责将状态写入到一个新备忘录。
  3. 创建负责人角色Caretaker,用来保存和读取备忘录的历史记录,所有备忘录均可以保存在历史中,以便恢复。
  4. 客户调用方通过Originator来生成备忘录,再通过Caretaker读取和恢复备忘录历史记录。

UML

 

 

Java代码

具体备忘录

// Memento.java 备忘录(Memento)角色,负责存储发起人传入的状态
public class Memento 
   private String state;

   public Memento(String state) 
      System.out.println(this.getClass().getName() + "::Memento() [state = " + state + "]");
      this.state = state;
   

   public String getState() 
      return state;
   

   public void setState(String state) 
      this.state = state;
   

发起人

// Originator.java 发起人(Originator)负责生成状态快照,即利用一个新备忘录对象将自己的内部状态存储起来
public class Originator 

    private String state;

    // 每次创建一个新备忘录来保存状态
    public Memento saveMemento() 
        System.out.println(this.getClass().getName() + "::saveMemento() [state = " + state + "]");
        return new Memento(state);
    

    // 从备忘录中恢复状态
    public void restoreMemento(Memento memento) 
        this.state = memento.getState();
    

    public String getState() 
        return state;
    

    public void setState(String state) 
        this.state = state;
    

负责人类

// Caretaker.java 负责人(Caretaker)角色,只负责保存备忘录记录,不能修改备忘录对象的内容
public class Caretaker 
    // 备忘录可以是一个记录,也可以就是一个对象,根据业务场景设置
    private List<Memento> mementoList = new ArrayList<Memento>();

    public void add(Memento memento) 
        System.out.println(this.getClass().getName() + "::add() [memento = " + memento.getClass().getName() + "]");
        mementoList.add(memento);
    

    public Memento get(int index) 
        return mementoList.get(index);
    

    public List<Memento> getMementoList() 
        return this.mementoList;
    

测试调用

    /*
     * 备忘录模式是在不暴露对象实现细节的情况下保存和恢复对象之前的状态。
     * 先声明发起人Originator,再声明负责人Caretaker,发起人生成备忘录Memento
     * 通过负责人则保存备忘录历史记录,读取备忘录由负责人来完成。
     */
    Originator originator = new Originator();
    Caretaker careTaker = new Caretaker();
    // 发起人产生一个状态
    originator.setState("state1");
    // 覆盖了状态,那么前面的状态未保存
    originator.setState("state2");
    // 发起人生成备忘录,一般添加时直接保存即可
    Memento memento = originator.saveMemento();
    // 负责人保添加备忘录历史记录
    careTaker.add(memento);

    // 直接生成备忘录并添加到负责人的备忘录列表
    originator.setState("state3");
    careTaker.add(originator.saveMemento());
    originator.setState("state4");
    careTaker.add(originator.saveMemento());

    System.out.println("发起人当前的状态: " + originator.getState());

    // 发起人通过负责人那里取出状态
    originator.restoreMemento(careTaker.get(0));
    System.out.println("第一个保存的状态: " + originator.getState());
    originator.restoreMemento(careTaker.get(1));
    System.out.println("第二个保存的状态: " + originator.getState());

    // 遍历全部备忘录
    for (int i = 0; i < careTaker.getMementoList().size(); i++) 
      // 外部一般不直接访问备忘录里面的状态,而是逐个恢复备忘录,再取出状态来
      originator.restoreMemento(careTaker.get(i));
      System.out.println("state: " + i + ")" + originator.getState());
    

JavaScript代码

具体备忘录

// Memento.js 备忘录(Memento)角色,负责存储发起人传入的状态
// 备忘录(Memento)角色,负责存储发起人传入的状态
export class Memento 
  constructor(state) 
    console.log(this.constructor.name + \'::Memento() [state = \' + state + \']\')
    this.state = state
  

  getState() 
    return this.state
  

  setState(state) 
    this.state = state
  


发起人

// Originator.js 发起人(Originator)负责生成状态快照,即利用一个新备忘录对象将自己的内部状态存储起来
import  Memento  from \'./Memento.js\'

export class Originator 
  constructor() 
    this.state = undefined
  

  // 每次创建一个新备忘录来保存状态
  saveMemento() 
    console.log(
      this.constructor.name + \'::saveMemento() [state = \' + this.state + \']\'
    )
    return new Memento(this.state)
  

  // 从备忘录中恢复状态
  restoreMemento(memento) 
    this.state = memento.getState()
  

  getState() 
    return this.state
  

  setState(state) 
    this.state = state
  

负责人类

// Caretaker.js 负责人(Caretaker)角色,只负责保存备忘录记录,不能修改备忘录对象的内容
export class Caretaker 
  constructor() 
    // 备忘录可以是一个记录,也可以就是一个对象,根据业务场景设置
    this.mementoList = []
  

  add(memento) 
    console.log(
      this.constructor.name +
        \'::add() [memento = \' +
        memento.constructor.name +
        \']\'
    )
    this.mementoList.push(memento)
  

  get(index) 
    return this.mementoList[index]
  

  getMementoList() 
    return this.mementoList
  

测试调用

import  Originator  from \'../src/Originator.js\'
import  Caretaker  from \'../src/Caretaker.js\'

export function test() 
  /*
   * 备忘录模式是在不暴露对象实现细节的情况下保存和恢复对象之前的状态。
   * 先声明发起人Originator,再声明负责人Caretaker,发起人生成备忘录Memento
   * 通过负责人则保存备忘录历史记录,读取备忘录由负责人来完成。
   */
  const originator = new Originator()
  const careTaker = new Caretaker()
  // 发起人产生一个状态
  originator.setState(\'state1\')
  // 覆盖了状态,那么前面的状态未保存
  originator.setState(\'state2\')
  // 发起人生成备忘录,一般添加时直接保存即可
  const memento = originator.saveMemento()
  // 负责人保添加备忘录历史记录
  careTaker.add(memento)

  // 直接生成备忘录并添加到负责人的备忘录列表
  originator.setState(\'state3\')
  careTaker.add(originator.saveMemento())
  originator.setState(\'state4\')
  careTaker.add(originator.saveMemento())

  console.log(\'发起人当前的状态: \' + originator.getState())

  // 发起人通过负责人那里取出状态
  originator.restoreMemento(careTaker.get(0))
  console.log(\'第一个保存的状态: \' + originator.getState())
  originator.restoreMemento(careTaker.get(1))
  console.log(\'第二个保存的状态: \' + originator.getState())

  // 遍历全部备忘录
  for (let i = 0; i < careTaker.getMementoList().length; i++) 
    // 外部一般不直接访问备忘录里面的状态,而是逐个恢复备忘录,再取出状态来
    originator.restoreMemento(careTaker.get(i))
    console.log(\'state: \' + i + \')\' + originator.getState())
  


// 执行测试
;(function () 
  console.log(\'test start:\')
  test()
)()

更多语言版本

不同语言实现设计模式:https://github.com/microwind/design-pattern

设计模式之状态模式与备忘录模式详解和应用

目录


1.状态模式

1.1 目标

1、 掌握状态模式和备忘录模式的应用场景。

2、 了解状态机实现订单状态流转控制的过程

3、 掌握状态模式和策略模式的区别。

4、 掌握状态模式和责任链模式的区别。

5、 掌握备忘录模式在落地实战中的压栈管理。

1.2 内容定位

1、如果参与电商订单业务开发的人群,可以重点关注状态模式。

2、如果参与富文本编辑器开发的人群,可以重点关注备忘录模式。

1.3.迭代器模式

状态模式( State Pattern ) 也称为状态机模式(State Machine Pattern), 是允许对象在内 部状态发生改变时改变它的行为,对象看起来好像修改了它的类,属于行为型模式。

原文:Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
解释:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

状态模式中类的行为是由状态决定的,不同的状态下有不同的行为。其意图是让一个对象在 其内部改变的时候,其行为也随之改变。状态模式核心是状态与行为绑定,不同的状态对应不 同的行为。

1.4 应用场景

状态模式在生活场景中也还比较常见。例如:我们平时网购的订单状态变化。另外,我们平 时坐电梯,电梯的状态变化。

在软件开发过程中,对于某一项操作,可能存在不同的情况。通常处理多情况问题最直接的 方式就是使用if…else或 switch…case条件语句进行枚举。但是这种做法对于复杂状态的判断 天然存在弊端:条件判断语句过于臃肿,可读性差,且不具备扩展性,维护难度也大。而如果 转换思维,将这些不同状态独立起来用各个不同的类进行表示,系统处于哪种情况,直接使用 相应的状态类对象进行处理,消除了 if-else , switch…case等冗余语句,代码更有层次性且具 备良好扩展力。

状态模式主要解决的就是当控制一个对象状态的条件表达式过于复杂时的情况。通过把状态 的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。对象的行为依赖 于它的状态(属性),并且会根据它的状态改变而改变它的相关行为。状态模式适用于以下场景 :

1、 行为随状态改变而改变的场景;

2、 一个操作中含有庞大的多分支结构,并且这些分支取决于对象的状态。

首先来看下状态模式的通用UML类图:

从 UML类图中,我们可以看到,状态模式主要包含三种角色:

1、环境类角色(Context):定义客户端需要的接口,内部维护一个当前状态实例,并负责 具体状态的切换

2、抽象状态角色(State ):定义该状态下的行为,可以有一个或多个行为;

3、具体状态角色(ConcreteState ):具体实现该状态对应的行为,并且在需要的情况下进行状态切换。

1.5 状态模式在业务场景中的应用

我们在GPer社区阅读文章时,如果觉得文章写的很好,我们就会评论、收藏两连发。如果 处于登录情况下,我们就可以直接做评论,收藏这些行为。否则,跳转到登录界面,登录后再 继续执行先前的动作。这里涉及的状态有两种:登录与未登录,行为有两种:评论,收满。下 面我们使状态模式来实现一下这个逻辑,代码如下。

首先创建抽象状态角色UserState类:

 public abstract class UserState 
     protected AppContext context;
 
     public void setContext(AppContext context) 
         this.context = context;
     
 
     public abstract void favorite();
 
     public abstract void comment(String comment);
 

然 后 ,创建登录状态LogInState类 :

 public class LoginState extends UserState 
     @Override
     public void favorite() 
         System.out.println("收藏成功!");
     
 
     @Override
     public void comment(String comment) 
         System.out.println(comment);
     
 

创建未登录状态UnloginState类 :

 public class UnLoginState extends UserState 
 
     @Override
     public void favorite() 
         this.switch2login();
         this.context.getState().favorite();
     
 
     @Override
     public void comment(String comment) 
         this.switch2login();
         this.context.getState().comment(comment);
     
 
     private void switch2login()
         System.out.println("跳转到登录页!");
         this.context.setState(this.context.STATE_LOGIN);
     
 

创建上下文角色AppContext类 :

 public class AppContext 
 
     public static final UserState STATE_LOGIN = new LoginState();
     public static final UserState STATE_UNLOGIN = new UnLoginState();
 
     private UserState currentState = STATE_UNLOGIN;
 
     
         STATE_LOGIN.setContext(this);
         STATE_UNLOGIN.setContext(this);
     
 
     public void setState(UserState state)
         this.currentState = state;
     
 
     public UserState getState()
         return this.currentState;
     
 
     public void favorite()
         this.currentState.favorite();
     
 
     public void comment(String comment)
         this.currentState.comment(comment);
     
 

编写客户端测试代码:

 public class Test 
     public static void main(String[] args) 
         AppContext context = new AppContext();
         context.favorite();
         context.comment("评论:好文章,360个赞");
     
 

运行结果如下:

1.6 利用状态机实现订单状态流转控制

状态机是状态模式的一种应用,相当于上下文角色的一个升级版。在工作流或游戏等各种系 统中有大量使用,如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。 Spring也提供给了我们一个很好的解决方案。Spring中的组件名称就叫StateMachine(状态机)。状态机帮助开发者简化状态控制的开发过程,让状态机结构更加层次化。下面,我们用 Spring状态机模拟一个订单状态流转的过程。

 <dependency>
     <groupId>org.springframework.statemachine</groupId>
     <artifactId>spring-statemachine-core</artifactId>
     <version>2.0.1.RELEASE</version>
 </dependency>

1、创建订单实体Order类

 @Data
 public class Order 
     private int id;
     private OrderStatus status;
 
     @Override
     public String toString() 
         return "订单号:" + id + ", 订单状态:" + status;
     
 

2、创建订单状态枚举类和状态转换枚举类

 public enum OrderStatus 
     // 待支付,待发货,待收货,订单结束
     WAIT_PAYMENT, WAIT_DELIVER, WAIT_RECEIVE, FINISH;
 
public enum OrderStatusChangeEvent 
    // 支付,发货,确认收货
    PAYED, DELIVERY, RECEIVED;

3、添加状态流转配置

/**
 * 订单状态机配置
 */
@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> 
 
    /**
     * 配置状态
     * @param states
     * @throws Exception
     */
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception 
        states
                .withStates()
                .initial(OrderStatus.WAIT_PAYMENT)
                .states(EnumSet.allOf(OrderStatus.class));
    
 
    /**
     * 配置状态转换事件关系
     * @param transitions
     * @throws Exception
     */
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitions) throws Exception 
        transitions
                .withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED)
                .and()
                .withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY)
                .and()
                .withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED);
    
 
    /**
     * 持久化配置
     * 实际使用中,可以配合redis等,进行持久化操作
     * @return
     */
    @Bean
    public DefaultStateMachinePersister persister()
        return new DefaultStateMachinePersister<>(new StateMachinePersist<Object, Object, Order>() 
            @Override
            public void write(StateMachineContext<Object, Object> context, Order order) throws Exception 
                //此处并没有进行持久化操作
            
 
            @Override
            public StateMachineContext<Object, Object> read(Order order) throws Exception 
                //此处直接获取order中的状态,其实并没有进行持久化读取操作
                return new DefaultStateMachineContext(order.getStatus(), null, null, null);
            
        );
    

4、添加订单状态监听器

@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListenerImpl
 
    @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
    public boolean payTransition(Message<OrderStatusChangeEvent> message) 
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_DELIVER);
        System.out.println("支付,状态机反馈信息:" + message.getHeaders().toString());
        return true;
    
 
    @OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")
    public boolean deliverTransition(Message<OrderStatusChangeEvent> message) 
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_RECEIVE);
        System.out.println("发货,状态机反馈信息:" + message.getHeaders().toString());
        return true;
    
 
    @OnTransition(source = "WAIT_RECEIVE", target = "FINISH")
    public boolean receiveTransition(Message<OrderStatusChangeEvent> message)
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.FINISH);
        System.out.println("收货,状态机反馈信息:" + message.getHeaders().toString());
        return true;
    

5、创建 lOrderService 接口

public interface IOrderService 
    //创建新订单
    Order create();
    //发起支付
    Order pay(int id);
    //订单发货
    Order deliver(int id);
    //订单收货
    Order receive(int id);
    //获取所有订单信息
    Map<Integer, Order> getOrders();

6、在 Service业务逻辑中应用

@Service("orderService")
public class OrderServiceImpl implements IOrderService 

    @Autowired
    private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;
 
    @Autowired
    private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, Order> persister;
 
    private int id = 1;
    private Map<Integer, Order> orders = new HashMap<>();

    public Order create() 
        Order order = new Order();
        order.setStatus(OrderStatus.WAIT_PAYMENT);
        order.setId(id++);
        orders.put(order.getId(), order);
        return order;
    

    public Order pay(int id) 
        Order order = orders.get(id);
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 尝试支付,订单号:" + id);
        Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.PAYED).setHeader("order", order).build();
        if (!sendEvent(message, order)) 
            System.out.println("线程名称:" + Thread.currentThread().getName() + " 支付失败, 状态异常,订单号:" + id);
        
        return orders.get(id);
    

    public Order deliver(int id) 
        Order order = orders.get(id);
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 尝试发货,订单号:" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.DELIVERY).setHeader("order", order).build(), orders.get(id))) 
            System.out.println("线程名称:" + Thread.currentThread().getName() + " 发货失败,状态异常,订单号:" + id);
        
        return orders.get(id);
    

    public Order receive(int id) 
        Order order = orders.get(id);
        System.out.println("线程名称:" + Thread.currentThread().getName() + " 尝试收货,订单号:" + id);
        if (!sendEvent(MessageBuilder.withPayload(OrderStatusChangeEvent.RECEIVED).setHeader("order", order).build(), orders.get(id))) 
            System.out.println("线程名称:" + Thread.currentThread().getName() + " 收货失败,状态异常,订单号:" + id);
        
        return orders.get(id);
     

    public Map<Integer, Order> getOrders() 
        return orders;
     
 
    /**
     * 发送订单状态转换事件
     *
     * @param message
     * @param order
     * @return
     */
    private synchronized boolean sendEvent(Message<OrderStatusChangeEvent> message, Order order架构师内功心法,参与富文本编辑器开发的备忘录模式详解

架构师内功心法,参与富文本编辑器开发的备忘录模式详解

Java设计模式之十一种行为型模式(附实例和详解)

Java设计模式之五大创建型模式(附实例和详解)

Java开发中的23种设计模式详解之三:11种行为型模式

Go语言备忘录:反射的原理与使用详解