状态模式(State)

因为前段日子比较忙,所以好些日子没有写博客了,而这个设计模式系列也就被耽搁了,

现在又比较清闲了,所以打算将这个系列写完,还望大家支持。

         

        

引子

         

先来看一个描述,这个描述呢是来自《Java 与模式》这本书:

说的是,在 1979 年出土的那个什么编钟,

image

这个编钟呢,上面有 n 多的钟,敲打它就可以发出声音,那么这和状态模式扯得上什么关系呢?

很明显,编钟可以发出声音,并且这些声音是可以变化的,编钟的这种属性就叫做状态了,

而正因为编钟具有这种状态,所以编钟便可以称为是具有状态的对象了。

而至于钟发出的是什么声音的话,那就得由你敲打的是哪个钟来决定了,你敲打不同的钟便可以发出不同的声音,

也就是说,上面的这些钟就代表了状态对象(也就是编钟)的所有的属性(也就是可能的状态)了。

总结

在很多情况下,一个对象的行为取决于一个或者是多个动态变化的属性,这些的属性就叫做状态,

而拥有这些属性的对象便称为状态对象,这些对象的状态是从事先定义好的一系列值中取出的,

当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统行为也随之改变。

状态模式把所研究的对象的行为包装在不同的状态对象里,

每一个状态对象都属于一个抽象状态类的子类,状态模式的意图是让一个对象在其内部状态发生改变时其行为也随之改变。

                     

        

提出问题

         

而在这里,我就先来简要的描述一个问题吧,

在上面呢,我对这个编钟不了解,不知道一首曲子要先敲哪个,再敲哪个才能组成一首曲子,所以我弃用这个编钟的例子,

但为了不让读者们思绪发生太大改变,那我就使用电子琴来代替上面的编钟,

假设有这么一种便宜的电子琴(小朋友玩的那种),就只会一首简单的曲子,

在电子琴上呢,我假设就只有 0,1,2,3,4,5,6,7,8,9 这是个按钮,

然后这首简单的曲子的按键的顺序是 8—>6—>5—>0—>3—>2—>9—>1—>4—>7,

该如何实现这一逻辑呢,

最简单的,直接在 main 函数中写,10 个 if 判断或者是 switch 便可以 OK 了,

if(youKey==8){ ...... }
if(youKey==6){ ...... }
if(youKey==5){ ...... }
if(youKey==0){ ...... }
if(youKey==3){ ...... }
if(youKey==2){ ...... }
if(youKey==9){ ...... }
if(youKey==1){ ...... }
if(youKey==4){ ...... }
if(youKey==7){ ...... }

不过因为是面向对象嘛,所以为了所谓的面向对象,便加一个类到里面,

然后再在类里面完成上面的这 10 个 if 语句,再到客户端实例化这个类,也是 OK 的。

public class ElectronicOrgan
{
     public MakeMusic
     {
         if(youKey==8){ ...... }
         if(youKey==6){ ...... }
         if(youKey==5){ ...... }
         if(youKey==0){ ...... }
         if(youKey==3){ ...... }
         if(youKey==2){ ...... }
         if(youKey==9){ ...... } 
         if(youKey==1){ ...... }
         if(youKey==4){ ...... }
         if(youKey==7){ ...... }
     }
}

 

很明显上面的两种方式都是可以完成的,但是问题也是存在的,首先,看第一种,

第一种呢,这么多的 if 全部写在 main 函数中这就是一个不应该做的操作了,更何况会造成 main 函数变得很大(代码很多)吧,

不是有一句话说,如果一个函数过大,也就是代码太多的话,那么说明还需要重构,还有,这种方法似乎没有扩展的余地。

第二种方法呢,同样的,将这么大的 if 语句写在了 MakeMusic 中,也会造成这个函数过长,需要重构,

同时,在一个类里面维护了这么多的判断逻辑,很明显,说明这个类责任过重,也就是它完成的操作太多了,

违背了单一职责原则原则,同样,也无法实现好的扩展,所以第二种方法也还是通不过。

那么更好的办法是什么呢?

既然这篇博文是介绍状态模式的,自然是通过状态模式来解决了,不过这会在本篇博文的后面介绍,还请耐心读下去。

        

       

引入状态模式

        

还是先看结构类图比较合适:

State

状态模式(State)是当一个对象的内在状态改变时允许改变其行为,而这个对象看起来就像是改变了其类。

