23中设计模式之_状态模式

By | 03月09日
Advertisement

23中设计模式之_状态模式

前言

状态模式听起来有点像策略模式,根据不同的状态执行不同的程序代码,真正去理解的时候,才知道另有一片天地。

引入

现在城市发展很快,百万级人口的城市一堆一堆的,那其中有两个东西的发明在城市的发展中起到非
常重要的作用:一个是汽车,一个呢是…,猜猜看,是什么?是电梯!汽车让城市可以横向扩展,电梯让
城市可以纵向延伸,向空中伸展。汽车对城市的发展我们就不说了,电梯,你想想看,如果没有电梯,每
天你需要爬 10 层楼梯, 你是不是会崩溃掉?建筑师设计了一个没有电梯的建筑,那投资家肯定不愿意投资,

那也是建筑师的耻辱呀,今天我们就用程序表现一下这个电梯是怎么运作的。

23中设计模式之_状态模式

一个接口,一个实现类,然后Client去调用不同的方法

package stateParrern;

/**
 * 电梯的四种状态
 *
 * @author weichyang
 *
 */
public interface ILIft {

    void stop();

    void run();

    void openDoor();

    void closeDoor();

}
package stateParrern;

/**
 * 最简单的实现
 * @author weichyang
 *
 */
public class Client {

    public static void main(String[] args) {
        ILIft ift = new LIftIml();
        ift.openDoor();
        ift.run();
        ift.stop();
        ift.closeDoor();
    }

}

运行结果:

openDoor
run
stop
closeDoor

之所以没有贴源码,一,是因为太简单,想想都知道怎么实现,2,是下面会给到源码下载路径

这个程序有什么问题,你想呀电梯门可以打开,但不是随时
都可以开,是有前提条件的的,你不可能电梯在运行的时候突然开门吧?!电梯也不会出现停止了但是不
开门的情况吧?!那要是有也是事故嘛,再仔细想想,电梯的这四个动作的执行都是有前置条件,具体点
说说在特定状态下才能做特定事,那我们来分析一下电梯有什么那些特定状态:
门敞状态—按了电梯上下按钮,电梯门开,这中间有 5 秒的时间(当然你也可以用身体挡住电梯门,那就不是 5 秒了),那就是门敞状态;在这个状态下电梯只能做的动作是关门动作,做别的动作?那就危险喽
门闭状态—电梯门关闭了,在这个状态下,可以进行的动作是:开门(我不想坐电梯了)、停止(忘
记按路层号了)、运行
运行状态—电梯正在跑,上下窜,在这个状态下,电梯只能做的是停止;
停止状态—电梯停止不动,在这个状态下,电梯有两个可选动作:继续运行和开门动作;
我们用一张表来表示电梯状态和动作之间的关系

23中设计模式之_状态模式

看到这张表后,我们才发觉,哦~~,我们的程序做的很不严谨,好,我们来修改一下,先看类图:

23中设计模式之_状态模式

在接口中定义了四个常量,分别表示电梯的四个状态:门敞状态、关闭状态、运行状态、停止状态,
然后在实现类中电梯的每一次动作发生都要对状态进行判断,判断是否运行执行,也就是动作的执行是否
符合业务逻辑,实现类中的四个私有方法是仅仅实现电梯的动作,没有任何的前置条件,因此这四个方法
是不能为外部类调用的,设置为私有方法。我们先看接口的改变:

package stateParrern.typetwo;

/**
 * 电梯的四种行为,当然四种行为不是随意切换的,行为根据不同的状态进行
 *
 * @author weichyang
 *
 */
public interface ILIft {

    public static int DOOR_OPEN = 0;// 敞开状态
    public static int DOOR_CLOSE = 1;// 关闭状态
    public static int Lift_RUNNING = 2;// 运行状态
    public static int Lift_STOPED = 3;// 停止状态

    void setliftState(int state);

    void Liftstop();

    void LiftRunning();

    void openDoor();

    void closeDoor();

}

这种写法添加了四中状态,在具体行为操作时候需要进行判断

Eg:
    /**
     * 什么条件可以跑起来
     */
    public void LiftRunning() {

        switch (this.state) {

        case ILIft.DOOR_CLOSE:
            this.runWithoutLogic();
            this.setliftState(ILIft.Lift_RUNNING);
            break;
        case ILIft.DOOR_OPEN:

            break;
        case ILIft.Lift_RUNNING:

            break;
        case ILIft.Lift_STOPED:
            this.runWithoutLogic();
            this.setliftState(ILIft.Lift_RUNNING);
            break;
        default:
            break;
        }
        System.out.println("state " + state);

    }

