状态模式(Stats Pattern)

来源:互联网 发布:软件开发培训学费 编辑:程序博客网 时间:2024/06/07 06:24

在实际项目中,应用程序往往需要根据不同的情况做出不同的处理。在开发工程中,需要考虑到各种场景、分支,常常会使用到if..else或者switch case等分支,通过判断条件处理不同的情况。当这种判断变得复杂的时候,分支增多,代码量增多,对代码的维护和可读性、扩展性带来了不好的影响。这种时候就可以考虑使用状态模式。

状态模式

允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。


例子1:按钮来控制一个电梯的状态,一个电梯开们,关门,停,运行。每一种状态改变,都有可能要根据其他状态来更新处理。例如,开门状体,你不能在运行的时候开门,而是在电梯定下后才能开门。


例子2:我们给一部手机打电话,就可能出现这几种情况:用户开机,用户关机,用户欠费停机,用户消户等。 所以当我们拨打这个号码的时候:系统就要判断,该用户是否在开机且不忙状态,又或者是关机,欠费等状态。但不管是那种状态我们都应给出对应的处理操作。


适用场景
1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
2.一个操作中含有庞大的多分支结构,并且这些分支决定于对象的状态。


角色说明

1.环境类(Context):  定义客户感兴趣的接口。维护一个ConcreteState子类的实例,这个实例定义当前状态。
2.抽象状态类(State):  定义一个接口以封装与Context的一个特定状态相关的行为。
3.具体状态类(ConcreteState):  每一子类实现一个与Context的一个状态相关的行为。


类图



代码示例

状态模式最出名的莫过于状态机了,这里用代码模拟一个娃娃机(Context)。简单模拟一下娃娃机的状态分别为:空仓,等待投币,操作中三个具体状态(ConcreteStats);用户对娃娃机的行为包括:补货,投币,摇杆操作,抓娃娃操作。

先写一段伪代码,看看不用状态模式时是怎么写的

