Состояние (State)
Паттерн Состояние управляет изменением поведения объекта при изменении его внутреннего состояния. Внешне это выглядит так, словно объект меняет свой класс.
-
Context - контекст
-
State - абстрактное состояние
-
ConcreteStates - конкретные состояния
Абстрактная реализация на C# (GoF)
/// <summary>
/// MainApp startup class for Structural
/// State Design Pattern.
/// </summary>
internal class MainApp
{
/// <summary>
/// Entry point into console application.
/// </summary>
private static void Main()
{
// Setup context in a state
var context = new Context(new ConcreteStateA());
// Issue requests, which toggles state
context.Request();
context.Request();
context.Request();
context.Request();
// Wait for user
Console.ReadKey();
}
}
/// <summary>
/// The 'State' abstract class
/// </summary>
internal abstract class State
{
public abstract void Handle(Context context);
}
/// <summary>
/// A 'ConcreteState' class
/// </summary>
internal class ConcreteStateA : State
{
public override void Handle(Context context)
{
context.State = new ConcreteStateB();
}
}
/// <summary>
/// A 'ConcreteState' class
/// </summary>
internal class ConcreteStateB : State
{
public override void Handle(Context context)
{
context.State = new ConcreteStateA();
}
}
/// <summary>
/// The 'Context' class
/// </summary>
internal 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);
}
}
Реальная реализация на C# (GoF)
/// <summary>
/// MainApp startup class for Real-World
/// State Design Pattern.
/// </summary>
internal class MainApp
{
/// <summary>
/// Entry point into console application.
/// </summary>
private 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();
}
}
/// <summary>
/// The 'State' abstract class
/// </summary>
internal 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();
}
/// <summary>
/// A 'ConcreteState' class
/// <remarks>
/// Red indicates that account is overdrawn
/// </remarks>
/// </summary>
internal 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);
}
}
}
/// <summary>
/// A 'ConcreteState' class
/// <remarks>
/// Silver indicates a non-interest bearing state
/// </remarks>
/// </summary>
internal 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);
}
}
}
/// <summary>
/// A 'ConcreteState' class
/// <remarks>
/// Gold indicates an interest bearing state
/// </remarks>
/// </summary>
internal 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);
}
}
}
/// <summary>
/// The 'Context' class
/// </summary>
internal 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);
}
}
Улучшенная реальная реализация на C# (GoF)
/// <summary>
/// MainApp startup class for .NET optimized
/// State Design Pattern.
/// </summary>
internal class MainApp
{
/// <summary>
/// Entry point into console application.
/// </summary>
private 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();
}
}
/// <summary>
/// The 'State' abstract class
/// </summary>
internal 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();
}
/// <summary>
/// A 'ConcreteState' class
/// <remarks>
/// Red state indicates account is overdrawn
/// </remarks>
/// </summary>
internal 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);
}
}
}
/// <summary>
/// A 'ConcreteState' class
/// <remarks>
/// Silver state is non-interest bearing state
/// </remarks>
/// </summary>
internal 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);
}
}
}
/// <summary>
/// A 'ConcreteState' class
/// <remarks>
/// Gold incidates interest bearing state
/// </remarks>
/// </summary>
internal 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);
}
}
}
/// <summary>
/// The 'Context' class
/// </summary>
internal 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);
}
}
Реализация на C# (Head First)
internal class GumballMachineTestDrive
{
private 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--;
}
}
private 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 Gumball Machine
#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 State
Реализация на JAVA
TODO