23,状态模式
23.1,问题引入_APP抽奖活动
- 提供一种抽奖活动,每一次抽奖扣除用户50积分,且中奖概率为10%,奖品数量固定,送完为止
- 活动存在四种状态:
- 不能抽奖:未进行积分兑换
- 可以抽奖:已经进行积分兑换。抽奖完成后,如果未中奖,转到不能抽奖;如果中奖,转到发放奖品
- 发放奖品:对中奖用户发放奖品。奖品发送后,如果还有剩余奖品,转到不能抽奖;如果奖品已经送完,转到奖品领完
- 奖品领完:奖品全部领完
- 这种需求可以通过状态模式完成
23.2,基本介绍
- 状态模式(State Pattern):主要是解决对象在多种间转换时,需要对外输出不同行为的问题。状态和行为是一一对应的,状态之间可以相互切换
- 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
- 状态模式也是一种行为型模式
23.3,类图
State
:状态类顶层接口,定义了状态全流程的基本行为模式;再状态接口下可以加一层状态类的异常实现,并用具体状态类继承该类通过方法重写进行具体实现XXXState
:状态类具体实现类,根据各自状态对对应的方法进行实现,实现业务功能StateTypeEnum
:状态类对应枚举类,用于进行状态合法性判断Activity
:状态类的环境角色,组合State
状态类,用户进行各个状态之间切换,继续进行业务流转
23.4,代码实现
-
StateTypeEnum
:状态类枚举package com.self.designmode.state; /** * 状态模式: 抽奖流程状态枚举类 * @author PJ_ZHANG * @create 2020-12-17 17:26 **/ public enum StateTypeEnum { // 0:不能抽奖, 1:可以抽奖, 2:抽奖成功,发放奖品, 3:奖品发放结束 NON("0"), CAN("1"), PROVIDE("2"), COMPLETE("3"); private String type; private StateTypeEnum(String type) { this.type = type; } public String getStrValue() { return this.type; } }
-
State
:状态类顶层接口package com.self.designmode.state; /** * 状态模式: 顶层状态类接口 * @author PJ_ZHANG * @create 2020-12-17 17:26 **/ public interface State { /** * 扣除积分 * @param activity */ void lessPoints(Activity activity); /** * 抽奖 * @param activity */ boolean raffle(Activity activity); /** * 发放奖品类 * @param activity */ void awards(Activity activity); /** * 获取状态类型值 * @return */ String getStateType(); }
-
NonState
:具体状态类,初始状态类,不能抽奖,需要进行积分兑换package com.self.designmode.state; /** * 状态模式: 不能抽奖类 * @author PJ_ZHANG * @create 2020-12-17 17:33 **/ public class NonState implements State { @Override public void lessPoints(Activity activity) { if (!StateTypeEnum.NON.getStrValue().equals(activity.getStateType())) { return; } if (activity.getCount() <= 0) { throw new RuntimeException("兑换积分失败, 领完了..."); } // 扣除积分即可以抽奖 System.out.println("扣除了积分, 有了一次抽奖机会"); // 变更状态为可抽奖 activity.setState(new CanState()); } @Override public boolean raffle(Activity activity) { throw new RuntimeException("状态不符合...."); } @Override public void awards(Activity activity) { throw new RuntimeException("状态不符合...."); } @Override public String getStateType() { return StateTypeEnum.NON.getStrValue(); } }
-
CanState
:具体状态类,抽检类,积分兑换完成,可进行抽奖package com.self.designmode.state; import java.util.Random; /** * 状态模式: 可以抽奖类 * @author PJ_ZHANG * @create 2020-12-17 17:33 **/ public class CanState implements State { @Override public void lessPoints(Activity activity) { throw new RuntimeException("状态不符合...."); } @Override public boolean raffle(Activity activity) { if (!activity.getStateType().equals(StateTypeEnum.CAN.getStrValue())) { throw new RuntimeException("状态不符合...."); } // 进行抽奖 int data = new Random().nextInt(10); if (data == 0) { System.out.println("抽奖成功, 可以进行领奖"); activity.setState(new ProvideState()); return true; } else { System.out.println("抽奖失败, 继续花积分吧"); activity.setState(new NonState()); return false; } } @Override public void awards(Activity activity) { throw new RuntimeException("状态不符合...."); } @Override public String getStateType() { return StateTypeEnum.CAN.getStrValue(); } }
-
ProvideState
:具体状态类,奖品下发类,抽奖中奖,进行奖品下发package com.self.designmode.state; /** * 状态模式: 抽奖成功, 发放奖品 * @author PJ_ZHANG * @create 2020-12-17 17:33 **/ public class ProvideState implements State { @Override public void lessPoints(Activity activity) { throw new RuntimeException("状态不符合...."); } @Override public boolean raffle(Activity activity) { throw new RuntimeException("状态不符合...."); } @Override public void awards(Activity activity) { if (!StateTypeEnum.PROVIDE.getStrValue().equals(activity.getStateType())) { throw new RuntimeException("状态不符合...."); } System.out.println("发放奖品"); activity.subCount(); if (activity.getCount() <= 0) { activity.setState(new NonState()); } else { activity.setState(new CompleteState()); } } @Override public String getStateType() { return StateTypeEnum.PROVIDE.getStrValue(); } }
-
CompleteState
:具体状态类,奖品发放完成类,也就是状态流转结束状态package com.self.designmode.state; /** * 状态模式: 奖品发放完成类 * @author PJ_ZHANG * @create 2020-12-17 17:33 **/ public class CompleteState implements State { @Override public void lessPoints(Activity activity) { System.out.println("奖品发放完成..."); } @Override public boolean raffle(Activity activity) { throw new RuntimeException("状态不符合...."); } @Override public void awards(Activity activity) { throw new RuntimeException("状态不符合...."); } @Override public String getStateType() { return StateTypeEnum.COMPLETE.getStrValue(); } }
-
Activity
:状态流转控制类package com.self.designmode.state; /** * 状态模式: 流程控制, 抽奖流程类 * @author PJ_ZHANG * @create 2020-12-17 17:30 **/ public class Activity { private State state; private int count; public Activity(int count) { this.count = count; state = new NonState(); } /** * 扣除积分,兑换抽奖机会 */ public void lessPoints() { state.lessPoints(this); } /** * 抽奖 */ public boolean raffle() { return state.raffle(this); } /** * 发放奖品 */ public void awards() { state.awards(this); } public void setState(State state) { this.state = state; } public String getStateType() { return state.getStateType(); } public void subCount() { count--; } public int getCount() { return count; } }
-
Client
:客户端package com.self.designmode.state; /** * 状态模式, 客户端 * @author PJ_ZHANG * @create 2020-12-17 17:54 **/ public class Client { public static void main(String[] args) { Activity activity = new Activity(1); // 抽10次, 可能抽奖失败 for (int i = 0; i < 10; i++) { activity.lessPoints(); boolean result = activity.raffle(); if (result) { activity.awards(); } } } }
23.5,状态模式的注意事项和细节
- 代码具有很高的可读性,状态模式将每个状态的行为封装到一个对应的类中
- 方便维护,代码中去除了
if-else
分支,用不同的状态类将业务流转串联起来,不需要每次执行的之后进行分支判断,提高代码的容错率 - 符合OCP原则,容易对状态进行灵活变更
- 状态模式容易产生很多类,每个状态都会对应一个类,当状态过多的时候,会加大维度难度
- 应用场景:当一个事件或者对象有多种状态,且存在状态间切换,对不同的状态又存在不同的行为的时候,可以使用状态模式