设计模式~状态模式

40 阅读5分钟

1. 介绍

  • 状态模式中的行为是由状态来决定的,不同的状态下有不同的行为。状态模式与策略模式的结构几乎完全一样,但他们的目的和本质却是完全不一样的。

2. 定义

  • 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

3. UML类图

image.png

  • Context:环境类,定义客户端需要的接口,通过维护一个State实现类的实例(这个实例定义了对象的当前状态),负责具体状态的切换。
  • State:抽象状态类或接口,通过定义一个或一组接口方法,表示相应状态下的行为
  • ConcreteStateA/ConcreteStateB/...:具体状态实现类,通过实现State接口或抽象类,并实现抽象方法,来表示A/B...状态下的相对应的行为

4. 使用场景

  • 代码中包含大量与对象状态有关的条件语句,例如,一个操作中含有大量的多分支语句(if-else/switch-case),且这些分支依赖于该对象的状态。(状态模式将每一个条件分支放入一个独立的类中,每一种状态均可作为一个单独的对象,而且各对象间互不耦合,便可通过多态来去除重复的分支语句)
  • 对象的行为取决于它的状态,并且行为会随着状态的改变而改变。

5. 简单实现

  • 需求:电视在开机状态时能调音量和频道以及关机但不能开机,而当关机状态时则只能开机

5.1 使用普通方式

  • 定义一个电视控制类
/**
 * 电视控制类(使用包含大量if-else的普通方式)
 *
 * @author BTPJ  2023/1/10
 */
public class TvController {
    /**
     * 关机状态
     */
    private static final int POWER_OFF = 0;
    /**
     * 开机状态
     */
    private static final int POWER_ON = 1;

    /**
     * 当前状态
     */
    private int mState = POWER_OFF;

    /**
     * 电视开机操作
     */
    public void powerOn() {
        if (mState == POWER_ON) {
            System.out.println("遥控器闪两下红灯提示电视已经开机");
        } else {
            mState = POWER_ON;
            System.out.println("电视开机");
        }
    }

    /**
     * 电视关机操作
     */
    public void powerOff() {
        if (mState == POWER_ON) {
            mState = POWER_OFF;
            System.out.println("电视关机");
        } else {
            System.out.println("遥控器闪两下红灯提示电视并未开机");
        }
    }

    /**
     * 增加频道
     */
    public void nextChannel() {
        if (mState == POWER_ON) {
            System.out.println("下一频道");
        } else {
            System.out.println("遥控器闪两下红灯提示电视并未开机");
        }
    }
    
    /**
     * 减少频道
     */
    public void prevChannel() {
        if (mState == POWER_ON) {
            System.out.println("上一频道");
        } else {
            System.out.println("遥控器闪两下红灯提示电视并未开机");
        }
    }
    
    /**
     * 增加频道
     */
    public void turnUp() {
        if (mState == POWER_ON) {
            System.out.println("调高音量");
        } else {
            System.out.println("遥控器闪两下红灯提示电视并未开机");
        }
    }
    
    /**
     * 减少频道
     */
    public void turnDown() {
        if (mState == POWER_ON) {
            System.out.println("调低音量");
        } else {
            System.out.println("遥控器闪两下红灯提示电视并未开机");
        }
    }
}
  • 可见普通方式方法内含有重复的if-else代码,而且这还只有两种状态,假如新增状态,每个方法都得因为新增的状态而改动代码,难以维护

5.2 使用状态模式

  • 抽取电视的行为作为各状态下的抽象方法
/**
 * 封装状态下的行为
 *
 * @author BTPJ  2023/1/11
 */
public interface TvState {
    void powerOn();
    void powerOff();
    void nextChannel();
    void prevChannel();
    void turnUp();
    void turnDown();
}
  • 实现开机状态下的行为
/**
 * 实现开机状态下的行为
 *
 * @author BTPJ  2023/1/11
 */
public class PowerOnState implements TvState {

    @Override
    public void powerOn() {
        System.out.println("遥控器闪两下红灯提示电视已经开机");
    }

    @Override
    public void powerOff() {
        System.out.println("电视关机");
    }

    @Override
    public void nextChannel() {
        System.out.println("下一频道");
    }

    @Override
    public void prevChannel() {
        System.out.println("上一频道");
    }

    @Override
    public void turnUp() {
        System.out.println("调高音量");
    }

    @Override
    public void turnDown() {
        System.out.println("调低音量");
    }
}
  • 同理实现关机状态下的行为
/**
 * 实现关机状态下的行为
 *
 * @author BTPJ  2023/1/11
 */
public class PowerOffState implements TvState{

    @Override
    public void powerOn() {
        System.out.println("电视开机");
    }

    @Override
    public void powerOff() {
        System.out.println("遥控器闪两下红灯提示电视并未开机");
    }

    @Override
    public void nextChannel() {
        System.out.println("遥控器闪两下红灯提示电视并未开机");
    }

    @Override
    public void prevChannel() {
        System.out.println("遥控器闪两下红灯提示电视并未开机");
    }

    @Override
    public void turnUp() {
        System.out.println("遥控器闪两下红灯提示电视并未开机");
    }

    @Override
    public void turnDown() {
        System.out.println("遥控器闪两下红灯提示电视并未开机");
    }
}
  • TvController可以理解为UML类图中的Context
/**
 * 电视控制器(可以理解为UML结构图中的Context)
 *
 * @author BTPJ  2023/1/11
 */
public class TvController {
    private TvState tvState;

    public void setTvState(TvState tvState) {
        this.tvState = tvState;
    }

    public void powerOn() {
        tvState.powerOn();
        // 调用开机方法后将状态改变为开机状态
        setTvState(new PowerOnState());
    }

    public void powerOff() {
        tvState.powerOff();
        // 调用关机机方法后将状态改变为关机状态
        setTvState(new PowerOffState());
    }

    public void nextChannel() {
        tvState.nextChannel();
    }

    public void prevChannel() {
        tvState.prevChannel();
    }

    public void turnUp() {
        tvState.turnUp();
    }

    public void turnDown() {
        tvState.turnDown();
    }
}
  • 客户端调用
/**
 * 客户端调用
 *
 * @author BTPJ  2023/1/11
 */
public class client {

    public static void main(String[] args) {
        TvController tvController = new TvController();
        tvController.setTvState(new PowerOnState());
        // tvController.setTvState(new PowerOffState());
        tvController.powerOff();
        tvController.powerOn();
        tvController.nextChannel();
        tvController.prevChannel();
        tvController.turnUp();
        tvController.turnDown();
    }
}

运行结果:
电视关机
电视开机
下一频道
上一频道
调高音量
调低音量
  • 从前后两种方式可以发现,虽然应用状态模式使得类和对象增加了,但可维护性大大增强,各状态下的行为均封装到不同的类中,互不影响,大大降低了耦合度,使得可维护性大大增强,特别是在后期新增更多状态的时候

6. 源码中的使用场景

  • Android系统中的Wifi状态的管理(开启状态时扫描附近wifi并展示,关闭时列表为空,还有开启中...、关闭中等状态的处理)
  • 自己项目中登录与未登录状态下各种逻辑的封装处理

7. 优缺点

  • 优点:
    • 避免了过多的条件语句,使得结构更清晰,提高代码的可维护性。
    • 每个状态都是一个子类,方便增加和修改状态。
    • 状态被放置到类的内部,外部调用不需要知道类的内部如何实现状态和行为的变换。
  • 缺点:
    • 增加类和对象的数量