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

news2024/11/23 1:39:07

文章目录

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

案例引入

请编写程序完成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;
    }

    /**
     * 当前状态可以扣积分,扣除后,将状态设置成可以抽奖状态
     */
    @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方法,这种方式既有优点又有缺点:

  • 优点:当我们想知道“什么时候从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.coloradmin.cn/o/875340.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

element-ui的el-dialog,简单的封装。

el-dialog是使用率很高的组件 使用el-dialog很多都是按照文档的例子&#xff0c;用一个变量控制是否显示&#xff0c;再来一个变量控制标题。 如果我这个对话框多个地方使用的话还要创建多个变量&#xff0c;甚至关闭之后还要清空一些变量&#xff0c;应该可以简化一点。我写…

数据结构:力扣OJ题(每日一练)

目录 题一&#xff1a;环形链表 思路一&#xff1a; 题二&#xff1a;复制带随机指针的链表 思路一&#xff1a; 本人实力有限可能对一些地方解释的不够清晰&#xff0c;可以自己尝试读代码&#xff0c;望海涵&#xff01; 题一&#xff1a;环形链表 给定一个链表的头节点…

奥威BI数据可视化工具:报表就是平台,随时自助分析

别的数据可视化工具&#xff0c;报表就只是报表&#xff0c;而奥威BI数据可视化工具&#xff0c;一张报表就约等于一个平台&#xff0c;可随时展开多维动态自助分析&#xff0c;按需分析&#xff0c;立得数据信息。 奥威BI是一款多维立体分析数据的数据可视化工具。它可以帮助…

Camtasia2023最新专业的电脑屏幕录制和视频剪辑软件

Camtasia专业的屏幕录制和视频剪辑软件3000多万专业人士在全球范围内使用Camtasia展示产品&#xff0c;教授课程&#xff0c;培训他人&#xff0c;以更快的速度和更吸引人的方式进行沟通和屏幕分享。使您在Windows和&#xff01;Camtasia是一款功能强大的屏幕录制和视频编辑软件…

办理流量卡也是有条件的,这五种情况就不能办理流量卡!

流量卡资费虽然便宜&#xff0c;但也不是谁都可以办得&#xff0c;以下这几种情况是办不了的&#xff01; 看到网上的流量卡资费便宜&#xff0c;也想随手申请一张&#xff0c;别想得太简单了&#xff0c;流量卡也不是那么好办理的&#xff0c;换句话来讲&#xff0c;办理流量…

【Quarkus技术系列】「云原生架构体系」在云原生时代下的Java“拯救者”是Quarkus,那云原生是什么呢?

云原生时代下的Java"拯救者" 在云原生时代&#xff0c;其实Java程序是有很大的劣势的&#xff0c;以最流行的spring boot/spring cloud微服务框架为例&#xff0c;启动一个已经优化好&#xff0c;很多bean需要lazy load的application至少需要3-4秒时间&#xff0c;内…

Chrome小技巧---多用户登录同一网站不串信息

测试中经常需要用到浏览器需要登录多个账号 但是有一个问题就是会串号 通过添加不同的用户再用这用户登录&#xff0c;就不串号了&#xff1a; 还可以在浏览器的偏好设置中添加启动地址 这样每次打开&#xff0c;就进到设置的地址中了

如何将阿里云WiredTiger引擎的MongoDB物理备份文件恢复至自建数据库

数据库操作一直是一个比较敏感的话题&#xff0c;动不动“删库跑路”&#xff0c;可见数据库操作对于一个项目而言是非常重要的&#xff0c;我们有时候会因为一个游戏的严重bug或者运营故障要回档数据库&#xff0c;而你们刚好使用的是阿里云的Mongodb&#xff0c;那么这篇文章…

用ThreadLocal做链路追踪(演变升级版)

前言 1、ThreadLocal是线程变量&#xff0c;线程之间彼此隔离&#xff0c;天生线程安全。因为它是跟着线程走的&#xff0c;考虑到这点&#xff0c;它很适合做链路追踪&#xff08;TraceId&#xff09; 2、当我们写的接口接收到其它地方&#xff08;可能是前端、也可能是其它服…

python如何开发一个电商进销存管理系统?

