状态模式
1、APP抽奖活动问题
1)假如每参加一次这个活动要扣除用户50积分,中奖概率是10%。
2)奖品数量固定,抽完就不能抽奖。
3)活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完。
2、状态模式基本介绍
1)状态模式(State Pattern),它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题,状态和行为是一一对应的,状态之间可以相互转换。
2)当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。
【状态模式原理】
1)Context类为环境角色,用于维护State实例,这个示例定义当前状态。
也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
2)State是抽象状态角色,定义一个接口封装与Context的一个特定接口相关行为。
3)ConcreteState具体的状态角色,每个子类实现一个与Context的一个状态相关行为。
3、应用实例代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
| public abstract class State { public abstract void deductMoney(); public abstract boolean raffle(); public abstract void dispensePrize(); }
public class RaffleActivity { State state; int count = 0; State noRaffleState = new NoRaffleState(this); State canRaffleState = new CanRaffleState(this); State dispenseState = new DispenseState(this); State dispenseOutState = new DispenseOutState(this); public RaffleActivity(int count) { this.state = getNoRaffleState(); this.count = count; } public void deductMoney() { state.deductMoney(); } public void raffle() { if(state.raffle()) { state.dispensePrize(); } } public int getCount() { return count--; } getter、setter }
public class NoRaffleState extends State { RaffleActivity activity; public NoRaffleState(RaffleActivity activity) { this.activity = activity; } @Override public void deductMoney() { System.out.println("扣除50积分成功,您可以抽奖了"); activity.setState(activity.getCanRaffleState()); } @Override public boolean raffle() { System.out.println("扣了积分才能抽奖喔!"); return false; } @Override public void dispensePrize() { System.out.println("不能发放奖品"); } }
public class CanRaffleState extends State { RaffleActivity activity; public NoRaffleState(RaffleActivity activity) { this.activity = activity; } @Override public void deductMoney() { System.out.println("已经扣除了积分,无需重复扣除"); } @Override public boolean raffle() { System.out.println("正在抽奖,请稍等!"); Random r = new Random(); int num = r.nextInt(); if(num == 0) { activity.setState(activity.getDispenseState()); return true; }else { System.out.println("很遗憾没有抽中奖品!"); activity.setState(activity.getNoRaffleState()); return false; } } @Override public void dispensePrize() { System.out.println("不能发放奖品"); } }
public class DispenseState extends State {
RaffleActivity activity; public DispenseState(RaffleActivity activity) { this.activity = activity; } @Override public void deductMoney() { System.out.println("不能扣除积分"); } @Override public boolean raffle() { System.out.println("不能抽奖"); return false; } @Override public void dispensePrize() { if(activity.getCount() > 0) { System.out.println("恭喜中奖了"); activity.setState(activity.getNoRaffleState()); }else { System.out.println("很遗憾,奖品发送完了"); activity.setState(activity.getDispenseOutState()); } } }
public class DispenseOutState extends State { RaffleActivity activity; public DispenseOutState(RaffleActivity activity) { this.activity = activity; } @Override public void deductMoney() { System.out.println("奖品发送完了,请下次再参加"); } @Override public boolean raffle() { System.out.println("奖品发送完了,请下次再参加"); return false; } @Override public void dispensePrize() { System.out.println("奖品发送完了,请下次再参加"); } }
public class Client { public static void main(String[] args) { RaffleActivity activity = new RaffleActivity(1); for(int i = 0; i < 300; i++) { System.out.println("--------第" + (i+1) + "次抽奖-------"); activity.deductMoney(); activity.raffle(); } } }
|
4、状态模式在实际项目-借贷平台 源码剖析
1)借贷平台的订单,有审核-发布-抢单等步骤,随着操作的不同,会改变订单的状态,项目中的这个模块实现就会使用到状态模式。
2)通常通过if/else判断订单的状态,从而实现不同的逻辑,伪代码如下
1 2 3 4 5 6 7
| if(审核) { }else if(发布) { }else if(接单) { }
|
3)问题分析:这类代码难以应对变化,在添加一种状态时,我们需要手动添加if/else,在添加一种功能时,要对所有的状态进行判断。因此代码会变得越来越臃肿,并且一旦没有处理某个状态,便会发生极其严重的bug,难以维护。
4)代码改造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public interface State { void checkEvent(Context context); void checkFailEvent(COntext context); void makePriceEvent(Context context); void acceptOrderEvent(Context context); void notPeopleAcceptEvent(Context context); void payOrderEvent(Context context); void orderFailureEvent(Context context); void feedBackEvent(Context context); String getCurrentState(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public abstract class AbstractState implements State { protected static final RuntimeException EXCEPTION = new RuntimeException("操作流程不允许"); @Override public void checkEvent(Context context) {throw EXCEPTION} @Override public void checkFailEvent(COntext context) {throw EXCEPTION} @Override public void makePriceEvent(Context context) {throw EXCEPTION} @Override public void acceptOrderEvent(Context context) {throw EXCEPTION} @Override public void notPeopleAcceptEvent(Context context) {throw EXCEPTION} @Override public void payOrderEvent(Context context) {throw EXCEPTION} @Override public void orderFailureEvent(Context context) {throw EXCEPTION} @Override public void feedBackEvent(Context context) {throw EXCEPTION} @Override public String getCurrentState() {throw EXCEPTION} }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| class FeedBackState extends AbstractState { @Override public String getCurrentState() { return StateEnum.FEED_BACKED.getValue(); } }
class GenerateState extends AbstractState { @Override public void checkEvent(Context context) { context.setState(new ReviewState()); } @Override public void checkFailEvent(Context context) { context.setState(new FeedBackState()); } }
class PublishState extends AbstractState { @Override public void acceptOrderEvent(Context context) { context.setState(new NotPayState()); } @Override public void notPeopleAcceptEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.PUBLISHED.getValue(); } }
省略。。。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| public class Context extends AbstractState { private State state; @Override public void checkEvent(Context context) { state.checkEvent(this); getCurrentState(); } @Override public void checkFailEvent(COntext context) { state.checkFailEvent(this); getCurrentState(); } @Override public void makePriceEvent(Context context) { state.makePriceEvent(this); getCurrentState(); } @Override public void acceptOrderEvent(Context context) { state.acceptOrderEvent(this); getCurrentState(); } @Override public void notPeopleAcceptEvent(Context context) { state.notPeopleAcceptEvent(this); getCurrentState(); } @Override public void payOrderEvent(Context context) { state.payOrderEvent(this); getCurrentState(); } @Override public void orderFailureEvent(Context context) { state.orderFailureEvent(this); getCurrentState(); } @Override public void feedBackEvent(Context context) { state.feedBackEvent(this); getCurrentState(); } @Override public String getCurrentState() { System.out.println(state.getCurrentState()); return state.getCurrentState(); } public State getState() { return state; } public void setState(State state) { this.state = state; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Client { public static void main(String[] args) { Context context = new Context(); context.setState(new PublishState()); System.out.println(context.getCurrentState()); context.acceptOrderEvent(context); context.payOrderEvent(context); try { context.checktFailEvent(context); System.out.println("流程正常.."); }catch(Exception e) { System.out.println(e.getMessage()); } } }
|
5、状态模式的注意事项和细节
1)代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中。并且可以很方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
2)方便维护。允许状态转换逻辑与状态对象合成一体,将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产生很多的if-else语句,而且容易出错。
3)会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度。
4)状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码混乱。
5)对 “开闭原则”支持较差。
6)应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式。当一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。