状态模式与备忘录模式

一、状态模式

状态模式( 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.
解释:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

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

二、状态模式应用场景

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

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

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

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

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

首先来看下状态模式的通用UML类图:
在这里插入图片描述
从 UML类图中,我们可以看到,状态模式主要包含三种角色:
1、环境类角色(Context):定义客户端需要的接口,内部维护一个当前状态实例,并负责
具体状态的切换
2、抽象状态角色(State ):定义该状态下的行为,可以有一个或多个行为;
3、具体状态角色(ConcreteState ):具体实现该状态对应的行为,并且在需要的情况下进行状态切换。

  • 步骤 1 创建一个接口
public interface State {
   public void doAction(Context context);
}
  • 步骤 2创建实现接口的实体类。
public class StartState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in start state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Start State";
   }
}
public class StopState implements State {
 
   public void doAction(Context context) {
      System.out.println("Player is in stop state");
      context.setState(this); 
   }
 
   public String toString(){
      return "Stop State";
   }
}
  • 步骤 3 创建 Context 类。
public class Context {
   private State state;
 
   public Context(){
      state = null;
   }
 
   public void setState(State state){
      this.state = state;     
   }
 
   public State getState(){
      return state;
   }
}
  • 步骤 4 使用 Context 来查看当状态 State 改变时的行为变化。
public class StatePatternDemo {
   public static void main(String[] args) {
      Context context = new Context();
 
      StartState startState = new StartState();
      startState.doAction(context);
 
      System.out.println(context.getState().toString());
 
      StopState stopState = new StopState();
      stopState.doAction(context);
 
      System.out.println(context.getState().toString());
   }
}
  • 步骤 5 执行程序,输出结果:
Player is in start state
Start State
Player is in stop state
Stop State

三、备忘录模式

备忘录模式( Memento Pattern )又称为快照模式(Snapshot Pattern )或令牌模式(Token
Pattern ) , 是指在不破坏封装的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态,属于行为型模式。

原文:Without violating encapsulation, capture and externalize an object * s internal state so that the object can be restored to this state later.

在软件系统中,备忘录模式可以为我们提供一种"后悔药”的机制,它通过存储系统各个历
史状态的快照,使得我们可以在任一时刻将系统回滚到某一个历史状态。

备忘录模式本质是从发起人实体类(Originator) 隔离存储功能,降低实体类的职责。同时 由于存储信息( Memento ) 独 立 ,且存储信息的实体交由管理类(Caretaker) 管 理 ,则可以 通过为管理类扩展额外的功能对存储信息进行扩展操作(比如增加历史快照功能…) 。

备忘录模式的应用场景

对于我们程序员来说,可能天天都在使用备忘录模式,比如我们每天使用的Git、SVN都可 以提供一种代码版本撤回的功能。还有一个比较贴切的现实场景应该是游戏的存档功能,通过
将游戏当前进度存储到本地文件系统或数据库中,使得下次继续游戏时,玩家可以从之前的位
置继续进行。

备忘录模式适用于以下应用场景:

1、需要保存历史快照的场景;

2、希望在对象之外保存状态,且除了自己其他类对象无法访问状态保存具体内容。
在这里插入图片描述

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

发起人角色( Originator ) : 负责创建一个备忘录,记录自身需要保存的状态;具备状态回滚功能;

备忘录角色( Memento ) : 用于存储Originator的内部状态,且可以防止Originator以外的对象进行访问;

备忘录管理员角色( Caretaker) : 负责存储,提供管理备忘录( Memento ) , 无法对备忘录内容进行操作和访问。

四、备忘录模式实战

我们肯定都用过网页中的富文本编辑器,编辑器中的通常会附带草稿箱、撤销等这样的操作。
下面我们用一段带代码来实现一个这样的功能。假设 ,我们在GPer社区中发布一篇文章,文章编辑的过程需要花很长时间,中间也会不停地撤销、修改。甚至可能要花好几天才能写出一篇
精品文章,因此可能会将已经编辑好的内容实时保存到草稿箱。

首先创建发起人角色编辑器Editor类 :

@Data
public class Editor {

    private String title;
    private String content;
    private String imgs;

    public Editor(String title, String content, String imgs) {
        this.title = title;
        this.content = content;
        this.imgs = imgs;
    }

    public ArticleMemento saveToMemento(){
        ArticleMemento articleMemento = new ArticleMemento(this.title,this.content,this.imgs);
        return articleMemento;
    }

    public void undoFromMemento(ArticleMemento articleMemento){
        this.title = articleMemento.getTitle();
        this.content = articleMemento.getContent();
        this.imgs = articleMemento.getImgs();
    }
}

然后创建备忘录角色ArticleMemento类 :

@Data
public class ArticleMemento {
    private String title;
    private String content;
    private String imgs;

