|
20. Стан — State
ADO в Delphi AJAX Android C++ CakePHP CMS COM CSS Delphi Flash Flex HTML Internet Java JavaScript MySQL PHP RIA SCORM Silverlight SQL UML XML Бази даних Веб-розробка Генетичні алгоритми ГІС Гітара Дизайн Економіка Інтелектуальні СДН Колір Масаж Математика Медицина Музика Нечітка логіка ООП Патерни Подання знань Розкрутка сайту, SEO САПР Сесії в PHP Системне програмування Системний аналіз Тестологія Тестування ПЗ Фреймворки Штучний інтелект
|
20. Стан — State
Уявімо, що ми маємо розробити програму для опрацювання замовлень (Orders). Замовлення можуть бути в одному із декількох станів: новий (NewOrder), зареєстрований (Registered), погоджений (Granted), відправлений (Shipped), оплачений (Invoiced), відмінений (Cancelled). Крім правил переходу є ще й інші правила, які визначають поведінку вашого замовлення. Наприклад, не можна додати продукт до замовлення тоді, коли воно є у відміненому стані. Як можна гарно й чітко реалізувати таку систему поведінки замовлення? Стан дозволяє винести логіку визначення стану об’єкту та його поведінку, характерну для цього стану, в інші класи. Щоб поведінка замовлення і його станів була зрозуміла, глянемо на наступну діаграму станів (state-chart):
UML-діаграма 11. Діаграма станів замовлення Ми можемо інкапсулювати поведінку, що пов’язана зі станом об’єкту в класах різних станів, що наслідуються від одного базового класу. Кожна із конкретних реалізацій буде відповідальна за надання можливості переходу з одного стану в інший.
UML-діаграма 12. Діаграма патерну Стан для нашого прикладу із замовленнями І як же воно працює? Для початку зауважимо, що клас Order має поле-посилання на стан _state. Для того, щоб приклад вигладав правдоподібніше, добавимо також товари _products. Уривок коду 20.1. Замовленняclass Order { private OrderState _state; private List<Product> _products = new List<Product>(); 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); } // І так далі... Уривок коду 20.2. В базовому класі Order делегує поведінку поточному стануpublic void Ship() { _state.Ship(); } Наприклад, якщо поточний стан Granted, то метод _state.Ship() змінить стан замовлення на Shipped і якщо потрібно, зробить ще якусь специфічну для цього стану роботу. Код нижче зображає конкретну реалізацію одного із станів. Конструктор базового класу містить параметер типу Order, що дозволяє стану містити поле-посилання на власника цього стану. Уривок коду 20.3. Конкретна реалізація одного із станів
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));
}
public override void Cancel()
{
_order.DoCancel();
_order.SetOrderState(new Cancelled(_order));
}
}
Якщо вас зацікавили методи класу Order на подобі DoShipping(), то в нашому прикладі вони імітують роботу, просто виводячи інформацію про дії з замовленням, але, звісно, ми вміщаємо там необхідну хитру логіку для виконання операцій, пов’язаних із поточним станом: Уривок коду 20.4. Метод DoShipping для виконання загальної логіки
public void DoShipping()
{
// Тут водій вантажівки нагружає ваше замовлення і «рулить»
Console.WriteLine("Shipping...");
}
З іншого боку, логіку, яка стосується самого продукту, ми можемо виконувати у зовнішніх методах нашого замовлення не перевикликаючи її потім із стану, але це залежить від нас: Уривок коду 20.5. Додавання продукту до замовленняpublic void AddProduct(Product product) { _products.Add(product); _state.AddProduct(); } Якщо поточний стан Registered, то швидше за все такий стан не має перевизначеного методу ship(), а має тільки методи addProduct(), grant() та cancel(). Таким чином метод базового класу буде викликаний. OrderState, він же базовий клас, має всі методи, які можуть бути перевизначені у станах, але всі вони «плюються» ексепшинами, або ж просто виводять щось у консоль, як у нашому прикладі: Уривок коду 20.6. Базовий клас стану замовленняclass OrderState { public Order _order; public OrderState(Order order) { _order = order; } public virtual void AddProduct() { OperationIsNotAllowed("AddProduct"); } // Наступні методи (Register, Grant, Ship, Invoice, Cancel) виглядають так же private void OperationIsNotAllowed(string operationName) { Console.WriteLine("Operation {0} is not allowed for Order's state {1}", operationName, this.GetType().Name); } } Здійснимо певний перелік операцій із ствонення замовлення, додаванням до нього нашого улюбленого пива і доставки на дім: Уривок коду 20.7. Декілька операцій для демонстрації використання
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();
Вивід:Current Order's state: NewOrder 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 . . . Давайте-но додамо ще трохи пивка до замовлення, яке нам вже відправили: Уривок коду 20.8. Демонстрація неправильного використання замовлення і поведінки стану
// Пробуємо дозамовити пива до вже відправленого замовлення
// і дивимося що буде (див. довгий червоний рядок у виводі)
order.AddProduct(beer);
order.WriteCurrentStateName();
Вивід:Current Order's state: NewOrder 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 Operation AddProduct is not allowed for Order's state Shipped Current Order's state: Shipped Press any key to continue . . . Одним із суттєвих недоліків цього дизайн патерну є розплід великої кількості класів станів:
Малюнок 4. Розплід великої кількості класів-станів Але з іншої сторони, саме так ми можемо чітко розділяти поведінку в залежності від станів. Я читав про вирішення цієї проблеми за допомогою таблички на зразок [state|method|state], що зберігає дозволені переходи. Проблема також може бути вирішена за допомогою switch («мда», щось воно не звучить)!
По матеріалам книги Андрія Будая "Дизайн патерни – просто, як двері". Матеріал розміщується за домовленістю з автором.
Зверніть увагу на додаткові посиланняЯкщо вас цікавить...Головний розділСторінки, близькі за змістомзагрузка...
|
Сторінки, близькі за змістом ![]() Шаблони проектування програмного забезпечення (англ. software design patterns) — ефективні способи вирішення задач проектування програмного забезпечення. Шаблон не є закінченим зразком, який можна безпосередньо транслювати в програмний код. Об'єктно-орієнтований шаблон найчастіше є зразком вирішення проблеми і відображає відношення між класами та об'єктами, без вказівки на те, як буде зрештою реалізоване це відношення. |
|
Copyright © 2008—2026 Портал Знань.
При використанні матеріалів посилання, для інтернет-ресурсів — гіперпосилання, на Znannya.org обов'язкове.
Зв'язок
|
НТУУ "КПІ" Інженерія програмного забезпечення КПІ Лабораторія СЕТ |
|