【设计模式——学习笔记】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),进而综合两者的内容,让知识点更加全面
相关文章:

【设计模式——学习笔记】23种设计模式——状态模式State(原理讲解+应用场景介绍+案例介绍+Java代码实现)
文章目录 案例引入介绍基本介绍登场角色应用场景 案例实现案例一类图实现 案例二:借贷平台源码剖析传统方式实现分析状态修改流程类图实现 案例三:金库警报系统系统的运行逻辑伪代码传统实现方式使用状态模式 类图实现分析问题问题一问题二 总结文章说明…...

【LeetCode每日一题】——41.缺失的第一个正数
文章目录 一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【题目提示】七【解题思路】八【时间频度】九【代码实现】十【提交结果】 一【题目类别】 哈希表 二【题目难度】 困难 三【题目编号】 41.缺失的第一个正数 四【题目描述】 给你一个…...
typedef函数代码段解释以及部分Windows下的系统函数
文章目录 1、typedef int (WINAPI* LPSDOLInitialize)(const SDOLAppInfo* pAppInfo)2、typedef int (WINAPI* LPSDOLGetModule)(REFIID riid, void** intf)3、typedef int (WINAPI* LPSDOLTerminal)();4、GetProcAddress运行时获取一个动态链接库(DLL)中…...

Typora常用手册
常用快捷键 加粗: Ctrl B 标题: Ctrl H 插入链接: Ctrl K 插入代码: Ctrl Shift C – 无法执行 行内代码: Ctrl Shift K 插入图片: Ctrl Shift I 无序列表:Ctrl Shift L – 无法执行…...

互联网发展历程:从网线不够长到中继器的引入
互联网,这个如今贯穿我们生活的无所不在的网络,其发展历程充满了无数的创新和变革。有一项看似不太起眼的技术却在互联网的发展中发挥着至关重要的作用,那就是中继器。本文将带您深入了解互联网的发展历程,探讨在网线不够长的情况…...
【Java】异常处理 之 使用SLF4J 和 Logback
使用SLF4J和Logback 前面介绍了Commons Logging 和Log4j 这一对好基友,它们一个负责充当日志 API,一个负责实现日志底层,搭配使用非常便于开发。 有的童鞋可能还听说过SLF4J和Logback。这两个东东看上去也像日志,它们又是啥&…...

C++11并发与多线程笔记 (1)
C11并发与多线程笔记(1) 1、并发、进程、线程的基本概念和综述1.1 并发1.2 可执行程序1.3 进程1.4 线程1.5 学习心得 2、并发的实现方法2.1 多进程并发2.2 多线程并发 3、C11新标准线程库 1、并发、进程、线程的基本概念和综述 1.1 并发 指在一个时间段…...

07_Hudi案例实战、Flink CDC 实时数据采集、Presto、FineBI 报表可视化等
7.第七章 Hudi案例实战 7.1 案例架构 7.2 业务数据 7.2.1 客户信息表 7.2.2 客户意向表 7.2.3 客户线索表 7.2.4 线索申诉表 7.2.5 客户访问咨询记录表 7.3 Flink CDC 实时数据采集 7.3.1 开启MySQL binlog 7.3.2 环境准备 7.3.3 实时采集数据 7.3.3.1 客户信息表 7.3.3.2 客户…...

ceph相关概念和部署
Ceph 可用于向云提供 Ceph 对象存储 平台和 Ceph 可用于提供 Ceph 块设备服务 到云平台。Ceph 可用于部署 Ceph 文件 系统。所有 Ceph 存储集群部署都从设置 每个 Ceph 节点,然后设置网络。 Ceph 存储集群需要满足以下条件:至少一个 Ceph 监控器&#x…...

Android Jetpack Compose 中的分页与缓存展示
Android Jetpack Compose 中的分页与缓存展示 在几乎任何类型的移动项目中,移动开发人员在某个时候都会处理分页数据。如果数据列表太大,无法一次从服务器检索完毕,这就是必需的。因此,我们的后端同事为我们提供了一个端点&#…...

无名管道 / 有名管道(FIFO)
根据上节所讲就可以了解到:管道其实就是实现进程间通讯IPC中的一种类型方法 基本概念(无名管道) 管道是一种最基本的IPC机制,通常指无名管道,也是UNIX系统IPC最古老的形式。管道只能作用于有血缘关系的进程之间&…...

Three.js纹理贴图
目录 Three.js入门 Three.js光源 Three.js阴影 Three.js纹理贴图 纹理是一种图像或图像数据,用于为物体的材质提供颜色、纹理、法线、位移等信息,从而实现更加逼真的渲染结果。 纹理可以应用于Three.js中的材质类型,如MeshBasicMaterial…...