让我们来看一下题主的需求&#xff1a; 管理公司的淘宝天猫平台&#xff0c;后端仓库&#xff0c;采购进行数据同步。其中最主要的还是要对接淘宝API &#xff0c;实现实时订单的通知&#xff0c;同步淘宝订单&#xff0c;管理买家信息&#xff0c;发货&#xff0c;财务统计等…

亚马逊举报差评有什么作用?有没有可以举报差评的软件?

在亚马逊上举报差评具有以下作用&#xff1a; 1、维护信誉和公平性&#xff1a; 举报差评有助于维护亚马逊市场的信誉和公平性。虚假或不当的差评可能会误导其他消费者&#xff0c;影响他们做出购买决策。通过举报这些问题&#xff0c;可以确保评价体现真实的用户体验&#xf…

【SpringCloud技术专题】「Resilience4j入门指南」(1)轻量级熔断框架的入门指南

基础介绍 Resilience4j是一款轻量级&#xff0c;易于使用的容错库&#xff0c;其灵感来自于Netflix Hystrix&#xff0c;但是专为Java 8和函数式编程而设计。轻量级&#xff0c;因为库只使用了Vavr&#xff0c;它没有任何其他外部依赖下。相比之下&#xff0c;Netflix Hystrix…

Beats:使用 Filebeat 将 golang 应用程序记录到 Elasticsearch - 8.x

毫无疑问&#xff0c;日志记录是任何应用程序最重要的方面之一。 当事情出错时&#xff08;而且确实会出错&#xff09;&#xff0c;我们需要知道发生了什么。 为了实现这一目标&#xff0c;我们可以设置 Filebeat 从我们的 golang 应用程序收集日志&#xff0c;然后将它们发送…

大数据培训前景怎么样?企业需求量大吗

大数据行业对大家来说并不陌生&#xff0c;大数据行业市场人才需求量大&#xff0c;越早入行越有优势&#xff0c;发展机会和上升空间等大。不少人通过大数据培训来提升自己的经验和自身技术能力&#xff0c;以此来获得更好的就业机会。 2023大数据培训就业前景怎么样呢?企业需…

落地大模型应知必会(3): 如何构建多任务的LLM应用

编者按&#xff1a;今年以来&#xff0c;大语言模型(LLM)已被广泛应用于各种自然语言处理任务&#xff0c;也越来越多地被用于构建复杂的语言应用。但是构建多任务的 LLM 应用仍面临一定的挑战&#xff0c;需要解决任务组合和调控等问题。 本文内容介绍了构建多任务 LLM 应用可…

win10中Docker安装、构建镜像、创建容器、Vscode连接实例

Docker方便一键构建项目所需的运行环境&#xff1a;首先构建镜像(Image)。然后镜像实例化成为容器(Container)&#xff0c;构成项目的运行环境。最后Vscode连接容器&#xff0c;方便我们在本地进行开发。下面以一个简单的例子介绍在win10中实现&#xff1a;Docker安装、构建镜像…

STM32--TIM定时器(2)

文章目录 输出比较PWM输出比较通道参数计算舵机简介直流电机简介TB6612 PWM基本结构PWM驱动呼吸灯PWM驱动舵机PWM控制电机 输出比较 输出比较&#xff0c;简称OC&#xff08;Output Compare&#xff09;。 输出比较的原理是&#xff0c;当定时器计数值与比较值相等或者满足某种…

【计算机视觉|生成对抗】改进的生成对抗网络(GANs)训练技术

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Improved Techniques for Training GANs 链接&#xff1a;[1606.03498v1] Improved Techniques for Training GANs (arxiv.org) 摘要 本文介绍了一系列应用于生成对抗网络&#xff08;G…

2023国自然预计本周公布!如何第一时间知道是否中标?

国自然公布时间越来越近了&#xff0c;大家普遍关心今年国自然的具体公布时间。 基金委官网上明确&#xff1a;国家自然科学基金项目从接收至审批完成一般需要5个月左右时间。 集中接收的大部分项目类型&#xff0c;资助结果一般在当年8月中下旬公布&#xff0c;其余项目根据…

时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于GRU门控循环单元的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍 1.Matlab实现GRU门控循环单元时间序列预测未…