学习设计模式第二十三 - 状态模式

示例代码来自DoFactory

 

概述

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。当然如果这个状态判断很简单,那就没必要用"状态模式"了。

 

意图

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

 

UML

图1 状态模式的UML图

 

参与者

这个模式涉及的类或对象:

  • Context

    • 定义供客户端发起请求的接口

    • 维护一个定义当前状态的ConcreteState子类的实例。

  • State

    • 定义一个封装与Context的特定状态(State)相关联的行为的接口。

  • Concrete State(系列类型)

    • 每一个子类实现了一个与一个Context的state相关的行为

 

适用性

一个对象的状态通过其实例变量的值来表现。客户端可以通过调用方法或属性来依次改变实例变量从而改变对象的状态。这种类型的对象被称作具状态型对象。状态在一个复杂系统中常常是一个核心概念,如,股市交易系统,供求系统,文档管理系统,特别是工作流系统。

复杂性可能传播到数个类中,而你可以使用状态模式来控制这种复杂性。在这种模式中你将状态决定的行为封装在一组相关的类中,其中每一个类都表示一种不同的状态。这种方法降低了如if和else这样复杂且难以跟踪的条件语句的使用,反之依靠多态来实现所需的状态转换功能。

状态模式的目标是将对象决定的逻辑限制在一系列均用来表示一个特定状态的对象的集合中。状态转换图(也称状态机)在对这类复杂系统建模时非常有用。状态模式通过将对状态转换的响应分散到一个有限集合(集合中每一项都是系统状态的一个表示)中来简化编程。

下面是几种状态模式的具体应用场景:

  1. 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。

  2. 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通 常用一个或多个枚举常量表示。通常, 有多个操作包含这一相同的条件结构。State 模式将 每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作 为一个对象,这一对象可以不依赖于其他对象而独立变化。

一句话,当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式。

 

DoFactory GoF代码

标准示例中演示了通过状态模式使一个对象可以依赖于其内部状态表现出不同的行为。行为的不同被委托给表示状态的对象来决定。

// State pattern 
// Structural example 
using System;
 
namespace DoFactory.GangOfFour.State.Structural
{
    // MainApp test application
    class MainApp
    {
        static void Main()
        {
            // Setup context in a state
            Context c = new Context(new ConcreteStateA());
 
            // Issue requests, which toggles state
            c.Request();
            c.Request();
            c.Request();
            c.Request();
 
            // Wait for user
            Console.ReadKey();
        }
    }
 
    // "State"
    abstract class State
    {
        public abstract void Handle(Context context);
    }
 
    // "ConcreteState"
    class ConcreteStateA : State
    {
        public override void Handle(Context context)
        {
            context.State = new ConcreteStateB();
        }
    }
 
    // "ConcreteState"
    class ConcreteStateB : State
    {
        public override void Handle(Context context)
        {
            context.State = new ConcreteStateA();
        }
    }
 
    // "Context"
    class Context
    {
        private State _state;
 
        // Constructor
        public Context(State state)
        {
            this.State = state;
        }
 
        // Gets or sets the state
        public State State
        {
            get { return _state; }
            set
            {
                _state = value;
                Console.WriteLine("State: " + _state.GetType().Name);
            }
        }
 
        public void Request()
        {
            _state.Handle(this);
        }
    }
}

实际应用的例子中通过状态模式使一个账户根据其余额不同表现不同的行为。行为的不同被委托给名为RedState,SilverState和GoldState的State对象来决定。这些对象分别表示透支的账户,初级账户和声誉良好的账户。

例子中涉及到的类与状态模式中标准的类对应关系如下:

  • Context – Account

  • State – State

  • Concrete State – RedState, SilverState, GoldState

// State pattern 
// Real World example 
using System;
 
namespace DoFactory.GangOfFour.State.RealWorld
{
    // MainApp test application
    class MainApp
    {
        static void Main()
        {
            // Open a new account
            Account account = new Account("Jim Johnson");
 
            // Apply financial transactions
            account.Deposit(500.0);
            account.Deposit(300.0);
            account.Deposit(550.0);
            account.PayInterest();
            account.Withdraw(2000.00);
            account.Withdraw(1100.00);
 
            // Wait for user
            Console.ReadKey();
        }
    }
 
