2.7 备忘录模式
(1)概念
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。
(2)适用场景
1、需要保存/恢复数据的相关状态场景。
2、提供一个可回滚的操作。
注意事项:
1、为了符合迪米特原则,还要增加一个管理备忘录的类。
2、为了节约内存,可使用原型模式+备忘录模式。
(3)代码示例
备忘录模式使用三个类 Memento、Originator 和 CareTaker。Memento 包含了要被恢复的对象的状态。Originator 创建并在 Memento 对象中存储状态。Caretaker 对象负责从 Memento 中恢复对象的状态。
MementoPatternDemo,我们的演示类使用 CareTaker 和 Originator 对象来显示对象的状态恢复。
创建 Memento 类。
package com.alibaba.design.mementopattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-22:49 */ public class Memento { private String state; public Memento(String state){ this.state = state; } public String getState(){ return state; } }
创建 Originator 类。
package com.alibaba.design.mementopattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-22:50 */ public class Originator { private String state; public void setState(String state){ this.state = state; } public String getState(){ return state; } public Memento saveStateToMemento(){ return new Memento(state); } public void getStateFromMemento(Memento Memento){ state = Memento.getState(); } }
创建 CareTaker 类
package com.alibaba.design.mementopattern; import java.util.ArrayList; import java.util.List; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-22:50 */ public class CareTaker { private List<Memento> mementoList = new ArrayList<Memento>(); public void add(Memento state){ mementoList.add(state); } public Memento get(int index){ return mementoList.get(index); } }
package com.alibaba.design.mementopattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-22:51 */ public class MementoPatternDemo { public static void main(String[] args) { Originator originator = new Originator(); CareTaker careTaker = new CareTaker(); originator.setState("State #1"); originator.setState("State #2"); careTaker.add(originator.saveStateToMemento()); originator.setState("State #3"); careTaker.add(originator.saveStateToMemento()); originator.setState("State #4"); System.out.println("Current State: " + originator.getState()); originator.getStateFromMemento(careTaker.get(0)); System.out.println("First saved State: " + originator.getState()); originator.getStateFromMemento(careTaker.get(1)); System.out.println("Second saved State: " + originator.getState()); } }
(4)该模式在源码中的体现
可以联系Git,还有数据库事务处理等,它们都有版本记录,操作回滚的逻辑,这些都可以基于备忘录模式,搭配其他模式来优雅的实现。
在java.sql.Connection的源码中有
(5)备忘录模式的优缺点
- 优点:
1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
2、实现了信息的封装,使得用户不需要关心状态的保存细节。 - 缺点:
消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
2.8 状态模式
(1)概念
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
何时使用:代码中包含大量与对象状态有关的条件语句。
如何解决:将各种具体的状态类抽象出来。
关键代码:通常命令模式的接口中只有一个方法。而状态模式的接口中有一个或者多个方法。而且,状态模式的实现类的方法,一般返回值,或者是改变实例变量的值。也就是说,状态模式一般和对象的状态有关。实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if…else 等条件选择语句。
(2)适用场景
1、行为随状态改变而改变的场景。
2、条件、分支语句的代替者。
注意事项:在行为受状态约束的时候使用状态模式,而且状态不超过 5 个。
(3)代码示例
我们将创建一个 State 接口和实现了 State 接口的实体状态类。Context 是一个带有某个状态的类。
StatePatternDemo,我们的演示类使用 Context 和状态对象来演示 Context 在状态改变时的行为变化。
创建一个接口。
package com.alibaba.design.statepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-23:24 */ public interface State { public void doAction(Context context); }
创建 Context 类。
package com.alibaba.design.statepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-23:25 */ public class Context { private State state; public Context(){ state = null; } public void setState(State state){ this.state = state; } public State getState(){ return state; } }
创建实现接口的实体类。
- StartState
package com.alibaba.design.statepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-23:25 */ public class StartState implements State { public void doAction(Context context) { System.out.println("Player is in start state"); context.setState(this); } @Override public String toString(){ return "Start State"; } }
- StopState
package com.alibaba.design.statepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-23:26 */ public class StopState implements State { public void doAction(Context context) { System.out.println("Player is in stop state"); context.setState(this); } @Override public String toString(){ return "Stop State"; } }
使用 Context 来查看当状态 State 改变时的行为变化。
package com.alibaba.design.statepattern; /** * @author zhouyanxiang * @create 2020-08-2020/8/1-23:26 */ 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()); } }
结果输出:
(4)该模式在源码中的体现
Spring State Machine示例:
状态机(状态模式的一种应用)在工作流或游戏等各种系统中有大量使用,如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。Spring状态机帮助开发者简化状态机的开发过程,让状态机结构更加层次化。
参考链接 https://cloud.tencent.com/developer/article/1487718
(5)状态模式的优缺点
- 优点:
1、封装了转换规则。
2、枚举可能的状态,在枚举状态之前需要确定状态种类。
3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
- 缺点:
1、状态模式的使用必然会增加系统类和对象的个数。
2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
3、状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
(6) 与策略模式的比较
后续:与策略模式的比较
同:
1、子类的使用:状态和策略模式都通过状态/策略的不同派生子类来更改具体实现。
2、模式类图:状态模式和策略模式之间最大的相似性之一是它们的类图,除了类名之外,它们看起来几乎相同。这两种模式都定义了状态/策略基类,子状态/子策略都继承基类。
3、两者都遵循开闭原则:状态模式的Context是对修改关闭的,即关于状态如何被访问和使用的逻辑是固定的。但是各个状态是开放的,也就是说,可以通过扩展可以添加更多的状态。类似地,策略模式的context是对修改关闭的,但是各个策略的子类是开放可扩展的。
异:
1、模式意图:策略模式的意图或目的是拥有一系列可互换的算法,这些算法可以根据context和/或客户需求进行选择。而状态模式的目的是管理对象的状态以及对象的行为,对象的行为会随着状态的变化而变化。
2、客户端对策略/状态的感知:在策略模式实现中,所选择的策略依赖于客户端,因此客户端知道使用的是哪种策略。而在状态模式实现中,客户端与context交互以对对象进行操作,但不决定选择哪种状态。对象本身似乎根据客户端通过context进行的交互来更改其状态类。
3、context的引用:状态模式中的每个状态都持有context的引用。但是,策略模式中每个策略并不持有context的引用。
4、状态/策略之间的关系:状态模式中的不同状态彼此相关,例如作为前一个或者后一个状态等。这是因为在状态之间像有限状态机有一个流动。然而,策略模式只是从多个可用策略中选择一个策略,策略之间没有后者/前者的关系。
5、怎样做/什么&何时做:多种策略定义了做某事的多种方式。而多个状态定义要做什么,并基于状态之间的关系定义何时做。
2.9 策略模式
(1)概念
策略模式(Strategy Pattern)是指定义了算法家族、分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户。
(2)策略模式的应用场景
1、假如系统中有很多类,而他们的区别仅仅在于他们的行为不同。
2、一个系统需要动态地在几种算法中选择一种。
(3)用策略模式实现选择支付方式的业务场景
大家都知道,很多电商平台经常会有优惠活动,优惠策略会有很多种可能如:领取优惠券抵扣、返现促销、拼团优惠。下面我们用代码来模拟。
整体的类图
首先我们创建一个促销策略的抽象 PromotionStrategy:
package com.alibaba.design.strategypattern.promotion; /** * 促销策略抽象 * @author zhouyanxiang * @create 2020-07-2020/7/29-11:01 */ public interface PromotionStrategy { public void doPromotion(); }
然后分别创建优惠券抵扣策略 CouponStrategy 类、返现促销策略 CashbackStrategy类、拼团优惠策略 GroupbuyStrategy 类和无优惠策略 EmptyStrategy 类:
- CashbackStrategy类(返现促销)
package com.alibaba.design.strategypattern.promotion; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:03 */ public class CashbackStrategy implements PromotionStrategy { @Override public void doPromotion() { System.out.println("通过返现促销,消费额到达一定的额度后可以直接返现"); } }
- CouponStrategy类(优惠券促销)
package com.alibaba.design.strategypattern.promotion; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:03 */ public class CouponStrategy implements PromotionStrategy { @Override public void doPromotion() { System.out.println("通过优惠券的形式来促销"); } }
- EmptyStrategy类(无促销)
package com.alibaba.design.strategypattern.promotion; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:05 */ public class EmptyStrategy implements PromotionStrategy { @Override public void doPromotion() { System.out.println("无促销活动"); } }
- GroupbuyStrategy类(组团促销)
package com.alibaba.design.strategypattern.promotion; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:05 */ public class GroupbuyStrategy implements PromotionStrategy { @Override public void doPromotion() { System.out.println("通过拼团促销"); } }
然后创建促销活动方案 PromotionActivity 类
package com.alibaba.design.strategypattern.promotion; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:06 */ public class PromotionActivity { private PromotionStrategy promotionStrategy; public PromotionActivity(PromotionStrategy promotionStrategy){ this.promotionStrategy = promotionStrategy; } public void execute(){ promotionStrategy.doPromotion(); } }
编写客户端测试类PromotionActivityTest
package com.alibaba.design.strategypattern.test; import com.alibaba.design.strategypattern.promotion.CashbackStrategy; import com.alibaba.design.strategypattern.promotion.CouponStrategy; import com.alibaba.design.strategypattern.promotion.PromotionActivity; import com.alibaba.design.strategypattern.promotion.PromotionStrategyFactory; import org.apache.commons.lang3.StringUtils; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:23 */ public class PromotionActivityTest { public static void main(String[] args) { System.out.println("============No.1==========="); PromotionActivityTest.testPromotionActivity1(); System.out.println("============No.2==========="); PromotionActivityTest.testPromotionActivity2(); System.out.println("============No.3==========="); PromotionActivityTest.testPromotionActivity3(); } public static void testPromotionActivity1(){ PromotionActivity activity618 = new PromotionActivity(new CouponStrategy()); PromotionActivity activity1111 = new PromotionActivity(new CashbackStrategy()); activity618.execute(); activity1111.execute(); } public static void testPromotionActivity2() { PromotionActivity promotionActivity = null; String promotionKey = "COUPON"; if (StringUtils.equals(promotionKey, "COUPON")) { promotionActivity = new PromotionActivity(new CouponStrategy()); } else if (StringUtils.equals(promotionKey, "CASHBACK")) { promotionActivity = new PromotionActivity(new CashbackStrategy()); } promotionActivity.execute(); } public static void testPromotionActivity3() { String promotionKey = "GROUPBUY"; PromotionActivity promotionActivity = new PromotionActivity(PromotionStrategyFactory.getPromotionStrategy(promotionKey)); promotionActivity.execute(); } }
testPromotionActivity1()方法测试代码放到实际的业务场景其实并不实用,不能让客户自己选择不同的策略支付,而是由商家指定的方式,这样很不友好。
testPromotionActivity2()方法这样改造之后,满足了业务需求,客户可根据自己的需求选择不同的优惠策略了。不过经过一段时间的业务积累,我们的促销活动会越来越多代码将会需要更多判断逻辑可能也变得越来越复杂。这时候,我们是不需要思考代码是不是应该重构了?结合之前的单例模式和工厂模式,创建
PromotionStrategyFactory类
package com.alibaba.design.strategypattern.promotion; import java.util.HashMap; import java.util.Map; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-11:37 */ public class PromotionStrategyFactory { private static Map<String,PromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<String, PromotionStrategy>(); static { PROMOTION_STRATEGY_MAP.put(PromotionKey.COUPON,new CouponStrategy()); PROMOTION_STRATEGY_MAP.put(PromotionKey.CASHBACK,new CashbackStrategy()); PROMOTION_STRATEGY_MAP.put(PromotionKey.GROUPBUY,new GroupbuyStrategy()); } private static final PromotionStrategy NON_PROMOTION = new EmptyStrategy(); private PromotionStrategyFactory(){} public static PromotionStrategy getPromotionStrategy(String promotionKey){ PromotionStrategy promotionStrategy = PROMOTION_STRATEGY_MAP.get(promotionKey); return promotionStrategy == null ? NON_PROMOTION : promotionStrategy; } private interface PromotionKey{ String COUPON = "COUPON"; String CASHBACK = "CASHBACK"; String GROUPBUY = "GROUPBUY"; } }
代码优化之后,创建testPromotionActivity3()方法,每次上新活动,不影响原来的代码逻辑。
策略模式在 JDK 源码中的体现
首先来看一个比较常用的比较器 Comparator 接口,我们看到的一个大家常用的compare()方法,就是一个策略抽象实现:
最终根据什么去排序是由用户自己定义的。
比如,我们定义一个User类,里面包含姓名和分数两个属性
package com.alibaba.design.strategypattern.test; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-17:05 */ public class User { public String name; public int score; public User(String name, int score) { this.name =name; this.score = score; } }
那么我们重写的compare方法如果是比较的分数的话,最终将会是按照分数进行排序
package com.alibaba.design.strategypattern.test; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-17:02 */ public class UserTest implements Comparator<User> { @Override public int compare(User o1, User o2) { return o1.score-o2.score; } public static void main(String[] args) { ArrayList<User> list = new ArrayList<>(); list.add(new User("Tom",98)); list.add(new User("Jerry",95)); list.add(new User("Jane",99)); list.add(new User("Mary",100)); Collections.sort(list, new UserTest()); for(User index:list) { System.out.println(" score: " + index.score + " name : " + index.name); } } }
也可以按照name的首字母进行排序
package com.alibaba.design.strategypattern.test; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; /** * @author zhouyanxiang * @create 2020-07-2020/7/29-17:02 */ public class UserTest implements Comparator<User> { @Override public int compare(User u1, User u2) { return u1.name.compareTo(u2.name); } public static void main(String[] args) { ArrayList<User> list = new ArrayList<>(); list.add(new User("Tom",98)); list.add(new User("Jerry",95)); list.add(new User("Jane",99)); list.add(new User("Mary",100)); Collections.sort(list, new UserTest()); for(User index:list) { System.out.println(" score: " + index.score + " name : " + index.name); } } }
这就是策略模式在JDK中的应用之一。
还有一个非常典型的场景,Spring 的初始化也采用了策略模式,不同的类型的类采用不
同的初始化策略。首先有一个 InstantiationStrategy 接口,我们来看一下源码
package org.springframework.beans.factory.support; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.lang.Nullable; public interface InstantiationStrategy { Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3) throws BeansException; Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3, Constructor<?> var4, @Nullable Object... var5) throws BeansException; Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3, @Nullable Object var4, Method var5, @Nullable Object... var6) throws BeansException; }
顶层的策略抽象非常简单,但是它下面有两种策略 SimpleInstantiationStrategy 和
CglibSubclassingInstantiationStrategy,我们看一下类图:
打开类图我们还发现 CglibSubclassingInstantiationStrategy 策略类还继承了SimpleInstantiationStrategy 类,说明在实际应用中多种策略之间还可以继承使用。我们可以作为一个参考,在实际业务场景中,可以根据需要来设计。
(4) 策略模式的优缺点
- 优点:
1、策略模式符合开闭原则。
2、避免使用多重条件转移语句,如 if…else…语句、switch 语句
3、使用策略模式可以提高算法的保密性和安全性。 - 缺点:
1、客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
2、代码中会产生非常多策略类,增加维护难度。