【设计模式——学习笔记】23种设计模式——状态模式State(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录

  • 案例引入
  • 介绍
    • 基本介绍
    • 登场角色
    • 应用场景
  • 案例实现
    • 案例一
      • 类图
      • 实现
    • 案例二:借贷平台源码剖析
      • 传统方式实现分析
      • 状态修改流程
      • 类图
      • 实现
    • 案例三:金库警报系统
      • 系统的运行逻辑
      • 伪代码
        • 传统实现方式
        • 使用状态模式
      • 类图
      • 实现
      • 分析
      • 问题
        • 问题一
        • 问题二
  • 总结
  • 文章说明

案例引入

请编写程序完成APP抽奖活动具体要求如下:

  • 假如每参加一次这个活动要扣除用户50积分,中奖概率是10%
  • 奖品数量固定,抽完就不能抽奖
  • 活动有四个状态: 可以抽奖、不能抽奖、发放奖品和奖品领完,活动的四个状态转换关系图如下

在这里插入图片描述

一开始的状态为“不能抽奖”,当扣除50积分成功之后,状态就变成了“可以抽奖”状态

介绍

基本介绍

  • 状态模式: 它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的(如果处于A状态,就拥有A状态所拥有的行为和操作),状态之间可以相互转换
  • 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是变成了另外一个类的对象
  • 在状态模式中,使用类来表示状态,可以通过切换类来改变对象的状态,当需要增加新的类时,也只需要增加新的类即可

登场角色

在这里插入图片描述

  • 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;}/*** 当前状态可以扣积分,扣除后,将状态设置成可以抽奖状态*/@Overridepublic void deductMoney() {System.out.println("扣除50积分成功,您可以抽奖了");activity.setState(activity.getCanRaffleState());}/*** 当前状态不能抽奖* @return*/@Overridepublic boolean raffle() {System.out.println("扣了积分才能抽奖喔!");return false;}/*** 当前状态不能发奖品*/@Overridepublic 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;}/*** 已经扣除了积分,不能再扣*/@Overridepublic void deductMoney() {System.out.println("已经扣取过了积分");}/*** 可以抽奖, 抽完奖后,根据实际情况,改成新的状态** @return*/@Overridepublic boolean raffle() {System.out.println("正在抽奖,请稍等!");Random r = new Random();int num = r.nextInt(10);// 10%中奖机会if (num == 0) {// 改变活动状态为发放奖品 contextactivity.setState(activity.getDispenseState());return true;} else {System.out.println("很遗憾没有抽中奖品!");// 改变状态为不能抽奖activity.setState(activity.getNoRafflleState());return false;}}/*** 不能发放奖品*/@Overridepublic 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;}@Overridepublic void deductMoney() {System.out.println("不能扣除积分");}@Overridepublic boolean raffle() {System.out.println("不能抽奖");return false;}//发放奖品@Overridepublic 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;}@Overridepublic void deductMoney() {System.out.println("奖品发送完了,请下次再参加");}@Overridepublic boolean raffle() {System.out.println("奖品发送完了,请下次再参加");return false;}@Overridepublic 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("操作流程不允许");@Overridepublic void checkEvent(Context context) {throw EXCEPTION;}@Overridepublic void checkFailEvent(Context context) {throw EXCEPTION;}@Overridepublic void makePriceEvent(Context context) {throw EXCEPTION;}@Overridepublic void acceptOrderEvent(Context context) {throw EXCEPTION;}@Overridepublic void notPeopleAcceptEvent(Context context) {throw EXCEPTION;}@Overridepublic void payOrderEvent(Context context) {throw EXCEPTION;}@Overridepublic void orderFailureEvent(Context context) {throw EXCEPTION;}@Overridepublic void feedBackEvent(Context context) {throw EXCEPTION;}
}

【所有的具体状态类都在这个文件里面】