    // "State"
    abstract class State
    {
        protected Account account;
        protected double balance;
 
        protected double interest;
        protected double lowerLimit;
        protected double upperLimit;
 
        // Properties
        public Account Account
        {
            get { return account; }
            set { account = value; }
        }
 
        public double Balance
        {
            get { return balance; }
            set { balance = value; }
        }
 
        public abstract void Deposit(double amount);
        public abstract void Withdraw(double amount);
        public abstract void PayInterest();
    }
 
    // "ConcreteState"
    // Account is overdrawn
    class RedState : State
    {
        private double _serviceFee;
 
        // Constructor
        public RedState(State state)
        {
            this.balance = state.Balance;
            this.account = state.Account;
            Initialize();
        }
 
        private void Initialize()
        {
            // Should come from a datasource
            interest = 0.0;
            lowerLimit = -100.0;
            upperLimit = 0.0;
            _serviceFee = 15.00;
        }
 
        public override void Deposit(double amount)
        {
            balance += amount;
            StateChangeCheck();
        }
 
        public override void Withdraw(double amount)
        {
            amount = amount - _serviceFee;
            Console.WriteLine("No funds available for withdrawal!");
        }
 
        public override void PayInterest()
        {
            // No interest is paid
        }
 
        private void StateChangeCheck()
        {
            if (balance > upperLimit)
            {
                account.State = new SilverState(this);
            }
        }
    }
 
    // "ConcreteState"
    // Silver is non-interest bearing state
    class SilverState : State
    {
        // Overloaded constructors
 
        public SilverState(State state) :
            this(state.Balance, state.Account)
        {
        }
 
        public SilverState(double balance, Account account)
        {
            this.balance = balance;
            this.account = account;
            Initialize();
        }
 
        private void Initialize()
        {
            // Should come from a datasource
            interest = 0.0;
            lowerLimit = 0.0;
            upperLimit = 1000.0;
        }
 
        public override void Deposit(double amount)
        {
            balance += amount;
            StateChangeCheck();
        }
 
        public override void Withdraw(double amount)
        {
            balance -= amount;
            StateChangeCheck();
        }
 
        public override void PayInterest()
        {
            balance += interest * balance;
            StateChangeCheck();
        }
 
        private void StateChangeCheck()
        {
            if (balance < lowerLimit)
            {
                account.State = new RedState(this);
            }
            else if (balance > upperLimit)
            {
                account.State = new GoldState(this);
            }
        }
    }
 
    // "ConcreteState"
    // Interest bearing state
    class GoldState : State
    {
        // Overloaded constructors
        public GoldState(State state)
            : this(state.Balance, state.Account)
        {
        }
 
        public GoldState(double balance, Account account)
        {
            this.balance = balance;
            this.account = account;
            Initialize();
        }
 
        private void Initialize()
        {
            // Should come from a database
            interest = 0.05;
            lowerLimit = 1000.0;
            upperLimit = 10000000.0;
        }
 
        public override void Deposit(double amount)
        {
            balance += amount;
            StateChangeCheck();
        }
 
        public override void Withdraw(double amount)
        {
            balance -= amount;
            StateChangeCheck();
        }
 
        public override void PayInterest()
        {
            balance += interest * balance;
            StateChangeCheck();
        }
 
        private void StateChangeCheck()
        {
            if (balance < 0.0)
            {
                account.State = new RedState(this);
            }
            else if (balance < lowerLimit)
            {
                account.State = new SilverState(this);
            }
        }
    }
 
    // "Context"
    class Account
    {
        private State _state;
        private string _owner;
 
        // Constructor
        public Account(string owner)
        {
            // New accounts are 'Silver' by default
            this._owner = owner;
            this._state = new SilverState(0.0, this);
        }
 
        // Properties
        public double Balance
        {
            get { return _state.Balance; }
        }
 