四种行为属性,每种行为均会进行状态判断。

但是还是很简单的,就是在每一个接口定义的方法中使用 switch…case 来进行判断,是
否运行运行指定的动作。我们来 Client 程序的变更:

public class Client {

    public static void main(String[] args) {

        /**
         * 理解起来费事
         * 1.电梯门开启       关闭时候
         */
        ILIft ift = new LIftIml();
        ift.setliftState(ILIft.Lift_STOPED);

        ift.openDoor();

        ift.closeDoor();

        ift.LiftRunning();

        ift.Liftstop();

    }

}

业务调用的方法中增加了电梯状态判断,电梯要开门不是随时都可以开的,必须满足了一定条件你才
能开门,人才能走进去,我们设置电梯的起始是停止状态,看运行结果:

电梯门开启…
电梯门关闭…
电梯上下跑起来…
电梯停止了…

我们来想一下,这段程序有什么问题,首先 Lift.java 这个文件有点长,长的原因是我们在程序中使
用了大量的 switch…case 这样的判断(if…else 也是一样),程序中只要你有这样的判断就避免不了加长
程序,同步的在业务比较复杂的情况下,程序体会更长,这个就不是一个很好的习惯了,较长的方法或者
类的维护性比较差,毕竟程序是给人来阅读的;其次,扩展性非常的不好,大家来想想,电梯还有两个状
态没有加,是什么?通电状态和断电状态,你要是在程序再增加这两个方法, 你看看 Open()、Close()、Run()、
Stop()这四个方法都要增加判断条件,也就是说 switch 判断体中还要增加 case 项,也就说与开闭原则相
违背了;再其次,我们来思考我们的业务,电梯在门敞开状态下就不能上下跑了吗?电梯有没有发生过只
有运行没有停止状态呢(从 40 层直接坠到 1 层嘛)?电梯故障嘛,还有电梯在检修的时候,可以在 stop
状态下不开门,这也是正常的业务需求呀,你想想看,如果加上这些判断条件,上面的程序有多少需要修
改?虽然这些都是电梯的业务逻辑,但是一个类有且仅有一个原因引起类的变化,单一职责原则,看看我
们的类,业务上的任务一个小小增加或改动都对我们的这个电梯类产生了修改,这是在项目开发上是有很
大风险的。既然我们已经发现程序上有以上问题,我们怎么来修改呢?
刚刚我们是从电梯的有哪些方法以及这些方法执行的条件去分析,现在我们换个角度来看问题,我们
来想电梯在具有这些状态的时候,能够做什么事情,也就是说在电梯处于一个具体状态时,我们来思考这
个状态是由什么动作触发而产生以及在这个状态下电梯还能做什么事情,举个例子来说,电梯在停止状态
时,我们来思考两个问题:
第一、这个停止状态时怎么来的,那当然是由于电梯执行了 stop 方法而来的;
第二、在停止状态下,电梯还能做什么动作?继续运行?开门?那当然都可以了。
我们再来分析其他三个状态,也都是一样的结果,我们只要实现电梯在一个状态下的两个任务模型就可以了:这个状态是如何产生的以及在这个状态下还能做什么其他动作(也就是这个状态怎么过渡到其他
状态),既然我们以状态为参考模型,那我们就先定义电梯的状态接口,思考过后我们来看类图:

23中设计模式之_状态模式

在类图中,定义了一个 LiftState 抽象类,声明了一个受保护的类型 Context 变量,这个是串联我们
各个状态的封装类,封装的目的很明显,就是电梯对象内部状态的变化不被调用类知晓,也就是迪米特法
则了,我的类内部情节你知道越少越好,并且还定义了四个具体的实现类,承担的是状态的产生以及状态
间的转换过渡,我们先来看 LiftState 程序:

package stateParrern.typethree;

/**
 * 抽象类
 *
 * @author weichyang
 *
 */
public abstract class LiftState {

    public Context context;

    public void setContext(Context context) {
        this.context = context;
    }

    abstract void Liftstop();

    abstract void LiftRunning();

    abstract void openDoor();

    abstract void closeDoor();

}

我来解释一下这个类的几个方法,Openning 状态是由 open()方法产生的,因此这个方法中有一个具体
的业务逻辑,我们是用 print 来代替了;在 Openning 状态下,电梯能过渡到其他什么状态呢?按照现在的
定义的是只能过渡到 Closing 状态,因此我们在 Close()中定义了状态变更,同时把 Close 这个动作也委托
了给 CloseState 类下的 Close 方法执行,这个可能不好理解,我们再看看 Context 类就可能好理解一点:

package stateParrern.typethree;

public class Context {

    /**
     * 定义出所有电梯的状态
     */
    public final static OpenningState openningState = new OpenningState();
    public final static ClosingState closingState = new ClosingState();
    public final static RunningState runningState = new RunningState();
    public final static StoppingState stopstate = new StoppingState();

    public LiftState liftState;

    public LiftState getLiftState() {
        return liftState;
    }

    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        liftState.setContext(this);

        System.out.println(liftState.getClass().getSimpleName());

    }

    public void Liftstop() {
        liftState.Liftstop();
    }

    /**
     * 什么条件可以跑起来
     */
    public void LiftRunning() {
        liftState.LiftRunning();
    }

    /**
     * 开门的条件 二维表如何看 横纵进行合并
     *
     */

    public void openDoor() {
        liftState.openDoor();
    }

    public void closeDoor() {
        liftState.closeDoor();
    }

}

结合以上三个类,我们可以这样理解,Context 是一个环境角色,它的作用是串联各个状态的过渡,在
LiftSate 抽象类中我们定义了并把这个环境角色聚合进来,并传递到了子类,也就是四个具体的实现类中
自己根据环境来决定如何进行状态的过渡。

package stateParrern.typethree;

/**
 * 最简单的实现
 *
 * @author weichyang
 *
 */
public class Client {

    public static void main(String[] args) {

        /**
         * 理解起来费事 1.电梯门开启 关闭时候
         */
        Context context = new Context();

        context.setLiftState(Context.stopstate);

        context.openDoor();
        context.closeDoor();

        context.LiftRunning();

        context.Liftstop();

    }

}

Client 调用类太简单了,只要定义个电梯的初始状态,然后调用相关的方法,就完成了,完全不用考
虑状态的变更,看运行结果:

电梯门开启
电梯关闭状态
电梯正在运行
电梯停止状态

我们再来回顾一下我们刚刚批判上一段的代码,首先我们说人家代码太长,这个问题我们解决了,通

过各个子类来实现,每个子类的代码都很短,而且也取消了的 switch…case 条件的判断;其次,说人家不
符合开闭原则,那如果在我们这个例子中要增加两个状态怎么加?增加两个子类,一个是通电状态,一个
是断电状态,同时修改其他实现类的相应方法,因为状态要过渡呀,那当然要修改原有的类,只是在原有
类中的方法上增加,而不去做修改;再其次,我们说人家不符合迪米特法则,我们现在呢是各个状态是单
独的一个类,只有与这个状态的有关的因素修改了这个类才修改,符合迪米特法则,非常完美!
上面例子中多次提到状态,那我们这节讲的就是状态模式,什么是状态模式呢? 当一个对象内在状态
改变时允许其改变行为,这个对象看起来像是改变了其类。说实话,这个定义的后半句我也没看懂,看过
GOF 才明白是怎么回事: Allow an object to alter its behavior when its internal state changes. The
object will appear to change its class. [GoF, p305],也就是说状态模式封装的非常好,状态的变更
引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样。状态模式的通用实现类如下:
23中设计模式之_状态模式

状态模式通用类图
状态模式中有什么优点呢?首先是避免了过多的 swith…case 或者 if..else 语句的使用,避免了程序
的复杂性;其次是很好的使用体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就
增加子类,你要修改状态,你只修改一个子类就可以了;最后一个好处就是封装性非常好,这也是状态模
式的基本要求,状态变换放置到了类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变
换。
状态模式既然有优点,那当然有缺点了,只有一个缺点,子类会太多,也就是类膨胀,你想一个事物

上面就是状态模式的实现,写法

demo下载链接: http://pan.baidu.com/s/1mhVf5VE

引用 cbf4Life 写的23中设计模式