package com.atguigu.state.money;/*** 反馈状态*/
class FeedBackState extends AbstractState {@Overridepublic String getCurrentState() {return StateEnum.FEED_BACKED.getValue();}
}/*** 通用状态*/
class GenerateState extends AbstractState {@Overridepublic void checkEvent(Context context) {context.setState(new ReviewState());}@Overridepublic void checkFailEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return StateEnum.GENERATE.getValue();}
}/*** 未支付状态*/
class NotPayState extends AbstractState {@Overridepublic void payOrderEvent(Context context) {context.setState(new PaidState());}@Overridepublic void feedBackEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return StateEnum.NOT_PAY.getValue();}
}/*** 已支付状态*/
class PaidState extends AbstractState {@Overridepublic void feedBackEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return StateEnum.PAID.getValue();}
}/*** 发布状态*/
class PublishState extends AbstractState {@Overridepublic void acceptOrderEvent(Context context) {//接受订单成功,把当前状态设置为NotPayState//至于实际上应该变成哪个状态,由流程图来决定context.setState(new NotPayState());}@Overridepublic void notPeopleAcceptEvent(Context context) {context.setState(new FeedBackState());}@Overridepublic String getCurrentState() {return StateEnum.PUBLISHED.getValue();}
}/*** 回顾状态*/
class ReviewState extends AbstractState {@Overridepublic void makePriceEvent(Context context) {context.setState(new PublishState());}@Overridepublic String getCurrentState() {return StateEnum.REVIEWED.getValue();}}

【环境上下文】

package com.atguigu.state.money;/*** 环境上下文*/
public class Context extends AbstractState{/*** 当前的状态 state, 根据我们的业务流程处理,不停的变化*/private State state;@Overridepublic void checkEvent(Context context) {state.checkEvent(this);getCurrentState();}@Overridepublic void checkFailEvent(Context context) {state.checkFailEvent(this);getCurrentState();}@Overridepublic void makePriceEvent(Context context) {state.makePriceEvent(this);getCurrentState();}@Overridepublic void acceptOrderEvent(Context context) {state.acceptOrderEvent(this);getCurrentState();}@Overridepublic void notPeopleAcceptEvent(Context context) {state.notPeopleAcceptEvent(this);getCurrentState();}@Overridepublic void payOrderEvent(Context context) {state.payOrderEvent(this);getCurrentState();}@Overridepublic void orderFailureEvent(Context context) {state.orderFailureEvent(this);getCurrentState();}@Overridepublic void feedBackEvent(Context context) {state.feedBackEvent(this);getCurrentState();}public State getState() {return state;}public void setState(State state) {this.state = state;}@Overridepublic 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();//将当前状态设置为 PublishStatecontext.setState(new PublishState());System.out.println(context.getCurrentState());//        //publish --> not paycontext.acceptOrderEvent(context);
//        //not pay --> paidcontext.payOrderEvent(context);
//        // 失败, 检测失败时,会抛出异常
//        try {
//         context.checkFailEvent(context);
//         System.out.println("流程正常..");
//    } catch (Exception e) {
//       System.out.println(e.getMessage());
//    }}}

【运行】

当前状态 : PUBLISHED
PUBLISHED
当前状态 : NOT_PAY
当前状态 : PAIDProcess 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());//  配置textClockadd(textClock, BorderLayout.NORTH);textClock.setEditable(false);// 配置textScreenadd(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方法,这种方式既有优点又有缺点:

  • 优点:当我们想知道“什么时候从DayState的类变化为其他状态”,只需要查看DayState类即可
  • 缺点:每个ConcreteState角色都需要知道其他ConcreteState角色的方法,各个类之间的依赖关系较强,如果删除了一个ConcreteState类,就需要修改其他的ConcreteState类

除了这种实现方式之外,也可以将所有的状态迁移交给扮演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 "[白天]";}
}

夜间状态也需要修改对应的状态迁移方法,和白天状态类似,这里就不再展示了

总结

【优点】

  • 代码有很强的可读性,状态模式将每个状态的行为封装到对应的一个类中方便维护
  • 将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句,而且容易出错
  • 符合“开闭原则”,容易增删状态,只需要增删一个ConcreteState的类,然后修改负责状态迁移的类即可。如果使用的是传统方式,新增一个状态,就需要增加很多的判断语句
  • 使用“分而治之”的思想,将多个状态分开来,每个类只需要根据当前状态来写代码即可,不需要在执行事件之前写复杂的条件分支语句
  • 如果需要增加依赖于状态的处理方法,只需要在State接口中增加新的方法,并让所有的ConcreteState类实现这个方法,虽然修改量较大,但是开发者肯定不会忘记去实现这个方法,因为不实现,编译就会报错