        public State State
        {
            get { return _state; }
            set { _state = value; }
        }
 
        public void Deposit(double amount)
        {
            _state.Deposit(amount);
            Console.WriteLine("Deposited {0:C} --- ", amount);
            Console.WriteLine(" Balance = {0:C}", this.Balance);
            Console.WriteLine(" Status  = {0}", this.State.GetType().Name);
            Console.WriteLine("");
        }
 
        public void Withdraw(double amount)
        {
            _state.Withdraw(amount);
            Console.WriteLine("Withdrew {0:C} --- ", amount);
            Console.WriteLine(" Balance = {0:C}", this.Balance);
            Console.WriteLine(" Status  = {0}\n", this.State.GetType().Name);
        }
 
        public void PayInterest()
        {
            _state.PayInterest();
            Console.WriteLine("Interest Paid --- ");
            Console.WriteLine(" Balance = {0:C}", this.Balance);
            Console.WriteLine(" Status  = {0}\n", this.State.GetType().Name);
        }
    }
}

.NET优化版例子实现了与上面例子相同的功能,更多的采用了现代化的.NET内置特性,如泛型、反射、自动属性和对象初始化器等

// State pattern 
// .NET Optimized example 
using System;
 
namespace DoFactory.GangOfFour.State.NETOptimized
{
    class MainApp
    {
        static void Main()
        {
            // Open a new account
            var account = new Account("Jim Johnson");
 
            // Apply financial transactions
            account.Deposit(500.0);
            account.Deposit(300.0);
            account.Deposit(550.0);
            account.PayInterest();
            account.Withdraw(2000.00);
            account.Withdraw(1100.00);
 
            // Wait for user
            Console.ReadKey();
        }
    }
 
    // "State"
    abstract class State
    {
        protected double interest;
        protected double lowerLimit;
        protected double upperLimit;
 
        // Gets or sets the account
        public Account Account { get; set; }
 
        // Gets or sets the balance
        public double Balance { get; set; }
 
        public abstract void Deposit(double amount);
        public abstract void Withdraw(double amount);
        public abstract void PayInterest();
    }
 
    // "ConcreteState"
    // Account is overdrawn
    class RedState : State
    {
        private double _serviceFee;
 
        // Constructor
        public RedState(State state)
        {
            Balance = state.Balance;
            Account = state.Account;
            Initialize();
        }
 
        private void Initialize()
        {
            // Should come from a datasource
            interest = 0.0;
            lowerLimit = -100.0;
            upperLimit = 0.0;
            _serviceFee = 15.00;
        }
 
        public override void Deposit(double amount)
        {
            Balance += amount;
            StateChangeCheck();
        }
 
        public override void Withdraw(double amount)
        {
            amount = amount - _serviceFee;
            Console.WriteLine("No funds available for withdrawal!");
        }
 
        public override void PayInterest()
        {
            // No interest is paid
        }
 
        private void StateChangeCheck()
        {
            if (Balance > upperLimit)
            {
                Account.State = new SilverState(this);
            }
        }
    }
 
    // "ConcreteState"
    // Silver is non-interest bearing state
    class SilverState : State
    {
        // Overloaded constructors
        public SilverState(State state) :
            this(state.Balance, state.Account)
        {
        }
 
        public SilverState(double balance, Account account)
        {
            Balance = balance;
            Account = account;
            Initialize();
        }
 
        private void Initialize()
        {
            // Should come from a datasource
            interest = 0.0;
            lowerLimit = 0.0;
            upperLimit = 1000.0;
        }
 
        public override void Deposit(double amount)
        {
            Balance += amount;
            StateChangeCheck();
        }
 
        public override void Withdraw(double amount)
        {
            Balance -= amount;
            StateChangeCheck();
        }
 
        public override void PayInterest()
        {
            Balance += interest * Balance;
            StateChangeCheck();
        }
 
        private void StateChangeCheck()
        {
            if (Balance < lowerLimit)
            {
                Account.State = new RedState(this);
            }
            else if (Balance > upperLimit)
            {
                Account.State = new GoldState(this);
            }
        }
    }
 
