专栏首页开心的学习之路状态模式(State Design Pattern)

状态模式(State Design Pattern)

以此回顾《设计模式之禅》及其他设计模式书籍、视频中的状态模式。

什么是状态模式?

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. (当一个对象的内在状态改变时,允许它修改自己的行为。这个对象看起来像是改变了类)

状态模式有3个重要部分。(1)Context(Account)(2)State (3)Concrete State

如何理解状态模式中的Context?

Context(Account):Maintains an instance of a ConcreteState subclass that defines current state。持有一个定义了当前状态的实例,这个“当前状态”就是一个具体状态(ConcreteState),实现了State,是State的子集。

如何理解状态模式中的State?

State:Defines an interface for encapsulating the behavior associated with a particular state of the context. 定义了一个接口,接口里包含了context持有的具体状态要实现的方法。

如何理解状态模式中的Concrete State?

Concrete State:Each subclass implements a behavior associated with a state of context。具体状态里实现了接口中定义的方法。

理解了这3个部分,再看定义,不难发现,状态模式其实就是说,当context持有的某个状态(concrete state)改变时,它的行为也发生了改变(因为每个concrete state对state接口中定义的方法的实现不同,行为也就不同)。

例如,把电梯抽象出4个状态,分别是运行、停止、开门、关门,则它的状态模式UML类图为:

使用状态模式的好处?

(1)结构清晰。

避免了switch...case或者if...else的使用,减少了程序的负责行,使逻辑清晰。试想,刚才的电梯的例子,假设不用状态模式,则会变成这样,直接上代码:

public void close() {
    //电梯在什么情况下能关闭
    switch(state) {
        //如果此时门开着
        case OPEN_DOOR_STATE: 
            //可以关门
            this.closeDoor();
            this.setState(CLOSE_DOOR_STATE);
            break;
         //如果此时门关着
         case CLOSE_DOOR_STATE:
            //do nothing
            break;
         //如果此时电梯正在运行
         case RUN_STATE:
             //do nothing
             break;
         //如果此时电梯停止运行
         case STOP_STATE:
             //do nothing
             break;
    }
}

//同理,开门
public void openDoor() {
    //电梯什么情况下能开门
    switch(state) {
         case XXX:break;
         case XXX:break;
         case XXX:break;
         case XXX:break;
    }
}

//同理,运行
public void run() {
    //电梯什么情况下能运行
    switch(state) {
         case XXX:break;
         case XXX:break;
         case XXX:break;
         case XXX:break;
    }
}

//同理,停止
public void stop() {
    //电梯什么情况下能停止
    switch(state) {
         case XXX:break;
         case XXX:break;
         case XXX:break;
         case XXX:break;
    }
}

显然,大量的switch case语句这个类过长,程序逻辑结构复杂,不容易理解,难以维护。

(2)遵循设计原则

状态模式很好的体现了开闭原则和单一职责原则,每个状态都是一个子类,只要增加子类,就能进行扩展,比如电梯的例子,如果想增加通电和断电状态,直接增加子类就可以实现,但是如果用上面的未使用状态模式的代码,就会又增加一大波判断,逻辑更加复杂,且破坏了开闭原则。

(3)封装性好

这也是状态模式的要求,将类的变换放置到类的内部来实现,外部的调用不知道类内部如何实现状态和行为的变换,符合迪米特法则(最少知道法则)。

如何使用状态模式?

