设计模式12-状态模式与备忘模式详解
1.12-状态模式与备忘录模式详解
1.12.1.-备忘录模式详解
时长:46min
12.1.1.备忘录模式的定义
定义:
备忘录模式【Memento Pattern】,也称快照模式【Snapshot Pattern】,或令牌模式【Token Pattern],是指在不破坏封装的前提下,
捕获一个对象内部状态,并在对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。
特征:"后悔药"
属于行为型模式。
12.1.1.1.备忘录模式在生活中体现
代码版本管理中的撤销和恢复
游戏存档
12.1.1.2.备忘录模式的适用场景
1.需要保存历史快照的场景
2.希望在对象之外保存状态,且除了自己,其他类对象无法访问状态保存的具体内容。
12.1.2.备忘录模式的通用实现
12.1.2.1.系统类图设计
12.1.2.2.代码实现
12.1.3.备忘录模式的实现案例之富文本编辑
在网页编辑中,常常会用到富文本编辑器。会提供一个草稿箱【相当于剪贴板】,支持撤销,和恢复功能。
假设我们需要编写一篇博客,编写文章可能需要很长的时间,中间涉及到反复修改,撤回。。。,一般来说,我
们需要等到一篇文章彻底完成之后,才会进行发布。
而编辑工作可能会进行多天,当再次打开上次编辑时,需要保存原来的内容。所以,先把内容保存到草稿箱中。
12.1.3.1.系统类图设计
12.1.3.2..代码实现
1.编辑器
package com.wf.memento.demo; /** * @ClassName Editor * @Description 编辑器,备忘录发起者 * @Author wf * @Date 2020/6/19 16:26 * @Version 1.0 */ public class Editor { private String title; private String content; private String images; public Editor(String title, String content, String images) { this.title = title; this.content = content; this.images = images; } public ArticleMemento saveToMemento(){ ArticleMemento articleMemento = new ArticleMemento(title,content,images); return articleMemento; } public void undoFromMemento( ArticleMemento articleMemento){ //撤销,还原编辑器内容,为快照内容 this.title = articleMemento.getTitle(); this.content = articleMemento.getContent(); this.images = articleMemento.getImages(); } @Override public String toString() { return "Editor{" + "title='" + title + '\'' + ", content='" + content + '\'' + ", images='" + images + '\'' + '}'; } public String getTitle() { return title; } public String getContent() { return content; } public String getImages() { return images; } public void setTitle(String title) { this.title = title; } public void setContent(String content) { this.content = content; } public void setImages(String images) { this.images = images; } }
2.备忘录类
package com.wf.memento.demo; /** * @ClassName ArticleMemento * @Description 文章备忘录 * @Author wf * @Date 2020/6/19 16:31 * @Version 1.0 */ public class ArticleMemento { private String title; private String content; private String images; public ArticleMemento(String title, String content, String images) { this.title = title; this.content = content; this.images = images; } public String getTitle() { return title; } public String getContent() { return content; } public String getImages() { return images; } }
3.草稿箱
package com.wf.memento.demo; import java.util.Stack; /** * @ClassName DraftsBox * @Description 草稿箱 * @Author wf * @Date 2020/6/19 16:33 * @Version 1.0 */ public class DraftsBox { /**后进先出 栈*/ private final Stack<ArticleMemento> STACK = new Stack<ArticleMemento>(); /** * 压栈,将元素压入栈顶 * @param memento */ public void addMemento(ArticleMemento memento){ STACK.push(memento); } public ArticleMemento getMemento(){ ArticleMemento articleMemento = STACK.pop(); return articleMemento; } }
4.测试类
package com.wf.memento.demo; /** * @ClassName Test * @Description 测试类 * @Author wf * @Date 2020/6/22 10:32 * @Version 1.0 */ public class Test { public static void main(String[] args) { DraftsBox box = new DraftsBox(); Editor editor = new Editor("手写spring","文章节选至《。。》","xxx.png"); ArticleMemento articleMemento = editor.saveToMemento(); box.addMemento(articleMemento); System.out.println("标题:"+editor.getTitle()+ "\n内容:"+editor.getContent()+ "\n图片:"+editor.getImages()+ "暂存成功"); System.out.println("完整信息:"+editor); System.out.println("==========第一次修改文章=========="); editor.setTitle("【Tom原创】,我是这样手写spring中,麻雀虽小五脏俱全"); editor.setContent("文章节选至《。。》,tom著"); System.out.println("===============首次完成文章修改=============="); System.out.println("完整信息:"+editor); articleMemento = editor.saveToMemento(); box.addMemento(articleMemento); System.out.println("===============保存到草稿箱=============="); System.out.println("==========第二次修改文章=========="); editor.setTitle("手写spring核心原理功能实现"); System.out.println("完整信息:"+editor); System.out.println("===============第二次完成文章修改=============="); System.out.println("==========第一次撤销回退=========="); ArticleMemento memento = box.getMemento(); editor.undoFromMemento(memento); System.out.println("完整信息:"+editor); System.out.println("===============第1次撤销完成=============="); System.out.println("==========第二次撤销回退=========="); memento = box.getMemento(); editor.undoFromMemento(memento); System.out.println("完整信息:"+editor); System.out.println("===============第2次撤销完成=============="); } }
测试结果如下:
12.1.4.备忘录模式在源码中应用
12.1.4.1.spring-webflow中StateManageableMessageContext
需要引入依赖:
<dependency> <groupId>org.springframework.webflow</groupId> <artifactId>spring-webflow</artifactId> <version>2.4.5.RELEASE</version> </dependency>
接口中定义:
Serializable createMessagesMemento();
实现方法:
org.springframework.binding.message.DefaultMessageContext#createMessagesMemento
private Map<Object, List<Message>> sourceMessages;
public Serializable createMessagesMemento() { return new LinkedHashMap(this.sourceMessages); }
12.1.5.备忘录模式的使用总结
12.1.5.1.优缺点总结
优点:
1.简化发起人实体类【Originator】的职责,隔离状态存储与获取。实现信息封装,客户端无须关心状态的保存细节。
2.提供状态回滚的功能。
缺点:
1.消耗资源:如果需要存储的状态过多时,每一次保存都会消耗很多内存。
1.12.2.-状态模式详解
时长:1h10min
学习目标:
》掌握状态模式的应用场景
》了解状态机实现订单状态流转控制的过程
》掌握状态模式与策略模式、责任链模式的区别
12.2.1.状态模式的定义
定义:
状态模式【State Pattern】,也称为状态机模式【State Machine Pattern】,是允许对象在内部状态发生改变时
改变它的行为,对象看起来好像修改了它的子类。
属于行为型模式。
它的核心,是将状态与行为进行绑定,不同状态对应不同行为。
12.2.1.1.状态模式在生活中的应用
1.网购中订单状态变化
2.电梯状态的变化
12.2.1.2.状态模式的适用场景
1.行为随状态改变而改变
2.一个操作中含有庞大的多分支结构,并且这些分支取决于对象的状态。
12.2.2.状态模式的通用实现
12.2.2.1.类图设计
12.2.2.2.代码实现
1.顶层状态接口
package com.wf.state.general; /** * @ClassName IState * @Description 顶层状态接口 * @Author wf * @Date 2020/6/22 13:59 * @Version 1.0 */ public interface IState { void handle(); }
2.上下文对象
package com.wf.state.general; /** * @ClassName Context * @Description 上下文对象 * @Author wf * @Date 2020/6/22 14:01 * @Version 1.0 */ public class Context { private static final IState STATE_A = new ConcreteStateA(); private static final IState STATE_B = new ConcreteStateB(); //默认状态 private IState currentState = STATE_A; public void setState(IState state){ currentState = state; } public void handle(){ this.currentState.handle(); } }
3.状态实现子类
package com.wf.state.general; /** * @ClassName ConcreteStateB * @Description 具体状态实现子B * @Author wf * @Date 2020/6/22 14:00 * @Version 1.0 */ public class ConcreteStateB implements IState { @Override public void handle() { System.out.println("StateB do action"); } } package com.wf.state.general; /** * @ClassName ConcreteStateA * @Description 具体状态实现子类A * @Author wf * @Date 2020/6/22 14:00 * @Version 1.0 */ public class ConcreteStateA implements IState { @Override public void handle() { System.out.println("StateA do action"); } }
4.测试类
package com.wf.state.general; /** * @ClassName Test * @Description 测试类 * @Author wf * @Date 2020/6/22 14:09 * @Version 1.0 */ public class Test { public static void main(String[] args) { Context context = new Context(); context.setState(new ConcreteStateB()); context.handle(); } }
测试结果:
12.2.3.状态模式的使用案例之访问权限系统
比如:当我们访问一个社区时,如果未登录【游客】,当我们看到一篇好的文章,想要进行收藏。是不存在的,是无法看到的。
只有当登录后,才允许访问。
这里的是否登录【是两种不同的状态】,对应了不同的行为。
12.2.3.1.类图设计
12.2.3.2.代码实现
1.顶层用户状态抽象类
package com.wf.state.demo.gper; /** * @ClassName UserState * @Description 用户状态 * @Author wf * @Date 2020/6/22 14:24 * @Version 1.0 */ public abstract class UserState { //抽象类引入context protected AppContext context; public void setContext(AppContext context) { this.context = context; } //收藏功能 public abstract void favorite(); //评论 public abstract void comment(String comment); }
2.上下文对象
package com.wf.state.demo.gper; /** * @ClassName AppContext * @Description 上下文 * @Author wf * @Date 2020/6/22 14:26 * @Version 1.0 */ 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 currentState){ this.currentState = currentState; } public UserState getState(){ return currentState; } public void favorite(){ this.currentState.favorite(); } public void comment(String comment){ this.currentState.comment(comment); } }
3.用户状态实现子类
package com.wf.state.demo.gper; /** * @ClassName UnLoginState * @Description 未登录状态 * @Author wf * @Date 2020/6/22 14:27 * @Version 1.0 */ public class UnLoginState extends UserState { @Override public void favorite() { //要想收藏,先进行登录 this.switchToLogin(); //调用登录后的功能 this.context.getState().favorite(); } @Override public void comment(String comment) { this.switchToLogin(); this.context.getState().comment(comment); } private void switchToLogin(){ System.out.println("跳转到登录页,进行登录"); //登录完成后,切换为登录状态 this.context.setState(this.context.STATE_LOGIN); } } package com.wf.state.demo.gper; /** * @ClassName UnLoginState * @Description 未登录状态 * @Author wf * @Date 2020/6/22 14:27 * @Version 1.0 */ public class UnLoginState extends UserState { @Override public void favorite() { //要想收藏,先进行登录 this.switchToLogin(); //调用登录后的功能 this.context.getState().favorite(); } @Override public void comment(String comment) { this.switchToLogin(); this.context.getState().comment(comment); } private void switchToLogin(){ System.out.println("跳转到登录页,进行登录"); //登录完成后,切换为登录状态 this.context.setState(this.context.STATE_LOGIN); } }
4.测试类
package com.wf.state.demo.gper; /** * @ClassName Test * @Description 测试类 * @Author wf * @Date 2020/6/22 14:39 * @Version 1.0 */ public class Test { public static void main(String[] args) { AppContext context = new AppContext(); //默认为未登录状态 context.favorite(); context.comment("你好,你评论得perfect"); } }
测试结果:
12.2.4.状态模式的使用案例之spring状态机使用
状态机模式,是在context的基础上,进行的一种优化。spring-statemachine模块有提供相关功能。
需求:
基于spring-statemachine实现状态机模式应用。这里以订单状态流程处理为例:
依赖包:
<!--状态机-->
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
12.2.4.1.类图设计
12.2.4.2.代码实现
1.定义订单实体类
package com.wf.state.demo.order; /** * @ClassName Order * @Description 订单,业务bean * @Author wf * @Date 2020/6/22 15:14 * @Version 1.0 */ public class Order { private Integer id; private OrderStatus status; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public OrderStatus getStatus() { return status; } public void setStatus(OrderStatus status) { this.status = status; } @Override public String toString() { return "Order{" + "id=" + id + ", status=" + status + '}'; } }
2.订单状态枚举常量
package com.wf.state.demo.order; /** * @ClassName OrderStatus * @Description 订单状态枚举 * @Author wf * @Date 2020/6/22 15:17 * @Version 1.0 */ public enum OrderStatus { //待支付,待发货,待收货,订单结束 WAIT_PAYMENT,WAIT_DELIVER,WAIT_RECEIVE,FINISH; }
3.订单变更行为状态
package com.wf.state.demo.order; /** * @ClassName OrderStatusChangeEvent * @Description 订单状态变更行为状态 * @Author wf * @Date 2020/6/22 15:21 * @Version 1.0 */ public enum OrderStatusChangeEvent { //支付,发货,确认收货 PAYED,DELIVER,RECEIVED; }
4.定义订单状态机配置类
package com.wf.state.demo.order; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.statemachine.StateMachineContext; import org.springframework.statemachine.StateMachinePersist; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.StateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; import org.springframework.statemachine.persist.DefaultStateMachinePersister; import org.springframework.statemachine.support.DefaultStateMachineContext; import java.util.EnumSet; /** * @ClassName OrderStatusMachineConfig * @Description 订单状态机配置类 * @Author wf * @Date 2020/6/22 15:24 * @Version 1.0 */ @Configuration @EnableStateMachine(name="orderStateMachine") public class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatus,OrderStatusChangeEvent> { /** * 配置状态,初始化状态为OrderStatus.WAIT_PAYMENT【待支付】 * @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 */ @Override 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.DELIVER) .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> stateMachineContext, Order order) throws Exception { //持久化操作略 } @Override public StateMachineContext<Object, Object> read(Order order) throws Exception { //此处直接获取order中订单状态,其实并没有进行持久化操作 return new DefaultStateMachineContext<>(order.getStatus(),null,null,null); } }); } }
5.定义listener监听器
package com.wf.state.demo.order; /** * @ClassName OrderStatusListenerImpl * @Description 监听器实现 * @Author wf * @Date 2020/6/22 15:48 * @Version 1.0 */ import org.springframework.messaging.Message; import org.springframework.statemachine.annotation.OnTransition; import org.springframework.statemachine.annotation.WithStateMachine; import org.springframework.stereotype.Component; @Component("orderStateListener") @WithStateMachine(name="orderStateMachine") public class OrderStatusListenerImpl { @OnTransition(source ="WAIT_PAYMENT", target = "WAIT_DELIVER") public boolean payTransaction(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 deliverTransaction(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 receiveTransaction(Message<OrderStatusChangeEvent> message){ Order order = (Order) message.getHeaders().get("order"); order.setStatus(OrderStatus.FINISH); System.out.println("收货,状态机反馈信息:"+message.getHeaders().toString()); return true; } }
6. 订单服务接口及实现
package com.wf.state.demo.order; import java.util.Map; /** * @ClassName IOrderService * @Description 订单服务接口 * @Author wf * @Date 2020/6/22 16:20 * @Version 1.0 */ public interface IOrderService { Order create(); Order pay(int id); Order deliver(int id); Order receive(int id); Map<Integer,Order> getOrders(); } package com.wf.state.demo.order; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.StateMachinePersist; import org.springframework.statemachine.persist.StateMachinePersister; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; /** * @ClassName OrderServiceImpl * @Description 订单服务 * @Author wf * @Date 2020/6/22 15:57 * @Version 1.0 */ @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<Integer, Order>(); 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); Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.DELIVER).setHeader("order",order).build(); if(!sendEvent(message, order)){ 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); Message message = MessageBuilder.withPayload(OrderStatusChangeEvent.RECEIVED).setHeader("order",order).build(); if(!sendEvent(message, order)){ 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 message, Order order) { boolean result = false; try{ orderStateMachine.start(); //尝试恢复状态机状态 persister.restore(orderStateMachine, order); //添加线程延时,用于线程安全测试 Thread.sleep(1000); result = orderStateMachine.sendEvent(message); //持久化状态机状态 persister.persist(orderStateMachine, order); }catch (Exception e){ e.printStackTrace(); }finally { orderStateMachine.stop(); } return result; } }
7.测试类
package com.wf.state.demo.order; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; /** * @ClassName Test * @Description 测试类 * @Author wf * @Date 2020/6/22 16:21 * @Version 1.0 */ @SpringBootApplication public class Test { public static void main(String[] args) { Thread.currentThread().setName("主线程"); ConfigurableApplicationContext context = SpringApplication.run(Test.class,args); IOrderService orderService = (IOrderService) context.getBean("orderService"); orderService.create(); orderService.create(); orderService.pay(1); new Thread("客户线程"){ @Override public void run() { orderService.deliver(1); orderService.receive(1); } }.start(); orderService.pay(2); orderService.deliver(2); orderService.receive(2); System.out.println("订单全部状态:"+orderService.getOrders()); } }
测试结果:
12.1.5.状态模式在源码中的应用
状态模式,在源码中很少用到。
12.1.5.1.在jsf中应用
依赖包:
<dependency> <groupId>com.sun.faces</groupId> <artifactId>jsf-api</artifactId> <version>1.2</version> </dependency>
jsf是一个前端框架。
Lifecycle是相当于状态顶层抽象接口,源码如下所示:
package javax.faces.lifecycle; import javax.faces.FacesException; import javax.faces.context.FacesContext; import javax.faces.event.PhaseListener; public abstract class Lifecycle { public Lifecycle() { } public abstract void addPhaseListener(PhaseListener var1); public abstract void execute(FacesContext var1) throws FacesException;//关联上下文对象 public abstract PhaseListener[] getPhaseListeners(); public abstract void removePhaseListener(PhaseListener var1); public abstract void render(FacesContext var1) throws FacesException; }
jsf是一个前端框架,它关注一个请求从页面打开,点击触发请求,到返回结果的一个过程。
PhaseId类定义多个状态常量。如下所示:
12.1.6.状态模式使用总结
12.1.6.1.状态模式与其他模式的区别
1.状态模式与责任链模式
状态模式:
强调是内部状态的一个变化,通常是种流程化的演变。
状态模式各个状态对象知道自己下一个要进入的状态对象。
责任链模式:
强调外部节点对象间的改变。
而责任链模式并不清楚其下一个节点处理对象,因为链式组装由客户端负责。
状态模式和责任链模式都能消除if分支过多的问题。但某些情况下,状态模式中的状态可以理解为责任
2.状态模式与策略模式
状态模式和策略模式的UML类图架构几乎完全一样, 但他们的应用场景是不一样的。
策略模式多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法;
而状态模式各个状态间是存在相互关系的,彼此之间在一定条件下存在自动切换状态效果,且用户无法指定状态,只能设置初始状态。
12.1.6.2.状态模式的优缺点总结
优点:
1.结构清晰:将状态独立为类, 消除了冗余的if...else或switch...case语句, 使代码更加简洁,提高系统可维护性;
2.将状态转换显式化:通常的对象内部都是使用数值类型来定义状态,状态的切换是通过赋值进行表现,不够直观;
而使用状态类,在切换状态时,是以不同的子类进行表示,转换目的更加明确;
3.状态类职责明确且具备扩展性。
缺点:
1.类膨胀:如果一个事物具备很多状态,则会造成状态类太多;
2.状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱;
3.状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,
否则无法切换到新增状态,而且修改某个状态类的行为也需要修改源代码。