在上面的结构类图中呢,有很多的具体状态对象 ConcreteStateA . . . . 但是这些具体状态对象都是继承自抽象状态对象 AbstractState ,

在 AbstractState 这个抽象类中呢,定义了一个接口,其用来封装与 Context 的一个特定状态相关的行为,

简单点说就是 AbstractState 定义了一个所有具体状态 ConcreteState 的共同接口,

不然,怎么利用多态来实现从一个状态转换到另一个状态呢?

而对于所有的 ConcreteState 呢,其是用来处理来自 Context 的请求的,

其实现了一个 Context 的一个状态所对应的行为,

也就是每一个 ConcreteState 对于来自 Context 的请求都有自己的实现,

Context 类呢,需要维护一个 ConcreteState 的实例,而这个具体状态类的实例给出了此环境下的对象的现有状态。

那么状态模式是如何实现的“这个对象看起来就像是改变了其类”?

这里的这个对象,其实是指的 Context 对象,而非 ConcreteState 对象,

只不过在 Context 类中,通过组合的方式来改变了 Context 的行为,

至于组合的方式呢,就是在 Context 中维护的那个 ConcreteState 来改变的,

ConcreteState 处理了来自 Context 的请求,自然就会改变 Context ,

而客户端却并不知道是 ConcreteState 改变了 Context (客户端甚至不认识 ConcreteState),

在客户端眼里,是 Context 自个儿改变了自己。

   

状态模式将逻辑和行为进行了分离,

逻辑很明显是定义在各个 ConcreteState 中,而至于动作,则是在 Context 中定义了,

这样,便使得逻辑和行为互不干扰,独立变化,这有一个什么好处呢,很明显,扩展性比较好,

您可以方便的添加 ConcreteState 类,而这里的添加并不会影响到 Context 类,

而对于 Context 类呢,也是可以通过创建 Context 的子类来创建新的动作或者是覆盖原有的动作都是可以的,

并且这些操作都不会影响到 ConcreteState 类。

    

状态模式的功效,

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况(比如上面的电子琴问题中,就存在很多的 if 语句),

把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简单化(也就是通过将每一个 if 语句当做一个状态),

当然,如果这个状态逻辑很简单,那就没有必要使用“状态模式”了(就 2 个 if 分支,来用个设计模式,设计一大堆类,这也太不划算了吧)。

    

         

状态模式和策略模式的区别

       

策略模式可以参考笔者的博文:http://www.cnblogs.com/QinBaoBei/archive/2010/05/02/1725972.html

首先,给出策略模式的结构类图:

image

从策略模式和状态模式的类图来看,两者是一模一样的,

不过,两者却是完全不同的,策略模式是通过可以互换的算法来完成业务逻辑的(在运行时动态的改变算法)。

而状态模式则是通过改变对象内部的状态(通过 ConcreteState 实现)来帮助对象控制自己的行为。

简单点说,策略模式用来处理算法的变化,而状态模式用来出来状态的变化,

策略模式呢,主要是通过 Context 外面的对象也就是 ConcreteStrategy 来完成算法的切换,

而在状态模式中,则是在 Context 中直接维护一个 ConcreteState 来完成状态的自动转换。

         

          

状态模式的实现

      

AbstractState 代码:

namespace State
{
    public abstract class AbstractState
    {
        public abstract void Handle(Context context);
    }
}

ConcreteStateA 代码:

using System;
namespace State
{
    public class ConcreteStateA : AbstractState
    {
        public override void Handle(Context context)
        {
            Console.WriteLine("当前状态时:{0}",
                this.GetType().ToString());
            context.State = new ConcreteStateB();
        }
    }
}

ConcreteStateB 代码:

using System;
namespace State
{
    public class ConcreteStateB : AbstractState
    {
        public override void Handle(Context context)
        {
            Console.WriteLine("当前状态是:{0}",
                this.GetType().ToString());
            context.State = new ConcreteStateC();
        }
    }
}

ConcreteStateC 代码:

using System;
namespace State
{
    public class ConcreteStateC : AbstractState
    {
        public override void Handle(Context context)
        {
            Console.WriteLine("当前状态是:{0}",
                this.GetType().ToString());
            context.State = new ConcreteStateA();
        }
    }
}

Context 代码:

using System;
namespace State
{
    public class Context
    {
        private AbstractState state;

        public AbstractState State
        {
            get { return state; }
            set { state = value; }
        }

        public Context(AbstractState state)
        {
            this.state = state;
        }