Similar Posts:

  • 23种设计模式之_解释器模式

    定义:给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子. 类型:行为类模式 类图: 解释器模式的结构 抽象解释器:声明一个所有具体表达式都要实现的抽象接口(或者抽象类),接口中主要是一个interpret()方法,称为解释操作.具体解释任务由它的各个实现类来完成,具体的解释器分别由终结符解释器TerminalExpression和非终结符解释器NonterminalExpression完成. 终结符表达式:实现与文法中的元素相关联的解释操作,通常一个解释

  • 软件设计的23中设计模式(转载)

    1.工厂模式:Factory 客户类和工厂类分开.消费者任何时候需要某种产品,只需向工厂请求即可.消费者无须修改就可以接纳新产品.缺点是当产品修改时,工厂类也要做相应的修改.如:如何创建及如何向客户端提供. 2.建造模式:Builder 将产品的内部表象和产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象.建造模式使得产品内部表象可以独立的变化,客户不必知道产品内部组成的细节.建造模式可以强制实行一种分步骤进行的建造过程. 3.工厂方法模式:FactoryMethod 核

  • 24种设计模式--状态模式【State Pattern】

    现在城市发展很快,百万级人口的城市一堆一堆的,那其中有两个东西的发明在城市的发展中起到非常重要的作用:一个是汽车,一个呢是...,猜猜看,是什么?是电梯!汽车让城市可以横向扩展,电梯让城市可以纵向延伸,向空中伸展.汽车对城市的发展我们就不说了,电梯,你想想看,如果没有电梯,每天你需要爬 10 层楼梯, 你是不是会崩溃掉?建筑师设计了一个没有电梯的建筑,那投资家肯定不愿意投资,那也是建筑师的耻辱呀,今天我们就用程序表现一下这个电梯是怎么运作的. 我们每天都在乘电梯,那我们来看看电梯有哪些动作(映射

  • 游戏编程 状态模式 有限状态机

    转自 红黑联盟 http://www.2cto.com/kf/201510/447364.html 说起状态模式游戏开发者们第一个想到的一定是AI的有限状态机FSMs,状态模式确实是实现有限状态机的一种方法.之后还会讲状态机的进阶分层状态机(hierarchical state machines),和pushdown自动机(pushdown automata), 本文就拿人物控制的有限状态机来讲讲状态机模式,本文例子其实是状态模式和观察者模式的组合,通过获取玩家按键消息来改变状态. 如果不使用有

  • 设计模式(十七)—状态模式(行为型)

    一.简介(Brief Introduction) 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类.状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况.把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化. 例子1:按钮来控制一个电梯的状态,一个电梯开们,关门,停,运行.每一种状态改变,都有可能要根据其他状态来更新处理.例如,开门状体,你不能在运行的时候开门,而是在电梯定下后才能开门. 例子2:我们给一部手机打电话,就可能出现这几种情况:

  • 设计模式 ( 十七) 状态模式State(对象行为型)

    设计模式 ( 十七) 状态模式State(对象行为型) 1.概述 在软件开发过程中,应用程序可能会根据不同的情况作出不同的处理.最直接的解决方案是将这些所有可能发生的情况全都考虑到.然后使用if... ellse语句来做状态判断来进行不同情况的处理.但是对复杂状态的判断就显得"力不从心了".随着增加新的状态或者修改一个状体(if else(或switch case)语句的增多或者修改)可能会引起很大的修改,而程序的可读性,扩展性也会变得很弱.维护也会很麻烦.那么我就考虑只修改自身状态的

  • 状态模式与状态图

    说到状态模式,书上给出的解释是:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类.看不懂?没关系,毕竟我们都不是小菜. 我们还是从熟悉的状态图说起吧. 故事是这样的:我们来看一个人他的下午时光是如何度过的.在12:00~13:00之间,他在吃饭.过了13点,他就开始午睡.睡醒以后,到了15点,他就出去玩了.也就是说,他的下午时间就在吃饭--睡觉--玩耍之间转换. 用状态图描述,基本上是这个样子的.(简化理解) 联系我们的状态模式,其实状态模式根本上就是几个状态之间的转换,但

  • 常用设计模式----状态模式

    package org.design.patterns; /** * 状态模式: 允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类. * */ // 状态接口 public interface State { void handle(); } // 状态实现 class Step1State implements State { @Override public void handle() { System.out.println("ConcreteStateA.handle()

  • 【设计模式】---状态模式详解及应用实例

    近期在机房合作开发中,对状态模式又有了进一步的理解,下面就以机房收费系统为例子来学习装套模式 1. 状态模式基本概念 状态模式(State Pattern)是设计模式的一种,属于行为模式. 定义(源于Design Pattern):当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类. 状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况.把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化. 意图:允许一个对象在其内部状态改变时改变它的行为

  • PHP设计模式笔记:使用PHP实现状态模式

    [意图] 允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类 状态模式变化的位置在于对象的状态 [状态模式结构图] 状态模式 [状态模式中主要角色] 抽象状态(State)角色:定义一个接口,用以封装环境对象的一个特定的状态所对应的行为 具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为 环境(Context)角色:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例.这个具体状态类的实例给出此环境对象的现有状态

Tags: