“Патерн Вівторка” #12: Стан (State)
|Як можна гарно й чітко реалізувати таку систему поведінки Замовлення?
Ми можемо інкапсулювати поведінку що пов’язана із станом об’єкту в класах різних станів, що наслідуються від якогось базового класу. Кожена із конкретних реалізацій буде відповідальна за надання можливості переходу із одного стану в інший.
Класична UML-діаграма дизнайн патерну Стан
І як же воно працює?
Для початку зауважимо, що клас Order має поле-посилання на стан _state. Для того щоб приклад вигладав більш правдоподібніше добавимо також товари _products.
public class Order
{
private OrderState _state;
private List_products = new List ();
public Order()
{
_state = new NewOrder(this);
}
public void SetOrderState(OrderState state)
{
_state = state;
}
public void WriteCurrentStateName()
{
Console.WriteLine("Current Order's state: {0}", _state.GetType().Name);
}
//...
Order делегує специфічну для стану поведінку поточному стану:
public void Ship()
{
_state.Ship();
}
public class Granted : OrderState
{
public Granted(Order order) : base(order)
{
}
public override void AddProduct()
{
_order.DoAddProduct();
}
public override void Ship()
{
_order.DoShipping();
_order.SetOrderState(new Shipped(_order));
}
Якщо вас зацікавив метод DoShipping() то для нашого прикладу він просто виводить що він у процесі перевезення:
public void DoShipping()
{
Console.WriteLine("Shipping...");
}
Проте логіку яка стосується самого продукту ми можемо виконувати зразу у зовнішніх методах нашого замовлення не перевикликаючи її потім із стану, але це залежить від нас:
public void AddProduct(Product product)
{
_products.Add(product);
_state.AddProduct();
}
public class OrderState
{
public Order _order;
public OrderState(Order order)
{
_order = order;
}
public virtual void Ship()
{
OperationIsNotAllowed("Ship");
}
// Other methods look similar...
private void OperationIsNotAllowed(string operationName)
{
Console.WriteLine("Operation {0} is not allowed for Order's state {1}", operationName, this.GetType().Name);
}
}
Приклад використання
Здійснимо певний перелік операцій із ствонення замовлення, додання до нього нашого улюбленого пива і доставки на дім:
public static void Run()
{
Product beer = new Product();
beer.Name = "MyBestBeer";
beer.Price = 78000;
Order order = new Order();
order.WriteCurrentStateName();
order.AddProduct(beer);
order.WriteCurrentStateName();
order.Register();
order.WriteCurrentStateName();
order.Grant();
order.WriteCurrentStateName();
order.Ship();
order.WriteCurrentStateName();
order.Invoice();
order.WriteCurrentStateName();
}
Вивід:
Adding product…
Current Order’s state: NewOrder
Registration…
Current Order’s state: Registered
Granting…
Current Order’s state: Granted
Shipping…
Current Order’s state: Shipped
Invoicing…
Current Order’s state: Invoiced
Press any key to continue . . .
Нумо додамо ще трохи пивка до замовлення, яке вже нам відправили:
order.Ship();
order.WriteCurrentStateName();
//trying to add more beer to already shipped order
order.AddProduct(beer);
order.WriteCurrentStateName();
Вивід:
Adding product…
Current Order’s state: NewOrder
Registration…
Current Order’s state: Registered
Granting…
Current Order’s state: Granted
Shipping…
Current Order’s state: Shipped
Current Order’s state: Shipped
Press any key to continue . . .
Інші способи вирішити нашу проблему (не із пивом)
Одним із суттєвих недоліків цього дизайн патерну є розплід векилої кількості класів станів:
Автор видалив цей коментар.
Обратите внимание на отличный FSM кит для .NET. Stateless – http://code.google.com/p/stateless/ . Намного проще (и прозрачнее) конфигурируется и работает. И каши (в виде большого количества подклассов) не просит 🙂