赞
踩
请编写程序完成APP抽奖活动具体要求如下:
一开始的状态为“不能抽奖”,当扣除50积分成功之后,状态就变成了“可以抽奖”状态
Context(上下文)
:用于维护State实例, 根据state的不同,实例对应的ConcreteState类也不同,这样子State对象的方法也不同State(状态)
:抽象状态角色,定义多个接口ConcreteState(具体状态)
:是具体状态角色,根据自身的状态来实现State接口的方法Activity类含有所有的状态对象,各个状态子类也含有Activity对象
【抽象状态类:State】
package com.atguigu.state; /** * 状态抽象类 * * @author Administrator */ public abstract class State { /** * 扣除积分 - 50 */ public abstract void deductMoney(); /** * 是否抽中奖品 * * @return */ public abstract boolean raffle(); /** * 发放奖品 */ public abstract void dispensePrize(); }
【不能抽奖状态】
package com.atguigu.state; /** * 不能抽奖状态 * @author Administrator * */ 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()); } /** * 当前状态不能抽奖 * @return */ @Override public boolean raffle() { System.out.println("扣了积分才能抽奖喔!"); return false; } /** * 当前状态不能发奖品 */ @Override public void dispensePrize() { System.out.println("不能发放奖品"); } }
【可以抽奖的状态】
package com.atguigu.state; import java.util.Random; /** * 可以抽奖的状态 * * @author Administrator */ public class CanRaffleState extends State { RaffleActivity activity; public CanRaffleState(RaffleActivity activity) { this.activity = activity; } /** * 已经扣除了积分,不能再扣 */ @Override public void deductMoney() { System.out.println("已经扣取过了积分"); } /** * 可以抽奖, 抽完奖后,根据实际情况,改成新的状态 * * @return */ @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("没中奖,不能发放奖品"); } }
【发放奖品的状态】
package com.atguigu.state; /** * 发放奖品的状态 * * @author Administrator */ 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.getNoRafflleState()); } else { System.out.println("很遗憾,奖品发送完了"); // 改变状态为奖品发送完毕, 后面我们就不可以抽奖 activity.setState(activity.getDispensOutState()); //System.out.println("抽奖活动结束"); //System.exit(0); } } }
【奖品发放完毕状态】
package com.atguigu.state; /** * 奖品发放完毕状态 * 说明,当我们activity 改变成 DispenseOutState, 抽奖活动结束 * * @author Administrator */ 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("奖品发送完了,请下次再参加"); } }
【运行】
--------第1次抽奖---------- 扣除50积分成功,您可以抽奖了 正在抽奖,请稍等! 很遗憾没有抽中奖品! --------第2次抽奖---------- 扣除50积分成功,您可以抽奖了 正在抽奖,请稍等! 很遗憾没有抽中奖品! --------第3次抽奖---------- 扣除50积分成功,您可以抽奖了 正在抽奖,请稍等! 很遗憾没有抽中奖品! --------第4次抽奖---------- 扣除50积分成功,您可以抽奖了 正在抽奖,请稍等! 很遗憾没有抽中奖品! --------第5次抽奖---------- 扣除50积分成功,您可以抽奖了 正在抽奖,请稍等! 很遗憾没有抽中奖品! --------第6次抽奖---------- 扣除50积分成功,您可以抽奖了 正在抽奖,请稍等! 很遗憾没有抽中奖品! --------第7次抽奖---------- 扣除50积分成功,您可以抽奖了 正在抽奖,请稍等! 恭喜中奖了 --------第8次抽奖---------- 扣除50积分成功,您可以抽奖了 正在抽奖,请稍等! 很遗憾没有抽中奖品! --------第9次抽奖---------- 扣除50积分成功,您可以抽奖了 正在抽奖,请稍等! 很遗憾,奖品发送完了 --------第10次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第11次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第12次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第13次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第14次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第15次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第16次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第17次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第18次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第19次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第20次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第21次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第22次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第23次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第24次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第25次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第26次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第27次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第28次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第29次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 --------第30次抽奖---------- 奖品发送完了,请下次再参加 奖品发送完了,请下次再参加 Process finished with exit code 0
通过if/else判断订单的状态,从而实现不同的逻辑
【分析】
这类代码难以应对变化,在添加一种状态时,我们需要手动添加if/else,在添加一种功能时,要对所有的状态进行判断。因此代码会变得越来越臃肿,并且一旦没有处理某个状态便会发生极其严重的BUG,难以维护
【改进】
借贷平台的订单,有审核-发布-抢单
等等步骤,随着操作的不同,会改变订单的状态,项目中的这个模块实现就会使用到状态模式
【状态枚举类:StateEnum】
package com.atguigu.state.money; /** * 状态枚举类 * @author Administrator * */ public 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;} }
【状态接口:State】
package com.atguigu.state.money; /** * 状态接口 * @author Administrator * */ 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(); }
【抽象状态类】
使用抽象状态类来默认实现方法之后,具体的状态类(其子类)可以只选择所需要的方法来进行重写
package com.atguigu.state.money; /** * 抽象类,默认实现了 State 接口的所有方法 * 该类的所有方法,其子类(具体的状态类),可以有选择的进行重写 */ 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; } }
【所有的具体状态类都在这个文件里面】
package com.atguigu.state.money; /** * 反馈状态 */ 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(); } }
【环境上下文】
package com.atguigu.state.money; /** * 环境上下文 */ public 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(); } }
【主类】
package com.atguigu.state.money; /** * 测试类 */ public class ClientTest { public static void main(String[] args) { //创建context 对象 Context context = new Context(); //将当前状态设置为 PublishState context.setState(new PublishState()); System.out.println(context.getCurrentState()); // //publish --> not pay context.acceptOrderEvent(context); // //not pay --> paid context.payOrderEvent(context); // // 失败, 检测失败时,会抛出异常 // try { // context.checkFailEvent(context); // System.out.println("流程正常.."); // } catch (Exception e) { // System.out.println(e.getMessage()); // } } }
【运行】
当前状态 : PUBLISHED
PUBLISHED
当前状态 : NOT_PAY
当前状态 : PAID
Process finished with exit code 0
【状态接口】
package com.atguigu.state.Sample; public interface State { /** * 设置时间 * * @param context * @param hour */ public abstract void doClock(Context context, int hour); /** * 使用金库 * * @param context */ public abstract void doUse(Context context); /** * 按下警铃 * * @param context */ public abstract void doAlarm(Context context); /** * 正常通话 * * @param context */ public abstract void doPhone(Context context); }
【白天状态】
package com.atguigu.state.Sample; /** * 表示白天的状态 */ public class DayState implements State { /** * 每个状态都是一个类,如果每次改变状态都需要生成一个新的实例的话,比较浪费内存和时间,所以在这里使用单例模式 */ private static DayState singleton = new DayState(); /** * 构造函数的可见性是private */ private DayState() { } /** * 获取唯一实例 * @return */ public static State getInstance() { return singleton; } /** * 设置时间 * @param context * @param hour */ public void doClock(Context context, int hour) { if (hour < 9 || 17 <= hour) { // 如果时间是晚上,切换到夜间状态 context.changeState(NightState.getInstance()); } } /** * 使用金库 * @param context */ public void doUse(Context context) { context.recordLog("使用金库(白天)"); } /** * 按下警铃 * @param context */ public void doAlarm(Context context) { context.callSecurityCenter("按下警铃(白天)"); } /** * 正常通话 * @param context */ public void doPhone(Context context) { context.callSecurityCenter("正常通话(白天)"); } /** * 显示表示类的文字 * @return */ public String toString() { return "[白天]"; } }
【夜间状态】
package com.atguigu.state.Sample; public class NightState implements State { private static NightState singleton = new NightState(); /** * 构造函数的可见性是private */ private NightState() { } /** * 获取唯一实例 * @return */ public static State getInstance() { return singleton; } /** * 设置时间 * @param context * @param hour */ public void doClock(Context context, int hour) { if (9 <= hour && hour < 17) { context.changeState(DayState.getInstance()); } } /** * 使用金库 * @param context */ public void doUse(Context context) { context.callSecurityCenter("紧急:晚上使用金库!"); } /** * 按下警铃 * @param context */ public void doAlarm(Context context) { context.callSecurityCenter("按下警铃(晚上)"); } /** * 正常通话 * @param context */ public void doPhone(Context context) { context.recordLog("晚上的通话录音"); } /** * 显示表示类的文字 * @return */ public String toString() { return "[晚上]"; } }
【Context接口】
package com.atguigu.state.Sample; public interface Context { /** * 设置时间 * * @param hour */ public abstract void setClock(int hour); /** * 改变状态 * * @param state */ public abstract void changeState(State state); /** * 联系警报中心 * * @param msg */ public abstract void callSecurityCenter(String msg); /** * 在警报中心留下记录 * * @param msg */ public abstract void recordLog(String msg); }
【Context角色:SafeFrame】
在这个实例程序中,Context角色的作用被Context接口和SafeFrame类分担了。Context接口定义了供外部调用者使用状态模式的接口,而SafeFrame类持有表示当前状态的ConcreteState角色
package com.atguigu.state.Sample; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class SafeFrame extends Frame implements ActionListener, Context { /** * 显示当前时间 */ private TextField textClock = new TextField(60); /** * 显示警报中心的记录 */ private TextArea textScreen = new TextArea(10, 60); /** * 金库使用按钮 */ private Button buttonUse = new Button("使用金库"); /** * 按下警铃按钮 */ private Button buttonAlarm = new Button("按下警铃"); /** * 正常通话按钮 */ private Button buttonPhone = new Button("正常通话"); /** * 结束按钮 */ private Button buttonExit = new Button("结束"); /** * 金库当前的状态 */ private State state = DayState.getInstance(); /** * 构造函数 * * @param title */ public SafeFrame(String title) { super(title); setBackground(Color.lightGray); setLayout(new BorderLayout()); // 配置textClock add(textClock, BorderLayout.NORTH); textClock.setEditable(false); // 配置textScreen add(textScreen, BorderLayout.CENTER); textScreen.setEditable(false); // 为界面添加按钮 Panel panel = new Panel(); panel.add(buttonUse); panel.add(buttonAlarm); panel.add(buttonPhone); panel.add(buttonExit); // 配置界面 add(panel, BorderLayout.SOUTH); // 显示 pack(); show(); // 设置监听器 buttonUse.addActionListener(this); buttonAlarm.addActionListener(this); buttonPhone.addActionListener(this); buttonExit.addActionListener(this); } /** * 按钮被按下后,该方法会被调用 * * @param e */ public void actionPerformed(ActionEvent e) { System.out.println(e.toString()); if (e.getSource() == buttonUse) { // 金库使用按钮,并不需要去判断状态,直接调用即可 state.doUse(this); } else if (e.getSource() == buttonAlarm) { // 按下警铃按钮 state.doAlarm(this); } else if (e.getSource() == buttonPhone) { // 正常通话按钮 state.doPhone(this); } else if (e.getSource() == buttonExit) { // 结束按钮 System.exit(0); } else { System.out.println("?"); } } /** * 设置时间 * * @param hour */ public void setClock(int hour) { String clockstring = "现在时间是"; if (hour < 10) { clockstring += "0" + hour + ":00"; } else { clockstring += hour + ":00"; } System.out.println(clockstring); // 将当前时间显示在界面的上方 textClock.setText(clockstring); // 进行当前状态下的处理 state.doClock(this, hour); } /** * 改变状态 * * @param state */ public void changeState(State state) { System.out.println("从" + this.state + "状態变为了" + state + "状态。"); this.state = state; } /** * 联系警报中心 * * @param msg */ public void callSecurityCenter(String msg) { textScreen.append("call! " + msg + "\n"); } /** * 在警报中心留下记录 * * @param msg */ public void recordLog(String msg) { textScreen.append("record ... " + msg + "\n"); } }
【运行】
package com.atguigu.state.Sample; public class Main { public static void main(String[] args) { SafeFrame frame = new SafeFrame("State Sample"); while (true) { for (int hour = 0; hour < 24; hour++) { // 设置时间 frame.setClock(hour); try { Thread.sleep(1000); } catch (InterruptedException e) { } } } } }
【运行】
现在时间是00:00 从[白天]状態变为了[晚上]状态。 现在时间是01:00 现在时间是02:00 java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=使用金库,when=1691920919394,modifiers=] on button0 现在时间是03:00 java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=按下警铃,when=1691920920040,modifiers=] on button1 java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=正常通话,when=1691920920824,modifiers=] on button2 现在时间是04:00 现在时间是05:00 java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=按下警铃,when=1691920922071,modifiers=] on button1 java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=使用金库,when=1691920922626,modifiers=] on button0 现在时间是06:00 现在时间是07:00 现在时间是08:00 java.awt.event.ActionEvent[ACTION_PERFORMED,cmd=使用金库,when=1691920925446,modifiers=] on button0 现在时间是09:00 从[晚上]状態变为了[白天]状态。 现在时间是10:00 现在时间是11:00 现在时间是12:00 现在时间是13:00 现在时间是14:00 现在时间是15:00 Disconnected from the target VM, address: '127.0.0.1:1966', transport: 'socket' Process finished with exit code 130
上面的实现方式中,由具体状态类来实际调用方法切换到另一个状态,如DayState类的doClock方法,这种方式既有优点又有缺点:
除了这种实现方式之外,也可以将所有的状态迁移交给扮演Context角色的类来负责,这样可以提高ConcreteState角色的独立性,程序的整体结构也会更加清晰,当然,这样做需要Context角色知道所有的ConcreteState角色,可以使用中介者模式来改进
问:将Context定义为抽象类而非接口,然后让Context类持有state字段这样更符合状态模式的设计思想。但是在示例程序中我们并没有这么做,而是将Context角色定义为Context接口,让SafeFrame类持有state字段,请问这是为什么呢?
答:Java中只能单一继承,所以如果将Context角色定义为类,那么由于SafeFrame类已经是Frame类的子类了,它将无法再继承Context 类。不过,如果另外编写一个Context类的子类,并将它的实例保存在SafeFrame类的字段中那么通过将处理委托给这个实例是可以实现上述问题的需求的。
请在示例程序中增加一个新的“紧急情况”状态。不论是什么时间,只要处于“紧急情况”下,就向警报中心通知紧急情况
【增加一个紧急状态类】
package com.atguigu.state.A4; public class UrgentState implements State { private static UrgentState singleton = new UrgentState(); private UrgentState() { } public static State getInstance() { return singleton; } public void doClock(Context context, int hour) { // 设置时间 // 在设置时间处理中什么都不做 } public void doUse(Context context) { // 使用金库 context.callSecurityCenter("紧急:紧急时使用金库!"); } public void doAlarm(Context context) { // 按下警铃 context.callSecurityCenter("按下警铃(紧急时)"); } public void doPhone(Context context) { // 正常通话 context.callSecurityCenter("正常通话(紧急时)"); } public String toString() { // 显示字符串 return "[紧急时]"; } }
【修改其他状态的状态迁移方法】
package com.atguigu.state.A4; public class DayState implements State { private static DayState singleton = new DayState(); private DayState() { } public static State getInstance() { return singleton; } public void doClock(Context context, int hour) { if (hour < 9 || 17 <= hour) { context.changeState(NightState.getInstance()); } } public void doUse(Context context) { context.recordLog("使用金库(白天)"); } public void doAlarm(Context context) { context.callSecurityCenter("按下警铃(白天)"); // 只需要看这里就行,一旦按下紧铃,就会进入到紧急状态 context.changeState(UrgentState.getInstance()); } public void doPhone(Context context) { context.callSecurityCenter("正常通话(白天)"); } public String toString() { return "[白天]"; } }
夜间状态也需要修改对应的状态迁移方法,和白天状态类似,这里就不再展示了
【优点】
【缺点】
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。