前言介绍
接下里介绍的是Java 的设计模式之一:状态模式
咱们还是以一个问题进行开展,引入状态模式
咱们当初有一个抽奖APP须要编写
请编写程序实现 APP 抽奖流动 具体要求如下:
流动有四个状态: 能够抽奖、不能抽奖、发放奖品和奖品领完
每加入一次这个流动要扣除用户 50 积分,中奖概率是 10%
奖品数量固定,数量抽完就不能抽奖
请问你怎么制作这个程序?
咱们举荐你应用状态模式
一、什么是状态模式
状态模式(State Pattern):它次要用来解决对象在多种状态转换时,须要对外输入不同的行为的问题。
状态和行为是一一对应的,状态之间能够互相转换
当一个对象的外在状态扭转时,容许扭转其行为,这个对象看起来像是扭转了其类
状态模式原理类图
状态模式下的角色及职责
Context:类为环境角色用于保护 State 实例,这个实例定义以后状态
State:是形象状态角色,定义一个接口封装与 Context 的一个特点接口相干行为
ConcreteState:具体的状态角色,每个子类实现一个与 Context 的一个状态相干行为
二、应用状态模式解决问题
接下来咱们依据思路,创立不同状态的抽象类
/** *状态抽象类 *@author Administrator * */abstract class State { // 扣除积分 - 50 public abstract void deductMoney(); // 是否抽中奖品 public abstract boolean raffle(); // 发放奖品 public abstract void dispensePrize();}
接下里依据思路创立不同状态具体子类(可抽、不可抽、发放、发完)
/** * 能够抽奖的状态 * * @author Administrator */class CanRaffleState extends State { //曾经扣除了积分,不能再扣 @Override public void deductMoney() { System.out.println("曾经扣取过了积分"); } //能够抽奖, 抽完奖后,依据理论状况,改成新的状态 @Override public boolean raffle() { System.out.println("正在抽奖,请稍等!"); } // 不能发放奖品 @Override public void dispensePrize() { System.out.println("没中奖,不能发放奖品"); }}
/** * 不能抽奖状态 * * @author Administrator */class NoRaffleState extends State { // 以后状态能够扣积分 , 扣除后,将状态设置成能够抽奖状态 @Override public void deductMoney() { System.out.println("扣除 50 积分胜利,您能够抽奖了"); } // 以后状态不能抽奖 @Override public boolean raffle() { System.out.println("扣了积分能力抽奖喔!"); return false; } // 以后状态不能发奖品 @Override public void dispensePrize() { System.out.println("不能发放奖品"); }}
/** * 发放奖品的状态 * * @author Administrator */class DispenseState extends State { @Override public void deductMoney() { System.out.println("不能扣除积分"); } @Override public boolean raffle() { System.out.println("不能抽奖"); return false; } //发放奖品 @Override public void dispensePrize() { System.out.println("正在发送奖品中....."); }}
/** * 奖品发放结束状态 * 阐明,当咱们 activity 扭转成 DispenseOutState, 抽奖流动完结 * * @author Administrator */class DispenseOutState extends State { @Override public void deductMoney() { System.out.println("奖品发送完了,请下次再加入"); } @Override public boolean raffle() { System.out.println("奖品发送完了,请下次再加入"); return false; } @Override public void dispensePrize() { System.out.println("奖品发送完了,请下次再加入"); }}
接下来咱们依据思路,创立一个流动类治理不同的状态,同时初始化奖品数量、与初始化为不可抽奖的状态
/** * 抽奖流动 * * @author Administrator */class RaffleActivity { // state 示意流动以后的状态,是变动 State state = null; // 奖品数量 int count = 0; //结构器 //1. 初始化以后的状态为 noRafflleState(即不能抽奖的状态) //2. 初始化奖品的数量 public RaffleActivity(int count) { this.count = count; } //扣分, 调用以后状态的 deductMoney public void debuctMoney() { state.deductMoney(); } //抽奖 public void raffle() { // 如果以后的状态是抽奖胜利 if (state.raffle()) { //支付奖品 state.dispensePrize(); } } public State getState() { return state; } public void setState(State state) { this.state = state; } //这里请大家留神,每支付一次奖品,count-- public int getCount() { int curCount = count; count--; return curCount; } public void setCount(int count) { this.count = count; }}
接下里咱们使不同的状态类与流动类进行关联(可抽、不可抽、发放、发完)
/** * 能够抽奖的状态 * * @author Administrator */class CanRaffleState extends State { RaffleActivity activity; public CanRaffleState(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(10); // 10%中奖机会 if (num == 0) { // 改 变 活 动 状 态 为 发 放 奖 品 context activity.setState(activity.getDispenseState()); return true; } else{ System.out.println("很遗憾没有抽中奖品!"); // 扭转状态为不能抽奖 activity.setState(activity.getNoRafflleState()); return false; } } // 不能发放奖品 @Override public void dispensePrize() { System.out.println("没中奖,不能发放奖品"); }}
/** * 不能抽奖状态 * * @author Administrator */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("不能发放奖品"); }}
/** * 发放奖品的状态 * * @author Administrator */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.getNoRafflleState()); } else { System.out.println("很遗憾,奖品发送完了"); // 扭转状态为奖品发送结束, 前面咱们就不能够抽奖 activity.setState(activity.getDispensOutState()); System.out.println("抽奖流动完结"); System.exit(0); } }}
/** * 奖品发放结束状态 * 阐明,当咱们 activity 扭转成 DispenseOutState, 抽奖流动完结 * * @author Administrator */class DispenseOutState extends State { // 初始化时传入流动援用 RaffleActivity activity; public DispenseOutState(RaffleActivity activity) { this.activity = activity; } //省略其余关键性代码.....}
而流动订单则引入四种不同的状态,并且实现初始化
/** * 抽奖流动 // * * @author Administrator */class RaffleActivity { //省略其余关键性代码..... // 四个属性,示意四种状态 State noRafflleState = new NoRaffleState(this); State canRaffleState = new CanRaffleState(this); State dispenseState = new DispenseState(this); State dispensOutState = new DispenseOutState(this); //结构器 //1. 初始化以后的状态为 noRafflleState(即不能抽奖的状态) //2. 初始化奖品的数量 public RaffleActivity(int count) { this.state = getNoRafflleState(); this.count = count; } public State getNoRafflleState() { return noRafflleState; } public void setNoRafflleState(State noRafflleState) { this.noRafflleState = noRafflleState; } public State getCanRaffleState() { return canRaffleState; } public void setCanRaffleState(State canRaffleState) { this.canRaffleState = canRaffleState; } public State getDispenseState() { return dispenseState; } public void setDispenseState(State dispenseState) { this.dispenseState = dispenseState; } public State getDispensOutState() { return dispensOutState; } public void setDispensOutState(State dispensOutState) { this.dispensOutState = dispensOutState; }}
接下来咱们应用demo,领会一下这种设计模式下的抽奖流动
public static void main(String[] args) { // 创建活动对象,奖品有 1 个奖品 RaffleActivity activity = new RaffleActivity(1); // 咱们间断抽 300 次奖 for (int i = 0; i < 3; i++) { System.out.println("--------第" + (i + 1) + "次抽奖----------"); // 加入抽奖,第一步点击扣除积分 activity.debuctMoney(); // 第二步抽奖 activity.raffle(); }}运行后果如下:--------第1次抽奖----------扣除 50 积分胜利,您能够抽奖了正在抽奖,请稍等!很遗憾没有抽中奖品!--------第2次抽奖----------扣除 50 积分胜利,您能够抽奖了正在抽奖,请稍等!很遗憾没有抽中奖品!--------第3次抽奖----------扣除 50 积分胜利,您能够抽奖了正在抽奖,请稍等!很遗憾没有抽中奖品!
三、通过我的项目领会状态模式
当初有一个借贷平台,他的订单有审核-公布-抢单等等步骤,随着操作的不同,会扭转订单的状态, 我的项目中的这个模块实现就会应用到状态模式
咱们先看看借贷平台的订单流程是怎么样的呢?
有不同的审核状况
订单生成后,进行审核,通过后公布,2小时后并没有人接单则间接完结
订单生成后,进行审核,审核不通过,则间接完结
订单生成后,进行审核,通过后公布,2小时内有人接单则进入待付款状态
- 待付款操作,进行付款,实现反馈,完结订单
- 待付款操作,有人付款胜利,则其他人再接到这个订单,则失败
依据订单流程,咱们再设计一张横纵坐标示意关系
应用状态模式下的原理类图
接下来依据思路,咱们创立接口寄存不同的状态办法
/** *状态接口 *@author Administrator * */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();}
接下里咱们依据思路创立抽象类,对接口实现默认空办法
abstract class AbstractState implements State { protected static final RuntimeException EXCEPTION = new RuntimeException("操作流程不容许"); //抽象类,默认实现了 State 接口的所有办法 //该类的所有办法,其子类(具体的状态类),能够有抉择的进行重写 @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; }}
接下来咱们依据思路,让各种状态的实现类实现各自不同的状态
//已完结状态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()); } @Override public String getCurrentState() { return StateEnum.GENERATE.getValue(); }}//待付款状态class NotPayState extends AbstractState { @Override public void payOrderEvent(Context context) { context.setState(new PaidState()); } @Override public void feedBackEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.NOT_PAY.getValue(); }}//领取状态class PaidState extends AbstractState { @Override public void feedBackEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.PAID.getValue(); }}//公布状态class PublishState extends AbstractState { @Override public void acceptOrderEvent(Context context) { //把以后状态设置为 NotPayState。。。 //至于应该变成哪个状态,有流程图来决定 context.setState(new NotPayState()); } @Override public void notPeopleAcceptEvent(Context context) { context.setState(new FeedBackState()); } @Override public String getCurrentState() { return StateEnum.PUBLISHED.getValue(); }}//已审核状态class ReviewState extends AbstractState { @Override public void makePriceEvent(Context context) { context.setState(new PublishState()); } @Override public String getCurrentState() { return StateEnum.REVIEWED.getValue(); }}
接下来依据思路,创立环境上下文治理所有状态
//环境上下文class Context extends AbstractState { //以后的状态 state, 依据咱们的业务流程解决,不停的变动 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(); } public State getState() {return state;} public void setState(State state) { this.state = state;} @Override public String getCurrentState() { System.out.println("以后状态 : " + state.getCurrentState()); return state.getCurrentState(); }}
同时咱们应用枚举的形式连贯状态类
/** *状态枚举类 *@author Administrator * */enum StateEnum { //订单生成 GENERATE(1, "GENERATE"), //已审核 REVIEWED(2, "REVIEWED"), //已公布 PUBLISHED(3, "PUBLISHED"), //待付款 NOT_PAY(4, "NOT_PAY"), //已付款 PAID(5, "PAID"), //已完结 FEED_BACKED(6, "FEED_BACKED"); private int key; private String value; StateEnum(int key, String value) { this.key = key; this.value = value; } public int getKey() {return key;} public String getValue() {return value; }}
接下来咱们应用demo ,领会状态模式下的借贷平台的状态机制
public static void main(String[] args) { //创立 context 对象 Context context = new Context(); //将以后状态设置为 PublishState context.setState(new PublishState()); //将publish状态改为 --> not pay状态 context.acceptOrderEvent(context); //将not pay 状态改为--> paid状态 context.payOrderEvent(context);}运行后果如下:以后状态 : NOT_PAY以后状态 : PAID
四、状态模式的注意事项和细节
代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
不便保护。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用办法时都要判断以后是什么状态,岂但会产出很多 if-else 语句,而且容易出错
合乎“开闭准则”。容易增删状态
然而会产生很多类,每个状态都要一个对应的类,当状态过多时会产生很多类,加大保护难度
利用场景:当一个事件或者对象有很多种状态,状态之间会互相转换,对不同的状态要求有不同的行为的时候, 能够思考应用状态模式
参考资料
尚硅谷:设计模式(韩顺平老师):备忘录模式
Refactoring.Guru:《深刻设计模式》