【缺点】

  • 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度

文章说明

  • 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
  • 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/91218.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于飞桨图学习框架实现的城市地点动态关系挖掘

李双利 飞桨开发者技术专家&#xff08;PPDE&#xff09;&#xff0c;百度研究院商业智能实验室研究实习生&#xff0c;中国科学技术大学在读博士生。 主要进行时空数据挖掘和图深度学习的相关研究工作。曾获2021年百度研究院年度优秀实习生&#xff0c;有多篇基于飞桨完成的…

error_Network Error

此页面为订单列表&#xff0c;是混合开发(页面嵌入在客户端中) 此页面为订单列表&#xff0c;此需求在开发时后端先将代码发布在测试环境&#xff0c;我在本地调试时调用的后端接口进行联调没有任何问题。 此后我将代码发布在测试环境&#xff0c;在app中打开页面&#xff0c…

smardaten实战丨谁说无代码不能开发出漂亮的门户首页?

一、需求背景 门户首页对于一个公司或组织来说是一个极其重要的网站页面&#xff0c;它可以作为访问者了解和获取相关信息的入口&#xff0c;同时也是展示品牌形象和吸引目标受众的重要工具。 开发一个门户首页需要开发团队在向访问者展示关于公司或组织基本信息的基础上&…

Qt自定义对话框

介绍 自定义框主要通过对现有对话框QDialog类的派生&#xff0c;根据需求编写成员函数、重载信号函数、槽函数&#xff0c;进而实现在主QWidget中点击某个按钮后&#xff0c;一个对话框的弹出 流程 简化创建派生类 最后点击完成即可。 自定义ui界面&#xff0c;编写成员函数…

Netty:用ByteBufUtil的函数将字节数组、或者ByteBuf的内容转换为十六进制表示的字符串

