【设计模式——学习笔记】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;
    }

    /**
     * 当前状态可以扣积分,扣除后,将状态设置成可以抽奖状态
     */
    @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),进而综合两者的内容,让知识点更加全面
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>