    public ArticleMemento(String title, String content, String imgs) {
        this.title = title;
        this.content = content;
        this.imgs = imgs;
    }
}

最后创建备忘录管理角色草稿箱DraftsBox类:

public class DraftsBox {
    private final Stack<ArticleMemento> STACK = new Stack<ArticleMemento>();

    public ArticleMemento getMemento(){
        ArticleMemento articleMemento = STACK.pop();
        return articleMemento;
    }

    public void addMemento(ArticleMemento articleMemento){
        STACK.push(articleMemento);
    }
}

草稿箱中定义的Stack类是Vector的一个子类,它实现了一个标准的后进先出的栈。主要
定义了以下方法:
在这里插入图片描述
最后,编写客户端测试代码:

public class Test {
    public static void main(String[] args) {
        DraftsBox draftsBox = new DraftsBox();

        Editor editor = new Editor("我是这样手写Spring的,麻雀虽小五脏俱全",
                "本文节选自《Spring5核心原理与30个类手写实战》一书,Tom著,电子工业出版社出版。",
                "35576a9ef6fc407aa088eb8280fb1d9d.png");

        ArticleMemento articleMemento = editor.saveToMemento();
        draftsBox.addMemento(articleMemento);

        System.out.println("标题:" + editor.getTitle() + "\n" +
                            "内容:" + editor.getContent() + "\n" +
                            "插图:" + editor.getImgs() + "\n暂存成功");

        System.out.println("完整的信息" + editor);


        System.out.println("==========首次修改文章===========");
        editor.setTitle("【Tom原创】我是这样手写Spring的,麻雀虽小五脏俱全");
        editor.setContent("本文节选自《Spring5核心原理与30个类手写实战》一书,Tom著");

        System.out.println("==========首次修改文章完成===========");

        System.out.println("完整的信息" + editor);

        articleMemento = editor.saveToMemento();

        draftsBox.addMemento(articleMemento);

        System.out.println("==========保存到草稿箱===========");


        System.out.println("==========第2次修改文章===========");
        editor.setTitle("手写Spring");
        editor.setContent("本文节选自《Spring5核心原理与30个类手写实战》一书,Tom著");
        System.out.println("完整的信息" + editor);
        System.out.println("==========第2次修改文章完成===========");

        System.out.println("==========第1次撤销===========");
        articleMemento = draftsBox.getMemento();
        editor.undoFromMemento(articleMemento);
        System.out.println("完整的信息" + editor);
        System.out.println("==========第1次撤销完成===========");


        System.out.println("==========第2次撤销===========");
        articleMemento = draftsBox.getMemento();
        editor.undoFromMemento(articleMemento);
        System.out.println("完整的信息" + editor);
        System.out.println("==========第2次撤销完成===========");

    }
}

备忘录模式在源码中的体现
在 Spring的 webflow源 码 中 还 是 找 到 一 个StateManageableMessageContext 接口 ,我们来看它的源代码:

public interface StateManageableMessageContext extends MessageContext {
    Serializable createMessagesMemento();

    void restoreMessages(Serializable var1);

    void setMessageSource(MessageSource var1);
}

我们看到有一个createMessagesMementoQ方法,创建一个消息备忘录。可以打开它的实
现类:

public class DefaultMessageContext implements StateManageableMessageContext {

   private static final Log logger = LogFactory.getLog(DefaultMessageContext.class);

   private MessageSource messageSource;

   @SuppressWarnings("serial")
   private Map<Object, List<Message>> sourceMessages = new AbstractCachingMapDecorator<Object, List<Message>>(
         new LinkedHashMap<Object, List<Message>>()) {

      protected List<Message> create(Object source) {
         return new ArrayList<Message>();
      }
   };
	public void clearMessages() {
		sourceMessages.clear();
	}

	// implementing state manageable message context
	public Serializable createMessagesMemento() {
		return new LinkedHashMap<Object, List<Message>>(sourceMessages);
	}

	@SuppressWarnings("unchecked")
	public void restoreMessages(Serializable messagesMemento) {
		sourceMessages.putAll((Map<Object, List<Message>>) messagesMemento);
	}

	public void setMessageSource(MessageSource messageSource) {
		if (messageSource == null) {
			messageSource = new DefaultTextFallbackMessageSource();
		}
		this.messageSource = messageSource;
	}        
}       

我们看到其主要逻辑就相当于是给Message留一个备份,以备恢复之用。

命令模式的优缺点
优点:

1、简化发起人实体类(Originator ) 职 责 ,隔离状态存储与获取,实现了信息的封装,客户端
无需关心状态的保存细节;

2、提供状态回滚功能;

缺点:

1、消耗资源:如果需要保存的状态过多时,每一次保存都会消耗很多内存。

十二、状态模式与备忘录模式详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值