switch (事件){    case 投币:        if(等待状态){            //可以投币        }else if(操作中状态){            //不能投币        }else {            //空仓状态,不能投币        }        return;    case 摇杆:        //…………    case 抓娃娃:        //…………    case 补货:        //…………}
为了缩短代码长度,写得比较粗糙,不过大意就是:不同的事件需要判断娃娃机当前的状态,然后根据状态做出相应的响应。如果这段代码实实在在的都写完写好了,相比是一大段判断语句,一大段代码,可读性、扩展性都不太好。


下面我们试着使用状态模式完成娃娃机

Stats:

/** * 定义了4个娃娃机的行为 * Created by yangjiachang on 2016/9/12. */public abstract class Stats {    //娃娃机实例    protected StatsMachine machine;    /**     * 投币     */    public abstract void insertCoins();    /**     * 摇杆     */    public abstract void rock();    /**     * 抓娃娃     */    public abstract void get();    /**     * 补货     */    public abstract void supplement(int count);}

ConcreteStats:

/** * 空仓状态,没有娃娃了 * Created by yangjiachang on 2016/9/12. */public class EmptyStats extends Stats {    public EmptyStats(StatsMachine machine){        this.machine = machine;    }    public EmptyStats(){    }    public void insertCoins() {        System.out.println("没有娃娃了,不能投币,钱退还给你");    }    public void rock() {        System.out.println("摇杆无效");    }    public void get() {        System.out.println("不能抓娃娃");    }    public void supplement(int count) {        machine.setCount(machine.getCount() + count);        machine.setStats(machine.getWaitStats());        System.out.println("补货完成,又有" + machine.getCount()+"个娃娃了");    }}
/** * 操作摇杆状态,抓娃娃 * Created by yangjiachang on 2016/9/12. */public class OperateStats extends Stats {    public OperateStats(StatsMachine machine){        this.machine = machine;    }    public OperateStats(){    }    public void insertCoins() {        System.out.println("已经投过钱币了,不要再投了,多的钱退给你");    }    /**     * 摇杆     */    public void rock() {        System.out.println("寻找一个最好的娃娃");    }    /**     * 抓娃娃     */    public void get() {        System.out.println("正在抓取娃娃……");        int count = machine.getCount()-1;        if(count > 0){            machine.setCount(count);            machine.setStats(machine.getWaitStats());        }else {            machine.setCount(count);            machine.setStats(machine.getEmptyStats());        }        System.out.println("抓到娃娃,剩余:"+machine.getCount());    }    public void supplement(int count) {        System.out.println("用户正在操作,请稍后补货");    }}
/** * 等待投币状态 * Created by yangjiachang on 2016/9/12. */public class WaitStats extends Stats {    public WaitStats(StatsMachine machine){        this.machine = machine;    }    public WaitStats(){}    public void insertCoins() {        System.out.println("投币成功,你可以开始抓娃娃了");        machine.setStats(machine.getOperateStats());    }    public void rock() {        System.out.println("摇杆无效,请先投币");    }    public void get() {        System.out.println("不能抓娃娃,请先投币");    }    public void supplement(int count) {        machine.setCount(machine.getCount() + count);        System.out.println("补货完成,又有" + machine.getCount()+"个娃娃了");    }}
Context:

/** * 定义一个状态机,这里就是娃娃机 * Created by yangjiachang on 2016/9/12. */public class StatsMachine {    //娃娃机存在的三个状态    private Stats emptyStats = new EmptyStats(this);    private Stats waitStats = new WaitStats(this);    private Stats operateStats = new OperateStats(this);    //记录当前状态    private Stats stats;    //娃娃数量    private int count;    //初始化    public StatsMachine(int count){        if (count <= 0){            this.count = 0;            this.stats = emptyStats;        }else {            this.count = count;            this.stats = waitStats;        }    }    /* 行为start */    public void insertCoins(){        stats.insertCoins();    }    public void rock(){        stats.rock();    }    public void get(){        stats.get();    }    public void supplement(int count){        stats.supplement(count);    }    /* 行为end */    //setting  getting    public Stats getStats() {        return stats;    }    public void setStats(Stats stats) {        this.stats = stats;    }    public Stats getEmptyStats() {        return emptyStats;    }    public Stats getWaitStats() {        return waitStats;    }    public Stats getOperateStats() {        return operateStats;    }    public int getCount() {        return count;    }    public void setCount(int count) {        this.count = count;    }}
测试方法:

public static void main(String[] args) {    int count = 2;    StatsMachine machine = new StatsMachine(count);    for(int i=0 ; i<count+1 ; i++){        System.out.println("当前状态:"+machine.getStats().getClass().getSimpleName());        machine.insertCoins();        System.out.println("当前状态:" + machine.getStats().getClass().getSimpleName());        machine.rock();        System.out.println("当前状态:" + machine.getStats().getClass().getSimpleName());        machine.get();        System.out.println();    }    machine.supplement(3);    System.out.println("剩余数量:" + machine.getCount());}

执行结果:

当前状态:WaitStats
投币成功,你可以开始抓娃娃了
当前状态:OperateStats
寻找一个最好的娃娃
当前状态:OperateStats
正在抓取娃娃……
抓到娃娃,剩余:1


当前状态:WaitStats
投币成功,你可以开始抓娃娃了
当前状态:OperateStats
寻找一个最好的娃娃
当前状态:OperateStats
正在抓取娃娃……
抓到娃娃,剩余:0


当前状态:EmptyStats
没有娃娃了,不能投币,钱退还给你
当前状态:EmptyStats
摇杆无效
当前状态:EmptyStats
不能抓娃娃


补货完成,又有3个娃娃了
剩余数量:3


对状态模式的理解:

1.它将与特定状态相关的行为局部化,并且将不同状态的行为分割开来

将状态封装成对象,由抽象状态类定义所有的行为;将Context的每种状态定义成一个具体状态类,并实现该状态下各种行为的实现。所以通过定义新的子类很容易扩展新的状态和转移状态(例如投币行为将娃娃机由等待投币状态变为可操作状态),并实现该新状态下的行为。这种方式避免了过多的if..else或者switch case语句带来的分支代码,并将各自状态的逻辑封装到一个具体状态类中,对于理解该状态下什么能做、做了什么提供了更好的可读性。


2.显示的转换状态,增强可读性

实际应用中,状态的判断往往的多个变量的值的判断,在换货状态时,需要对多个变量赋值,这样的方式加深了理解的难道。通过状态模式,将状态封装在一个类中,在判断与转换的过程中,更加直截了当


3.状态可以共享

如果有必要,可以多个Context共享同一个Stats状态,也可以减少


4.将状态封装成对象,势必会增加系统类和对象个数,运用不当反而会显得结构和代码混乱








1 0
原创粉丝点击