    // "ConcreteState"
    // Interest bearing state
    class GoldState : State
    {
        // Overloaded constructors
        public GoldState(State state)
            : this(state.Balance, state.Account)
        {
        }
 
        public GoldState(double balance, Account account)
        {
            Balance = balance;
            Account = account;
            Initialize();
        }
 
        private void Initialize()
        {
            // Should come from a database
            interest = 0.05;
            lowerLimit = 1000.0;
            upperLimit = 10000000.0;
        }
 
        public override void Deposit(double amount)
        {
            Balance += amount;
            StateChangeCheck();
        }
 
        public override void Withdraw(double amount)
        {
            Balance -= amount;
            StateChangeCheck();
        }
 
        public override void PayInterest()
        {
            Balance += interest * Balance;
            StateChangeCheck();
        }
 
        private void StateChangeCheck()
        {
            if (Balance < 0.0)
            {
                Account.State = new RedState(this);
            }
            else if (Balance < lowerLimit)
            {
                Account.State = new SilverState(this);
            }
        }
    }
 
    // "Context"
    class Account
    {
        private string _owner;
 
        // Constructor
        public Account(string owner)
        {
            // New accounts are 'Silver' by default
            this._owner = owner;
            this.State = new SilverState(0.0, this);
        }
 
        // Gets the balance
        public double Balance
        {
            get { return State.Balance; }
        }
 
        // Gets or sets state
        public State State { get; set; }
 
        public void Deposit(double amount)
        {
            State.Deposit(amount);
            Console.WriteLine("Deposited {0:C} --- ", amount);
            Console.WriteLine(" Balance = {0:C}", this.Balance);
            Console.WriteLine(" Status  = {0}", this.State.GetType().Name);
            Console.WriteLine("");
        }
 
        public void Withdraw(double amount)
        {
            State.Withdraw(amount);
            Console.WriteLine("Withdrew {0:C} --- ", amount);
            Console.WriteLine(" Balance = {0:C}", this.Balance);
            Console.WriteLine(" Status  = {0}\n", this.State.GetType().Name);
        }
 
        public void PayInterest()
        {
            State.PayInterest();
            Console.WriteLine("Interest Paid --- ");
            Console.WriteLine(" Balance = {0:C}", this.Balance);
            Console.WriteLine(" Status  = {0}\n", this.State.GetType().Name);
        }
    }
}

 

来自《深入浅出设计模式》的例子

这个例子使用状态模式实现了一个糖果自动售卖机。注意哦,这个机器有一个特色功能,顾客在购买时有10%的概率幸运的再得到一颗糖果。

using System;
using System.Text;
 
namespace DoFactory.HeadFirst.State.GumballStateWinner
{
    class GumballMachineTestDrive
    {
        static void Main(string[] args)
        {
            var machine = new GumballMachine(10);
 
            Console.WriteLine(machine);
 
            machine.InsertQuarter();
            machine.TurnCrank();
            machine.InsertQuarter();
            machine.TurnCrank();
 
            Console.WriteLine(machine);
 
            machine.InsertQuarter();
            machine.TurnCrank();
            machine.InsertQuarter();
            machine.TurnCrank();
 
            Console.WriteLine(machine);
 
            machine.InsertQuarter();
            machine.TurnCrank();
            machine.InsertQuarter();
            machine.TurnCrank();
 
            Console.WriteLine(machine);
 
            machine.InsertQuarter();
            machine.TurnCrank();
            machine.InsertQuarter();
            machine.TurnCrank();
 
            Console.WriteLine(machine);
 
            machine.InsertQuarter();
            machine.TurnCrank();
            machine.InsertQuarter();
            machine.TurnCrank();
 
            Console.WriteLine(machine);
 
            // Wait for user
            Console.ReadKey();
        }
    }
 
    #region Gumball Machine
 
    public class GumballMachine
    {
        private IState _soldOutState;
        private IState _noQuarterState;
        private IState _hasQuarterState;
        private IState _soldState;
        private IState _winnerState;
 
