前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >状态模式(State Design Pattern)

状态模式(State Design Pattern)

作者头像
刘开心_1266679
发布2019-02-14 15:20:26
4860
发布2019-02-14 15:20:26
举报

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

什么是状态模式?

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导致逻辑结构不清晰时,可以考虑状态模式。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年05月15日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是状态模式?
    • 如何理解状态模式中的Context?
      • 如何理解状态模式中的State?
        • 如何理解状态模式中的Concrete State?
        • 使用状态模式的好处?
          • (1)结构清晰。
            • (2)遵循设计原则
              • (3)封装性好
              • 如何使用状态模式?
              • 什么时候使用状态模式,以及状态模式的注意事项?
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档