1+X Web前端开发职业技能等级证书建设方案
一 、系统概述 1X Web前端开发技术是计算机类专业重要的核心课程,课程所包含的教学内容多,实践性强,并且相关技术更新快。传统的课堂讲授模式以教师为中心,学生被动式接收,难以调动学生学习的积极性和主动性。混合式教…...

Rx.NET in Action 第二章学习笔记
2 Hello, Rx 本章节涵盖的内容: 不使用Rx的工作方式向项目中添加Rx创建你的第一个Rx应用程序 Rx 的目标是协调和统筹来自社交网络、传感器、用户界面事件等不同来源的基于事件的异步计算。例如,建筑物周围的监控摄像头和移动传感器会在有人靠近建筑物时触发…...
【软件工程 | 模块耦合】什么是模块耦合及分类
概念 耦合(coupling)是对两个模块之间联接程度的一种度量。模块间的依赖程度越大,则其耦合程度也就越大; 反之,模块间的依赖程度越小,则其耦合程度也就越小。 很显然,为了使软件具有较好的可维护性和可修改性…...

OCT介绍和分类
前言:研究方向和OCT有关,为了方便以后回顾,所以整理了OCT相关的一些内容。 OCT介绍和分类 OCT介绍分类时域OCT频域OCT扫频OCT谱域OCT OCT介绍 名称:OCT、光学相干层析成像术、Optical Coherence Tomography。 概念:O…...

07-2_Qt 5.9 C++开发指南_二进制文件读写(stm和dat格式)
文章目录 1. 实例功能概述2. Qt预定义编码文件的读写2.1 保存为stm文件2.2 stm文件格式2.3 读取stm文件 3. 标准编码文件的读写3.1 保存为dat文件3.2 dat文件格式3.3 读取dat文件 4. 框架及源码4.1 可视化UI设计4.2 mainwindow.cpp 1. 实例功能概述 除了文本文件之外ÿ…...

SpringBoot复习:(41)配置文件中配置的server开头的属性是怎么配置到Servlet容器中起作用的?
ServletWebServerFactoryAutoConfiguration类: 可以看到其中使用了EnableConfigurationProperties导入了ServerProperties 而ServerProperties通过使用ConfigurationProperties注解导入了配置文件中已server开头的那些配置项。 可以看到ServletWebServerFactory定…...
深入解读网络协议:原理与重要概念
目录 TCP/IP协议 IP地址 子网掩码 DNS 网关 网络端口 TCP/IP协议 TCP/IP是互联网通信的基础协议。它由两个部分组成:TCP负责数据的可靠传输,确保数据按序到达目标;IP负责寻址和路由,确保数据在网络中正确传递。TCP/IP协议簇…...
O型圈不同类型的应用指南
O型圈因其优异的密封性能而广泛应用于各个行业和应用。它们简单、经济高效且密封可靠,下面我们了解一下适合每种应用的特定类型的O型圈。 1、汽车行业 在汽车行业中,O型圈在密封发动机部件和防止机油或冷却剂泄漏方面发挥着至关重要的作用。常见应用包…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

Razor编程中@Html的方法使用大全
文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
【实施指南】Android客户端HTTPS双向认证实施指南
🔐 一、所需准备材料 证书文件(6类核心文件) 类型 格式 作用 Android端要求 CA根证书 .crt/.pem 验证服务器/客户端证书合法性 需预置到Android信任库 服务器证书 .crt 服务器身份证明 客户端需持有以验证服务器 客户端证书 .crt 客户端身份…...
【题解-洛谷】P10480 可达性统计
题目:P10480 可达性统计 题目描述 给定一张 N N N 个点 M M M 条边的有向无环图,分别统计从每个点出发能够到达的点的数量。 输入格式 第一行两个整数 N , M N,M N,M,接下来 M M M 行每行两个整数 x , y x,y x,y,表示从 …...

Linux入门课的思维导图
耗时两周,终于把慕课网上的Linux的基础入门课实操、总结完了! 第一次以Blog的形式做学习记录,过程很有意思,但也很耗时。 课程时长5h,涉及到很多专有名词,要去逐个查找,以前接触过的概念因为时…...

作为点的对象CenterNet论文阅读
摘要 检测器将图像中的物体表示为轴对齐的边界框。大多数成功的目标检测方法都会枚举几乎完整的潜在目标位置列表,并对每一个位置进行分类。这种做法既浪费又低效,并且需要额外的后处理。在本文中,我们采取了不同的方法。我们将物体建模为单…...