用ByteBufUtil的hexDump(byte[] array)函数将字节数组的内容转换为十六进制表示的字符串 package com.thb;import io.netty.buffer.ByteBufUtil;public class Demo {public static void main(String[] args) {byte[] b new byte[] {0x68, 0x16, 0x03, 0x04, (byte)0xae};Stri…

ASR 语音识别接口封装和分析

这个文档主要是介绍一下我自己封装了 6 家厂商的短语音识别和实时流语音识别接口的一个包&#xff0c;以及对这些接口的一个对比。分别是&#xff0c;阿里&#xff0c;快商通&#xff0c;百度&#xff0c;腾讯&#xff0c;科大&#xff0c;字节。 zxmfke/asrfactory (github.c…

前后端分离------后端创建笔记(07)表单验证

1、我输入数据&#xff0c;然后关闭&#xff0c;重新打开会发现残存的数据仍然保留着 2、点了这个x号&#xff0c;数据就全部被清理了 3、点这三个地方&#xff0c;数据全部都清理掉 4、这里先写一个方法 4.1 定义一个方法 4.2 这里表单的数据在哪里&#xff0c;就是这个 4.3 …

Qt扫盲-QWidget理论使用总结

QWidget理论使用总结 一、概述二、顶层 控件 和子 控件三、复合控件四、自定义控件和绘制五、大小提示和大小策略六、事件七、一组函数和属性八、QWidget样式表九、透明度和双缓冲十、创建半透明窗口 一、概述 widget 是用户界面的最小单位&#xff1a;它从window系统接收鼠标…

Unity游戏源码分享-精品即时战略游戏_官网60美刀素材

Unity游戏源码分享-精品即时战略游戏_官网60美刀素材 下载地址&#xff1a;https://download.csdn.net/download/Highning0007/88204017

Jmeter-压测时接口按照顺序执行-临界部分控制器

文章目录 临界部分控制器存在问题 临界部分控制器 在进行压力测试时&#xff0c;需要按照顺序进行压测&#xff0c;比如按照接口1、接口2、接口3、接口4 进行执行 查询结果是很混乱的&#xff0c;如果请求次数少&#xff0c;可能会按照顺序执行&#xff0c;但是随着次数增加&a…

《零基础实践深度学习》(第2版)学习笔记,(二)机器学习和深度学习综述

文章目录 1. 人工智能、机器学习、深度学习的关系2. 机器学习2.1 实现原理2.2 如何实施 3. 深度学习神经网络核心概念 1. 人工智能、机器学习、深度学习的关系 **人工智能&#xff08;Artificial Intelligence&#xff0c;AI&#xff09;**是研发用于模拟、延伸和扩展人的智能…

helm安装harbor + nerdctl 制作push 镜像

参考 文章&#xff1a;Helm部署Harbor_helm harbor_风向决定发型丶的博客-CSDN博客 安装好后使用 nerd containerd对接harbor_containerd 容器 insecure-registries 配置_柠是柠檬的檬的博客-CSDN博客 推送镜像 Containerd 对接私有镜像仓库 Harbor - 知乎 接下来我们来…

“一日之际在于晨”,欢迎莅临WAVE SUMMIT上午场:Arm 虚拟硬件早餐交流会

8月16日&#xff0c;盛夏的北京将迎来第九届WAVE SUMMIT深度学习开发者大会。在峰会主论坛正式开启前&#xff0c;让我们先用一份精美的元气早餐&#xff0c;和一场“Arm虚拟硬件交流会”&#xff0c;唤醒各位开发小伙伴的开发魂&#xff01; 8月16日&#xff0c;WAVE SUMMIT大…

【注解使用】使用@Autowired后提示:Field injection is not recommended(Spring团队不推荐使用Field注入)

问题发生场景&#xff1a; 在使用 IDEA 开发 SpringBoot 项目时&#xff0c;在 Controller 类中使用注解 Autowired 注入一个依赖出现了警告提示&#xff0c;查看其他使用该注解的地方同样出现了警告提示。这是怎么回事&#xff1f;由于先去使用了SpringBoot并没有对Spring进行…

三、性能测试场景设计

性能测试场景设计 一、引言&#xff1a;如果公司要求你去做性能测试&#xff0c;遇到这些场景&#xff0c;我们要如何设计&#xff1f;二、6种常见设计方法1、普通性能场景设计2、负载测试性能场景3、压力测试场景4、面向目标性能场景 一、引言&#xff1a;如果公司要求你去做性…

W6100-EVB-PICO 做UDP Server进行数据回环测试(七)

前言 前面我们用W6100-EVB-PICO 开发板在TCP Client和TCP Server模式下&#xff0c;分别进行数据回环测试&#xff0c;本章我们将用开发板在UDP Server模式下进行数据回环测试。 UDP是什么&#xff1f;什么是UDP Server&#xff1f;能干什么&#xff1f; UDP (User Dataqram P…

海量数据迁移,亚马逊云科技云数据库服务为大库治理提供新思路

1.背景 目前&#xff0c;文档型数据库由于灵活的schema和接近关系型数据库的访问特点&#xff0c;被广泛应用&#xff0c;尤其是游戏、互联网金融等行业的客户使用MongoDB构建了大量应用程序&#xff0c;比如游戏客户用来处理玩家的属性信息&#xff1b;又如股票APP用来存储与时…

Jmeter常用功能-参数化介绍

JMeter也有像LR中的参数化&#xff0c;本篇就来介绍下JMeter的参数化如何去实现。 参数化&#xff1a;录制脚本中有登录操作&#xff0c;需要输入用户名和密码&#xff0c;假如系统不允许相同的用户名和密码同时登录&#xff0c;或者想更好的模拟多个用户来登录系统。 这个时…

RabbitMQ简单使用

RabbitMq是一个消息中间件&#xff1a;它接收消息、转发消息。你可以把它理解为一个邮局&#xff1a;当你向邮箱里寄出一封信后&#xff0c;邮递员们就能最终将信送到收信人手中。 RabbitMq、消息相关术语如下&#xff1a; 生产者&#xff1a;生产者只发送消息&#xff0c;发…

React(5)

1.受控组件案例 1.1之前的影院案例改写 import React, { Component } from react import axios from axios import BetterScroll from better-scroll import ./css/02_tab.cssexport default class Cinema extends Component {constructor() {super();this.state {cinemaLis…