        public IState State { get; set; }
        public int Count { get; private set; }
 
        public GumballMachine(int count)
        {
            _soldOutState = new SoldOutState(this);
            _noQuarterState = new NoQuarterState(this);
            _hasQuarterState = new HasQuarterState(this);
            _soldState = new SoldState(this);
            _winnerState = new WinnerState(this);
 
            Count = count;
            if (Count > 0)
            {
                State = _noQuarterState;
            }
            else
            {
                State = _soldOutState;
            }
        }
 
        public void InsertQuarter()
        {
            State.InsertQuarter();
        }
 
        public void EjectQuarter()
        {
            State.EjectQuarter();
        }
 
        public void TurnCrank()
        {
            State.TurnCrank();
            State.Dispense();
        }
 
        public void ReleaseBall()
        {
            if (Count > 0)
            {
                Console.WriteLine("A gumball comes rolling out the slot...");
                Count--;
            }
        }
 
        void Refill(int count)
        {
            Count = count;
            State = _noQuarterState;
        }
 
        public IState GetSoldOutState()
        {
            return _soldOutState;
        }
 
        public IState GetNoQuarterState()
        {
            return _noQuarterState;
        }
 
        public IState GetHasQuarterState()
        {
            return _hasQuarterState;
        }
 
        public IState GetSoldState()
        {
            return _soldState;
        }
 
        public IState GetWinnerState()
        {
            return _winnerState;
        }
 
        public override string ToString()
        {
            StringBuilder result = new StringBuilder();
            result.Append("\nMighty Gumball, Inc.");
            result.Append("\n.NET-enabled Standing Gumball Model #2004");
            result.Append("\nInventory: " + Count + " gumball");
            if (Count != 1)
            {
                result.Append("s");
            }
            result.Append("\n");
            result.Append("Machine is " + State + "\n");
            return result.ToString();
        }
    }
 
    #endregion
 
    #region State
 
    public interface IState
    {
        void InsertQuarter();
        void EjectQuarter();
        void TurnCrank();
        void Dispense();
    }
 
    public class SoldState : IState
    {
        private GumballMachine _machine;
 
        public SoldState(GumballMachine machine)
        {
            this._machine = machine;
        }
 
        public void InsertQuarter()
        {
            Console.WriteLine("Please wait, we're already giving you a gumball");
        }
 
        public void EjectQuarter()
        {
            Console.WriteLine("Sorry, you already turned the crank");
        }
 
        public void TurnCrank()
        {
            Console.WriteLine("Turning twice doesn't get you another gumball!");
        }
 
        public void Dispense()
        {
            _machine.ReleaseBall();
            if (_machine.Count > 0)
            {
                _machine.State = _machine.GetNoQuarterState();
            }
            else
            {
                Console.WriteLine("Oops, out of gumballs!");
                _machine.State = _machine.GetSoldOutState();
            }
        }
 
        public override string ToString()
        {
            return "dispensing a gumball";
        }
    }
 
    public class SoldOutState : IState
    {
        private GumballMachine _machine;
 
        public SoldOutState(GumballMachine machine)
        {
            this._machine = machine;
        }
 
        public void InsertQuarter()
        {
            Console.WriteLine("You can't insert a quarter, the machine is sold out");
        }
 
        public void EjectQuarter()
        {
            Console.WriteLine("You can't eject, you haven't inserted a quarter yet");
        }
 
        public void TurnCrank()
        {
            Console.WriteLine("You turned, but there are no gumballs");
        }
 
        public void Dispense()
        {
            Console.WriteLine("No gumball dispensed");
        }
 
        public override string ToString()
        {
            return "sold out";
        }
    }
 
    public class NoQuarterState : IState
    {
        private GumballMachine _machine;
 
        public NoQuarterState(GumballMachine machine)
        {
            this._machine = machine;
        }
 
        public void InsertQuarter()
        {
            Console.WriteLine("You inserted a quarter");
            _machine.State = _machine.GetHasQuarterState();
        }
 