        public void Request()
        {
            state.Handle(this);
        }
    }
}

Main 函数中代码:

using System;
using State;
namespace StateTest
{
    class Program
    {
        static void Main(string[] args)
        {
            //初始化一个状态(这里是 ConcreteStateA)给 Context
            Context context = new Context(new ConcreteStateA());
            //通过不断的请求来改变 Context 的状态
              context.Request();
            context.Request();
            context.Request();
            context.Request();
            context.Request();
            context.Request();
            context.Request();
            Console.Read();
        }
    }
}

最后的效果展示:

image

注意上述代码中使用红色标记的几行代码,这几行代码呢,就是表示在 ConcreteState 中改变 Context 的状态,

比如在 ConcreteStateA 中,我将 Context 的状态切换回 ConcreteStateB ,

而 ConcreteStateB 中,我将 Context 的状态切换回 ConcreteStateC,

而 ConcreteStateC 中,我将 Context 的状态切换回 ConcreteStateA,

这样便实现了 Context 在 A , B ,C 这三种状态中循环的切换。

           

             

状态模式解决电子琴问题

         

下面呢,就是通过状态模式来解决上面这个电子琴的问题咯 !!!

上面的这个电子琴呢,因为有多个按键,每按一个键就会发出对应的声音,

我可以把其发出的声音看做属性,自然,电子琴就是具有状态的对象了,

然后呢,我通过定义一个状态的抽象类(代表抽象的声音),而将每一个发出的声音当做一个具体的状态(代表不同的声音),

也就是将每一个按键当做具体的状态(因为是通过按键来发声的)。

然后画出结构类图:

image

下面就直接贴出代码:

抽象 State 类 ElectronicOrgan:

namespace StateElectronicOrgan
{
    public abstract class ElectronicOrgan
    {
        public abstract void MakeMusic(Context context);
    }
}

具体 State 类:

所有的具体 State 代码

Context 类:

namespace StateElectronicOrgan
{
    public class Context
    {
        private ElectronicOrgan electronicOrgan;

        public ElectronicOrgan ElectronicOrgan
        {
            get { return electronicOrgan; }
            set { electronicOrgan = value; }
        }

        //在构造函数中初始化第一个音符
        public Context(ElectronicOrgan electronicOrgan)
        {
            this.electronicOrgan = electronicOrgan;
        }

        /// <summary>
        /// 通过不断的请求来完成发出不同的声音,最后便成了一首曲子了
        /// </summary>
        public void Request()
        {
            electronicOrgan.MakeMusic(this);
        }
    }
}

客户端代码:

using System;
using StateElectronicOrgan;

namespace StateElectronicOrganTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Context context = new Context(new State8());
            for (int i = 0; i < 10; i++)
            {
                context.Request();
            }

            Console.Read();
        }
    }
}

效果展示:

image

上面呢,就是通过状态模式来实现了电子琴的效果了,

相比于最初提出的两种方法(即直接在 Main 函数中,或者是在一个类中的方法中写入大量的 if 判断逻辑)来说,

使用状态模式的优点在于将和 Context 中的特定状态相关的行为局部化到各个具体的 State 类中,

然后就是解决了在一个方法中判断逻辑过多(单一职责原则)的问题,

并且将各个逻辑状态转移到了具体的 State 类中,从而减少了各个状态之间的依赖性。

不过状态模式的缺点也是显而易见的,就是奶奶的,平白加了这么多子类上去,

把人头都搞大了,问题似乎变得更加复杂了,我靠 . . .

所以,对于什么 2,3 个判断逻辑这些还是别用状态模式为妙。

其实呢,上面的这个电子琴的例子并适合用来介绍状态模式,因为,电子琴可以自己发出 n 中曲子,

而我上面使用状态模式呢,却钉死了每一个状态之间的切换,

所以上面的例子是在假设这个电子琴就只会播放一首曲子的前提下完成的。

       

      

总结状态模式的适用性

        

当一个对象的行为取决于它的状态时,并且必须在运行时根据状态来改变其行为时(差不多就是状态模式的定义了),

就可以使用状态模式。

当一个操作中有 n 多的判断逻辑时,并且这些分支是依赖于该对象的状态时,可以将每一个分支看做一个状态,

然后使用状态模式来进行处理。

        

             

                 

            

 

        

 

posted @ 2010-06-22 14:09  小宝马的爸爸  阅读(3295)  评论(10编辑  收藏  举报