用ATM机的例子(来自于视频:https://www.youtube.com/watch?v=MGEx35FjBuo)来讲一个状态模式的应用。

1、想想ATM机的所有可能状态。

(1)HasCard

(2)NoCard

(3)HasPin

(4)NoCash

2、想想人们使用ATM机的几种操作。

(1)InsertCard

(2)EjectCard

(3)InsertPin

(4)RequestCash

3、UML类图

4、具体代码

ATMState.java

public interface ATMState {

    void insertCard();
    void ejectCard();
    void insertPin(int pinEntered);
    void requestCash(int cashToWithdraw);

}

ATMMachine.java

public class ATMMachine {
    ATMState hasCard;
    ATMState noCard;
    ATMState hasCorrectPin;
    ATMState atmOutOfMoney;

    ATMState atmState;

    int cashInMachine = 2000;
    boolean correctPinEntered = false;

    public ATMMachine() {
        hasCard = new HasCard(this);
        noCard = new NoCard(this);
        hasCorrectPin = new HasPin(this);
        atmOutOfMoney = new NoCash(this);

        atmState = noCard;

        if (cashInMachine < 0) {
            atmState = atmOutOfMoney;
        }
    }

    void setAtmState(ATMState newAtmState) {
        atmState = newAtmState;
    }

    public void setCashInMachine(int newCashInMachine) {
        cashInMachine = newCashInMachine;
    }

    public void insertCard() {
        atmState.insertCard();
    }

    public void ejectCard() {
        atmState.ejectCard();
    }

    public void requestCash(int cashToWithdraw) {
        atmState.requestCash(cashToWithdraw);
    }

    public void insertPin(int pinEntered) {
        atmState.insertPin(pinEntered);
    }

    public ATMState getYesCardState() {
        return hasCard;
    }

    public ATMState getNoCardState() {
        return noCard;
    }

    public ATMState getHasCorrectPinState() {
        return hasCorrectPin;
    }

    public ATMState getAtmOutOfMoneyState() {
        return atmOutOfMoney;
    }
}

HasCard.java

public class HasCard implements ATMState {

    ATMMachine atmMachine;

    public HasCard(ATMMachine atmMachine) {
        this.atmMachine = atmMachine;
    }

    @Override
    public void insertCard() {
        System.out.println("You can't enter more than one card!");
    }

    @Override
    public void ejectCard() {
        System.out.print("Card ejected");
        atmMachine.setAtmState(atmMachine.getNoCardState());
    }

    @Override
    public void insertPin(int pinEntered) {
        if (pinEntered == 1234) {
            System.out.println("Correct Pin");
            atmMachine.correctPinEntered = true;
            atmMachine.setAtmState(atmMachine.getHasCorrectPinState());
        } else {
            System.out.println("Wrong Pin");
            atmMachine.correctPinEntered = false;
            System.out.println("Card ejected");
            atmMachine.setAtmState(atmMachine.getNoCardState());
        }
    }

    @Override
    public void requestCash(int cashToWithdraw) {
        System.out.println("Enter pin first");
    }
}

NoCard.java

public class NoCard implements ATMState {

    ATMMachine atmMachine;

    public NoCard(ATMMachine atmMachine) {
        this.atmMachine = atmMachine;
    }

    @Override
    public void insertCard() {
        System.out.println("Please enter a pin");
        atmMachine.setAtmState(atmMachine.getYesCardState());
    }

    @Override
    public void ejectCard() {
        System.out.println("Enter a card first");
    }

    @Override
    public void insertPin(int pinEntered) {
        System.out.println("Enter a card first");
    }

    @Override
    public void requestCash(int cashToWithdraw) {
        System.out.println("Enter a card first");
    }
}

HasPin.java

public class HasPin implements ATMState {

    ATMMachine atmMachine;

    public HasPin(ATMMachine atmMachine) {
        this.atmMachine = atmMachine;
    }

    @Override
    public void insertCard() {
        System.out.println("You can't enter more than one card");
    }

    @Override
    public void ejectCard() {
        System.out.println("Card ejected");
        atmMachine.setAtmState(atmMachine.getNoCardState());
    }

    @Override
    public void insertPin(int pinEntered) {
        System.out.println("Already entered pin");
    }

    @Override
    public void requestCash(int cashToWithdraw) {
        if (cashToWithdraw > atmMachine.cashInMachine) {
            System.out.println("Don't have that cash");
            System.out.println("Card ejected");
            atmMachine.setAtmState(atmMachine.getNoCardState());
        } else {
            System.out.println(cashToWithdraw + " is provided by machine");
            atmMachine.setCashInMachine(atmMachine.cashInMachine - cashToWithdraw);

            System.out.println("Card ejected");
            atmMachine.setAtmState(atmMachine.getNoCardState());

            if (atmMachine.cashInMachine <= 0) {
                atmMachine.setAtmState(atmMachine.getAtmOutOfMoneyState());
            }
        }
    }
}

NoCash.java

public class NoCash implements ATMState {

    ATMMachine atmMachine;

    public NoCash(ATMMachine atmMachine) {
        this.atmMachine = atmMachine;
    }

    @Override
    public void insertCard() {
        System.out.println("We don't have money");
    }

    @Override
    public void ejectCard() {
        System.out.println("We don't have money, you didn't enter a card");
    }

    @Override
    public void insertPin(int pinEntered) {
        System.out.println("We don't have money");
    }

    @Override
    public void requestCash(int cashToWithdraw) {
        System.out.println("We don't have money");
    }
}

TestATMMachine.java

public class TestATMMachine {
    public static void main(String[] args) {
        ATMMachine atmMachine = new ATMMachine();
        atmMachine.insertCard();
        atmMachine.ejectCard();
        atmMachine.insertCard();
        atmMachine.insertPin(1234);
        atmMachine.requestCash(2000);
        atmMachine.requestCash(100);
        atmMachine.insertCard();
        atmMachine.insertPin(1234);
    }
}

什么时候使用状态模式,以及状态模式的注意事项?

(1)状态模式适用于某个对象当他的状态改变时,行为也有有较大变化的时候,考虑使用状态模式,但是对象的状态最好不要超过5个,否则会出现类爆炸,不好管理。

(2)当程序中有大量的if else 或者 switch case导致逻辑结构不清晰时,可以考虑状态模式。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 工厂模式(Factory Design Patter)

    定义一:Define an interface for creating an object, but let subclasses decide which ...

    刘开心_1266679
  • 神经网络体系搭建(二)——深度学习网络

    本篇是神经网络体系搭建的第二篇,解决体系搭建的深度学习网络相关问题,详见神经网络体系搭建(序) 深度学习是一个已经被说烂了的词,几乎说起人工智能,非专业人士也会...

    刘开心_1266679
  • 用TensorFlow的Linear/DNNRegrressor预测数据

    今天要处理的问题对于一个只学了线性回归的机器学习初学者来说还是比较棘手——通过已知的几组数据预测一组数据。用excel看了下,关系不是很明显,平方,log都不是...

    刘开心_1266679
  • Spring @Transactional踩坑记

    @Transactional踩坑记 总述 ​ Spring在1.2引入@Transactional注解, 该注解的引入使得我们可以简单地通过在方法或者类上添加@...

    SecondWorld
  • 用命令模式实现撤销与恢复 命令模式定义撤销与重做功能就此实现。整个过程中,最关键部分是命令对象的封装以及控制类与具体工厂类耦合的解除。

    通过 ICommand 接口,实现了控制类与调用者的解耦。 * 下面通过一个简单的实例来详细说明这种解耦以恢复撤销是如何实现。 假定有一个风扇,当前有...

    用户2434869
  • 线程的创建

    创建一个Java线程常见的有两种方式: 1.继承Thread类 两种表示方法: (1).外部类 import java.util.D...

    汤高
  • 程序、进程、线程的关系

    创建一个Java线程常见的有两种方式: 1.继承Thread类 两种表示方法: (1).外部类 import java.util.D...

    汤高
  • java之学习date类的概述和案例分析

    吾爱乐享
  • 「 互联网笔试题 」No.5答案与解析

    1.输入任意个字符串,将其中的小写字母变为大写,大写字母变为小写,其他字符不用处理; 输入描述: 任意字符串:abcd12#%XYZ 输出描述: 输出字符串:A...

    KEN DO EVERTHING
  • 如何优雅的在java中统计代码块耗时

    在我们的实际开发中,多多少少会遇到统计一段代码片段的耗时的情况,我们一般的写法如下

    小灰灰

扫码关注云+社区

领取腾讯云代金券