        public void EjectQuarter()
        {
            Console.WriteLine("You haven't inserted a quarter");
        }
 
        public void TurnCrank()
        {
            Console.WriteLine("You turned, but there's no quarter");
        }
 
        public void Dispense()
        {
            Console.WriteLine("You need to pay first");
        }
 
        public override string ToString()
        {
            return "waiting for quarter";
        }
    }
 
    public class HasQuarterState : IState
    {
        private GumballMachine _machine;
 
        public HasQuarterState(GumballMachine machine)
        {
            this._machine = machine;
        }
 
        public void InsertQuarter()
        {
            Console.WriteLine("You can't insert another quarter");
        }
 
        public void EjectQuarter()
        {
            Console.WriteLine("Quarter returned");
            _machine.State = _machine.GetNoQuarterState();
        }
 
        public void TurnCrank()
        {
            Console.WriteLine("You turned...");
            Random random = new Random();
 
            int winner = random.Next(11);
            if ((winner == 0) && (_machine.Count > 1))
            {
                _machine.State = _machine.GetWinnerState();
            }
            else
            {
                _machine.State = _machine.GetSoldState();
            }
        }
 
        public void Dispense()
        {
            Console.WriteLine("No gumball dispensed");
        }
 
        public override string ToString()
        {
            return "waiting for turn of crank";
        }
    }
 
    public class WinnerState : IState
    {
        private GumballMachine _machine;
 
        public WinnerState(GumballMachine machine)
        {
            this._machine = machine;
        }
 
        public void InsertQuarter()
        {
            Console.WriteLine("Please wait, we're already giving you a Gumball");
        }
 
        public void EjectQuarter()
        {
            Console.WriteLine("Please wait, we're already giving you a Gumball");
        }
 
        public void TurnCrank()
        {
            Console.WriteLine("Turning again doesn't get you another gumball!");
        }
 
        public void Dispense()
        {
            Console.WriteLine("YOU'RE A WINNER! You get two gumballs for your quarter");
            _machine.ReleaseBall();
            if (_machine.Count == 0)
            {
                _machine.State = _machine.GetSoldOutState();
            }
            else
            {
                _machine.ReleaseBall();
                if (_machine.Count > 0)
                {
                    _machine.State = _machine.GetNoQuarterState();
                }
                else
                {
                    Console.WriteLine("Oops, out of gumballs!");
                    _machine.State = _machine.GetSoldOutState();
                }
            }
        }
 
        public override string ToString()
        {
            return "despensing two gumballs for your quarter, because YOU'RE A WINNER!";
        }
    }
    #endregion
}

 

.NET中的状态模式

目前所知.NET Framework没有使用状态模式。

效果及实现要点

状态模式的好处是将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。

将特定的状态相关的行为都放入一个对象中,由于所有的状态相关的代码都存在于某个ConcreteState中,所以通过定义新的子类可以很容易地增加新的状态和转换。另外这样可以把某种状态下的后续所有行为都局部化了。

使用状态模式一个很大的作用与目的就是消除庞大的条件分支语句。

状态模式通过把各种状态转移逻辑分布到State的子类之间,来减少相互的依赖。

状态类可以被多个Context实例共享,但使用状态模式也通常会导致设计中类的数目大量增加。

 

与其他模式比较

状态模式的类图和策略模式看起来是一样的,这两种模式的差别在于它们的意图不同。在状态模式下,Context的行为委托到状态对象的一个,而具体行为由于被封装在状态对象中所以它们随着状态的变化而变化,另外Context的客户可能也不知道状态的变化,因为状态变化可能是由Context或一个状态对象主导的。

而策略模式中,指定不同的具体策略也是为了改变行为,而这是由客户端主动指定Context使用的具体策略对象,并且对于某个context对象,通常只有一个最适当策略对象。

 

总结

恰当使用状态模式,可以减少判断语句的使用,对象可以根据状态的变化自动变换行为。

posted @ 2015-01-18 15:58  hystar  阅读(255)  评论(0)    收藏  举报