Стан. Різниця між паттернами Стан і Стратегія в Java патерн стан

Для того, щоб правильно використовувати патерни стан і стратегія в ядрі Java додатків, важливо для Java-програмістів чітко розуміти різницю між ними. Хоча обидва шаблону, Стан і стратегія, мають схожу структуру, і обидва засновані на принципі відкритості / закритості, що представляють "O" в SOLID принципах, вони абсолютно різні за намірам. патерн стратегія в Java використовується для інкапсуляції пов'язаних наборів алгоритмів для забезпечення гнучкості виконання для клієнта. Клієнт може вибрати будь-який алгоритм під час виконання без зміни контексту класу, який використовує об'єкт Strategy. Деякі популярні приклади паттерна стратегія - це написання коду, який використовує алгоритми, наприклад, шифрування, стиснення або сортування. З іншого боку, патерн Стан дозволяє об'єкту поводитися по-різному в різному стані. Оскільки в реальному світі об'єкт часто має стану, і він веде себе по-різному в різних станах, наприклад, торговий автомат продає товари тільки якщо він в змозі hasCoin, він не продає до тих пір поки ви не покладете в нього монету. Зараз ви можете ясно бачити різницю між паттернами Стратегія і Стан, це різні наміри. Патерн Стан допомагає об'єкту керувати станом, тоді як патерн Стратегія дозволяє вибрати клієнту іншу поведінку. Ще одна відмінність, яка не так легко побачити, це хто управляє зміною в поведінці. У разі паттерна Стратегія, це клієнт, який надає різні стратегії до контексту, в паттерне Стан переходом управляє контекст або стан об'єкта самостійно. Крім того, якщо ви керуєте змінами станів в об'єкті Стан самостійно, повинна бути посилання на контекст, наприклад, в торговому автоматі повинна бути можливість викликати метод setState () для зміни поточного стану контексту. З іншого боку, об'єкт Стратегія ніколи не містить посилання на контекст, сам клієнт передає Стратегію свого вибору в контекст. Різниця між паттернами Стан і Стратегія один з популярних питань про патернах Java на інтерв'ю, в цій статті про патернах Java ми детальніше розглянемо це. Ми будемо досліджувати деякі подібності та відмінності між паттернами Стратегія і Стан в Java, які допоможуть вам поліпшити ваше розуміння цих патернів.

Схожість між паттернами Стан і Стратегія

Якщо ви подивіться на UML-діаграму патернів Стан і стратегія, можна помітити, що обидва виглядають схоже один на одного. Об'єкт, який використовує Стан для зміни своєї поведінки відомий як Context-об'єкт, аналогічно об'єкт, який використовує Стратегію щоб змінити свою поведінку згадується як Context-об'єкт. Запам'ятайте, що клієнт взаємодіє з Context-об'єкт. У разі патерну Стан контекст делегує методи виклику об'єкту Стан, який утримується у вигляді поточного об'єкта, а в разі паттерна Стратегія контекст використовує об'єкт Стратегії як параметр або надається під час створення контексту об'єкта. UML діаграма патерну Стан в Java
Ця UML діаграма для патерну Стан, зображує класичну проблему створення об'єктно-орієнтованого дизайну торгового апарату в Java. Ви можете бачити, що стан торговельного апарату представлено з використанням інтерфейсу, який далі має реалізацію для подання конкретного стану. Кожне стан також має посилання на контекст об'єкта, щоб зробити перехід в інший стан в результаті дій викликаних в контексті.
Ця UML діаграма для патерну Стратегія містить функціональні реалізації угруповань. Оскільки є багато алгоритмів сортування, цей шаблон проектування дозволяє клієнтові вибрати алгоритм при сортуванні об'єктів. Насправді Java Collection framework використовує цей патерн реалізуючи метод Collections.sort (), який використовується для сортування об'єктів в Java. Єдина різниця в тому, що замість дозволу клієнта вибирати алгоритм сортування він дозволяє йому вказати стратегію порівняння передаючи екземпляр інтерфейсу Comparator або Comparable в Java. Давайте подивимося на кілька подібностей між цими двома основними шаблонами проектування в Java:
  1. Обидва патерну, Стан і стратегія, роблять нескладним додавання нового стану і стратегії не зачіпаючи контекст об'єкта, який використовує їх.

  2. Обидва з них підтримують ваш код відповідно до принципу відкритості / закритості, тобто дизайн буде відкритий для розширень, але закритий для модифікації. У разі патернів Стан і стратегія, контекст об'єкта закритий для модифікацій, введень нових Станів або нових стратегій, або ви не маєте потребу в модифікації контексту іншого стану, або мінімальних змінах.

  3. Також як контекст об'єкта починається з стану ініціалізації об'єкта в паттерне Стан, контекст об'єкта також має стратегію за замовчуванням в разі паттерна Стратегія в Java.

  4. Патерн Стан представляє різні поведінки в формі різних станів об'єкта, в той час як патерн Стратегія являє різну поведінку в вигляді різних стратегій об'єкта.

  5. Обидва патерну, Стратегія і Стан, залежать від підкласів реалізації поведінки. Кожна конкретна стратегія розширює Абстрактну Стратегію, кожне стан є підклас інтерфейсу або абстрактного класу, який використовується для кончини Стану.

Відмінності між патернами Стратегія і Стан в Java

Отже, тепер ми знаємо, що патерни Стан і Стратегія схожі за структурою, а їх наміри різні. Давайте розглянемо деякі ключові відмінності між цими шаблонами проектування.
  1. Патерн Стратегія инкапсулирует набір пов'язаних алгоритмів, і дозволяє клієнтові використовувати взаємозамінні поведінки незважаючи на склад і делегування під час виконання, з іншого боку патерн Стан допомагає класу демонструвати різні поведінки в різних станах.

  2. Наступна різниця між паттернами Стан і Стратегія полягає в тому, що Стан инкапсулирует стан об'єкта, тоді як патерн Стратегія инкапсулирует алгоритм або стратегію. Оскільки стан пов'язаний з об'єктом воно не може бути повторно використано, але відокремлюючи стратегію або алгоритм з контексту ми можемо використовувати його повторно.

  3. У паттерне Стан особистий статок може містити посилання на контекст для реалізації переходів між станами, але Стратегія не містить посилання на контекст де вона використовується.

  4. Реалізація Стратегії може бути передана як параметр об'єкту, який буде використовувати її, наприклад, Collection.sort () приймає Comparator, який є стратегією. З іншого боку, стан є частиною самого контексту об'єкта, і протягом довгого часу контекст об'єкта переходить з одного стану в інший.

  5. Хоча і Стратегія і Стан дотримуються принципу відкритості / закритості, Стратегія також слід Принципу Єдиною Обов'язки так як кожна Стратегія містить індивідуальний алгоритм, різні стратегії незалежні один від одного. Зміна однієї стратегії не вимагає зміни іншої стратегії.

  6. Ще одне теоретичне відмінність між паттернами Стратегія і Стан полягає в тому, що творець визначає частину об'єкта "Як", наприклад, "Як" об'єкт сортування сортує дані, з іншого боку патерн Стан визначає частини "що" і "коли" в об'єкті, наприклад , що може об'єкт коли він знаходиться в певному стані.

  7. Порядок переходу стану добре визначений в паттерне Стан, такої вимоги немає до паттерну Стратегія. Клієнт вільний у виборі будь-якої реалізації Стратегії на його вибір.

  8. Деякі з загальних прикладів паттерна Стратегія - це інкапсуляція алгоритмів, наприклад, алгоритми сортування, шифрування або алгоритм стиснення. Якщо ви бачите, що ваш код повинен використовувати різні види пов'язаних алгоритмів, слід подумати про використання патерну Стратегія. З іншого боку, розпізнати можливість використання патерну Стан досить легко, якщо вам потрібно керувати станом і переходами між станами без великої кількості вкладених умовних операторів патерн Стан - потрібний патерн для використання.

  9. Останнє, але одне з найбільш важливих відмінностей між паттернами Стан і Стратегія полягає в тому, що зміна Стратегії виконується Клієнтом, а зміна Стану може бути виконано контекстом або станом об'єкта самостійно.

Це все про різницю між паттернами Стан і Стратегія в Java. Як я сказав, обидва виглядають схоже в своїх класах і UML діаграмах, обидва забезпечують відкрито / закритий принцип і інкапсулюють поведінку. Використовуйте патерн Стратегія для інкапсулювання алгоритму або стратегії, який надається контексту під час виконання, можливо як параметр або складений об'єкт і використовуйте патерн Стан для управління переходами між станами в Java. оригінал

Останнє оновлення: 31.10.2015

Стан (State) - шаблон проектування, який дозволяє об'єкту змінювати свою поведінку в залежності від внутрішнього стану.

Коли застосовується даний патерн?

    Коли поведінка об'єкта повинно залежати від його стану і може змінюватися динамічно під час виконання

    Коли в коді методів об'єкта використовуються численні умовні конструкції, вибір яких залежить від поточного стану об'єкта

UML-діаграма даного шаблону проектування пропонує наступну систему:

Формальне визначення патерну на C #:

Class Program (static void Main () (Context context \u003d new Context (new StateA ()); context.Request (); // Перехід в стан StateB context.Request (); // Перехід в стан StateA)) abstract class State (public abstract void Handle (Context context);) class StateA: State (public override void Handle (Context context) (context.State \u003d new StateB ();)) class StateB: State (public override void Handle (Context context) ( context.State \u003d new StateA ();)) class Context (public State State (get; set;) public Context (State state) (this.State \u003d state;) public void Request () (this.State.Handle (this );))

Учасники паттерна

    State: визначає інтерфейс стану

    Класи StateA і StateB - конкретні реалізації станів

    Context: представляє об'єкт, поведінка якого має динамічно змінюватися відповідно до стану. Виконання ж конкретних дій делегується об'єкту стану

Наприклад, вода може знаходитися в ряді станів: тверде, рідке, парообразное. Припустимо, нам треба визначити клас Вода, у якого б були методи для нагрівання і заморозки води. Без використання патерну Стан ми могли б написати таку програму:

Class Program (static void Main (string args) (Water water \u003d new Water (WaterState.LIQUID); water.Heat (); water.Frost (); water.Frost (); Console.Read ();)) enum WaterState (SOLID, LIQUID, GAS) class Water (public WaterState State (get; set;) public Water (WaterState ws) (State \u003d ws;) public void Heat () (if (State \u003d\u003d WaterState.SOLID) (Console.WriteLine ( "Перетворюємо лід в рідину"); State \u003d WaterState.LIQUID;) else if (State \u003d\u003d WaterState.LIQUID) (Console.WriteLine ( "Перетворюємо рідину в пару"); State \u003d WaterState.GAS;) else if (State \u003d\u003d WaterState.GAS) (Console.WriteLine ( "Підвищуємо температуру водяної пари");)) public void Frost () (if (State \u003d\u003d WaterState.LIQUID) (Console.WriteLine ( "Перетворюємо рідина в лід"); State \u003d WaterState.SOLID;) else if (State \u003d\u003d WaterState.GAS) (Console.WriteLine ( "Перетворюємо водяна пара в рідина"); State \u003d WaterState.LIQUID;)))

Вода має три стану, і в кожному методі нам треба дивитися на поточний стан, щоб зробити дії. В результаті з трьох станів вже виходить нагромадження умовних конструкцій. Та й самим методів в класі Вода може також бути безліч, де також треба буде діяти в залежності від стану. Тому, щоб зробити програму більш гнучкою, в даному випадку ми можемо застосувати патерн Стан:

Class Program (static void Main (string args) (Water water \u003d new Water (new LiquidWaterState ()); water.Heat (); water.Frost (); water.Frost (); Console.Read ();)) class Water (public IWaterState State (get; set;) public Water (IWaterState ws) (State \u003d ws;) public void Heat () (State.Heat (this);) public void Frost () (State.Frost (this); )) interface IWaterState (void Heat (Water water); void Frost (Water water);) class SolidWaterState: IWaterState (public void Heat (Water water) (Console.WriteLine ( "Перетворюємо лід в рідину"); water.State \u003d new LiquidWaterState ();) public void Frost (Water water) (Console.WriteLine ( "Продовжуємо заморозку льоду");)) class LiquidWaterState: IWaterState (public void Heat (Water water) (Console.WriteLine ( "Перетворюємо рідину в пару") ; water.State \u003d new GasWaterState ();) public void Frost (Water water) (Console.WriteLine ( "Перетворюємо рідина в лід"); water.State \u003d new SolidWaterState ();)) class GasWaterSt ate: IWaterState (public void Heat (Water water) (Console.WriteLine ( "Підвищуємо температуру водяної пари"); ) Public void Frost (Water water) (Console.WriteLine ( "Перетворюємо водяна пара в рідина"); water.State \u003d new LiquidWaterState ();))

Таким чином, реалізація патерну Стан дозволяє винести поведінку, залежне від поточного стану об'єкта, в окремі класи, і уникнути перевантаженості методів об'єкта умовними конструкціями, як if..else або switch. Крім того, при необхідності ми можемо ввести в систему нові класи станів, а наявні класи станів використовувати в інших об'єктах.

«патернState » .ru джерело

Стан - патерн поведінки об'єктів, що задає різну функціональність в залежності від внутрішнього стану об'єкта. сайт сайт оригінал джерело

Умови, Завдання, Призначення

Дозволяє об'єкту варіювати свою поведінку в залежності від внутрішнього стану. Оскільки поведінка може змінюватися абсолютно довільно без будь-яких обмежень, ззовні створюється враження, що змінився клас об'єкта.

мотивація

Розглянемо клас TCPConnection, За допомогою якого представлено мережеве з'єднання. Об'єкт цього класу може перебувати в одному з декількох станів: Established (Встановлено), Listening (Прослуховування), Closed (Закрите). коли об'єкт TCPConnection отримує запити від інших об'єктів, то в залежності від поточного стану він відповідає по-різному. Наприклад, відповідь на запит Open (Відкрити) залежить від того, чи знаходиться з'єднання в стані Closed або Established. Патерн стан описує, яким чином об'єкт TCPConnection може вести себе по-різному, перебуваючи в різних станах. істочнік.ru

Основна ідея цього патерну полягає в тому, щоб ввести абстрактний клас TCPState для представлення різних станів сполуки. Цей клас оголошує інтерфейс, єдиний для всіх класів, що описують різні робочі істочнік.ru

стану. У цих підкласах TCPState реалізується поведінка, специфічне для конкретного стану. Наприклад, в класах TCPEstablished і TCPClosed реалізовано поведінка, характерна для станів Established і Closed відповідно. сайт сайт оригінал джерело

сайт оригінал джерело сайт

клас TCPConnection зберігає у себе об'єкт стану (екземпляр підкласу TCPState), Що представляє поточний стан з'єднання, і делегує всіх залежних від стану запити цьому об'єкту. TCPConnection використовує свій екземпляр підкласу TCPState досить просто: викликаючи методи єдиного інтерфейсу TCPState, Тільки в залежності від того який в даний момент зберігається конкретний підклас TCPState-а - результат виходить різним, тобто в реальності виконуються операції, властиві тільки даному стану з'єднання. орігінал.ru джерело

А при кожній зміні стану з'єднанняTCPConnection змінює свій об'єкт-стан. Наприклад, коли встановлене з'єднання закривається, TCPConnection замінює екземпляр класу TCPEstablished екземпляром TCPClosed. сайт оригінал джерело сайт

Ознаки застосування, використання патерну Стан (State)

Використовуйте патерн стан в наступних випадках: джерело орігінал.ru
  1. Коли поведінка об'єкта залежить від його стану і при цьому має змінюватися під час виконання. .ru
  2. Коли в коді операцій зустрічаються складаються з багатьох гілок умовні оператори, в яких вибір гілки залежить від стану. Зазвичай в такому випадку стан представлено переліком константами. Часто одна і та ж структура умовного оператора повторюється в кількох операціях.Паттерн стан пропонує помістити кожну гілку в окремий клас. Це дозволяє трактувати стан об'єкта як самостійний об'єкт, який може змінюватися незалежно від інших. джерело орігінал.ru

Рішення

сайт джерело сайт оригінал

орігінал.ru

Учасники патерну Стан (State)

джерело орігінал.ru
  1. Context (TCPConnection) - контекст.
    Визначає єдиний інтерфейс для клієнтів.
    Зберігає екземпляр підкласу ConcreteState, Яким визначається поточний стан. орігінал.ru
  2. State (TCPState) - стан.
    Визначає інтерфейс для інкапсуляції поведінки, асоційованого з конкретним станом контексту Context. джерело орігінал.ru
  3. підкласи ConcreteState (TCPEstablished, TCPListen, TCPClosed) - конкретний стан.
    Кожен підклас реалізує поведінку, асоційоване з деяким станом контексту Context. сайт сайт оригінал джерело

Схема використання патерну Стан (State)

клас Context делегує запити поточного об'єкту ConcreteState. сайт сайт оригінал джерело

Контекст може передати себе в якості аргументу об'єкту State, Який буде обробляти запит. Це дає можливість об'єкту-станом ( ConcreteState) При необхідності отримати доступ до контексту. сайт оригінал джерело сайт

Context - це основний інтерфейс для клієнтів. Клієнти можуть конфігурувати контекст об'єктами стану State (точніше ConcreteState). Один раз сконфігурованої контекст, клієнти вже не повинні безпосередньо зв'язуватися з об'єктами стану (тільки через загальний інтерфейс State). сайт джерело сайт оригінал

При цьому або Context, Або самі підкласи ConcreteState можуть вирішити, за яких умов і в якому порядку відбувається зміна станів. .ru джерело

Питання, що стосуються реалізації паттерна Стан (State)

Питання, що стосуються реалізації паттерна State: орігінал.ru джерело
  1. Що визначає переходи між станами.
    Патерн стан нічого не повідомляє про те, який учасник визначає умови (критерії) переходу між станами. Якщо критерії зафіксовані, то їх можна реалізувати безпосередньо в класі Context. Однак в загальному випадку більш гнучкий і правильний підхід полягає в тому, щоб дозволити самим подклассам класу State визначати наступний стан і момент переходу. Для цього в клас Context треба додати інтерфейс, що дозволяє з об'єктів Stateвстановити його стан.
    Таку децентралізовану логіку переходів простіше модифікувати і розширювати - потрібно лише визначити нові підкласи State. Недолік децентралізації в тому, що кожен підклас State повинен «знати» ще хоча б про один підкласі іншого стану (на яке власне він і зможе переключити поточний стан), що вносить реалізаційні залежності між підкласами. істочнік.ru

    сайт джерело сайт оригінал
  2. Таблична альтернатива.
    Існує ще один спосіб структурування коду, керованого зміною станів. Це принцип кінцевого автомата. Він використовує таблицю для відображення вхідних даних на переходи між станами. З її допомогою можна визначити, до якого стану треба перейти під час вступу деяких вхідних даних. По суті, тим самим ми замінюємо умовний код пошуком в таблиці.
    Основна перевага автомата - в його регулярності: для зміни критеріїв переходу досить модифікувати тільки дані, а не код. Але є і недоліки:
    - пошук в таблиці часто менш ефективний, ніж виклик функції,
    - уявлення логіки переходів в однорідному табличному форматі робить критерії менш явними і, отже, більш складними для розуміння,
    - зазвичай важко додати дії, якими супроводжуються переходи між станами. Табличний метод враховує стану і переходи між ними, але його необхідно доповнити, щоб при кожній зміні стані можна було виконувати довільні обчислення.
    Головна відмінність між кінцевими автоматами на базі таблиць і патерн стан можна сформулювати так: патерн стан моделює поведінку, залежне від стану, а табличний метод акцентує увагу на визначенні переходів між станами. орігінал.ru джерело

    істочнік.ru оригінал
  3. Створення і знищення об'єктів стану.
    В процесі розробки зазвичай доводиться вибирати між:
    - створенням об'єктів стану, коли в них виникає необхідність, і знищенням відразу після використання,
    - створенням їх заздалегідь і назавжди.

    Перший варіант кращий, коли заздалегідь невідомо, в які стану потраплятиме система, і контекст змінює стан порівняно рідко. При цьому ми не створюємо об'єктів, які ніколи не будуть використані, що істотно, якщо в об'єктах стану зберігається багато інформації. Коли зміни стану відбуваються часто, тому не хотілося б знищувати представляють їх об'єкти (бо вони можуть дуже скоро знадобитися знову), слід скористатися другим підходом. Час на створення об'єктів витрачається тільки один раз, на самому початку, а на знищення - не витрачає зовсім. Правда, цей підхід може виявитися незручним, тому що в контексті повинні зберігатися посилання на їхні капітали, в які система теоретично може потрапити. істочнік.ru оригінал

    сайт джерело сайт оригінал
  4. Використання динамічного зміни.
    Варіювати поведінку за запитом можна, змінюючи клас об'єкта під час виконання, але в більшості об'єктно-орієнтованих мов це не підтримується. Виняток становить Perl, JavaScript та інші засновані на скриптовій движку мови, які надають такий механізм і, отже, підтримують патерн стан безпосередньо. Це дозволяє об'єктам варіювати поведінку шляхом зміни коду свого класу. істочнік.ru оригінал

    оригінал істочнік.ru

результати

результати використання патерну стан: орігінал.ru джерело
  1. Локалізує залежне від стану поведінку.
    І ділить його на частини, що відповідають станам. Патерн стан поміщає все поведінку, асоційоване з конкретним станом, в окремий об'єкт. Оскільки залежить від стану код цілком знаходиться в одному з підкласів класу State, То додавати нові стану і переходи можна просто шляхом породження нових підкласів.
    Замість цього можна було б використовувати дані-члени для визначення внутрішніх станів, тоді операції об'єкта Context перевіряли б ці дані. Але в такому випадку схожі умовні оператори або оператори розгалуження були б розкидані по всьому коду класу Context. При цьому додавання нового стану зажадало б зміни декількох операцій, що ускладнило б супровід. Патерн стан дозволяє вирішити цю проблему, але одночасно породжує іншу, оскільки поведінка для різних станів виявляється розподіленим між декількома підкласами State. Це збільшує число класів. Звичайно, один клас компактніше, але якщо станів багато, то такий розподіл ефективніше, тому що в противному випадку довелося б мати справу з громіздкими умовними операторами.
    Наявність громіздких умовних операторів небажано, так само як і наявність довгих процедур. Вони занадто монолітні, ось чому модифікація та розширення коду стає проблемою. Патерн стан пропонує більш вдалий спосіб структурування залежить від стану коду. Логіка, яка описувала переходи між станами, більше не укладена в монолітні оператори if або switch, А розподілена між підкласами State. При інкапсуляції кожного переходу і дії в клас - стан стає повноцінним об'єктом. Це покращує структуру коду і прояснює його призначення. джерело орігінал.ru
  2. Робить явними переходи між станами.
    Якщо об'єкт визначає свій поточний стан виключно в термінах внутрішніх даних, то переходи між станами не мають явного подання; вони проявляються лише як привласнення деяким змінним. Введення окремих об'єктів для різних станів робить переходи більш явними. Крім того, об'єкти State можуть захистити контекст Context від неузгодженості внутрішніх змінних, оскільки переходи з точки зору контексту - це атомарні дії. Для здійснення переходу треба змінити значення тільки однієї змінної (об'єктної змінної State у класі Context), А не кількох. орігінал.ru джерело
  3. Об'єкти стану можна розділяти.
    Якщо в об'єкті стану State відсутні змінні екземпляра, тобто він представляє стан кодується виключно самим типом, то різні контексти можуть розділяти один і той же об'єкт State. Коли стану поділяються таким чином, вони є, по суті справи, пристосуванцями (див. Патерн-пристосуванець), у яких немає внутрішнього стану, а є тільки поведінку. сайт джерело оригінал сайт

приклад

Розглянемо реалізацію приклади з розділу «», тобто побудова деякої простенької архітектури TCP з'єднання. Це спрощений варіант протоколу TCP, в ньому, звичайно ж, представлений не весь протокол і навіть не всі стани TCP-з'єднань. сайт оригінал сайт джерело

Перш за все визначимо клас TCPConnection, Який надає інтерфейс для передачі даних і обробляє запити на зміну стану: TCPConnection. істочнік.ru оригінал

В змінної-члені state класу TCPConnection зберігається екземпляр класу TCPState. Цей клас дублює інтерфейс зміни стану, визначений у класі TCPConnection. сайт джерело оригінал сайт

джерело орігінал.ru

TCPConnection делегує всіх залежних від стану запити що зберігається в state примірнику TCPState. Крім того, в класі TCPConnection існує операція ChangeState, За допомогою якої в цю змінну можна записати покажчик на інший об'єкт TCPState. конструктор класу TCPConnection инициализирует state покажчиком на стан-закриття TCPClosed (Ми визначимо його нижче). істочнік.ru

сайт джерело оригінал сайт

кожна операція TCPState приймає екземпляр TCPConnection як параметр, тим самим, дозволяючи об'єкту TCPState отримати доступ до даних об'єкта TCPConnection і змінити стан з'єднання. .ru

У класі TCPState реалізовано поведінка за умовчанням для всіх делегованих йому запитів. Він може також змінити стан об'єкта TCPConnection за допомогою операції ChangeState. TCPState розташовується в тому ж пакеті, що і TCPConnection, Тому також має доступ до цієї операції: TCPState. сайт сайт оригінал джерело

істочнік.ru

У підкласах TCPState реалізовано поведінку, залежне від стану. З'єднання TCP може перебувати в багатьох станах: Established (Встановлено), Listening (Прослуховування), Closed (Закрите) і т.д., і для кожного з них є свій підклас TCPState. Для простоти детально розглянемо лише 3 підкласу - TCPEstablished, TCPListen і TCPClosed. сайт сайт джерело оригінал

оригінал істочнік.ru

У підкласах TCPState реалізується залежне від стану поведінку для тих запитів, які допустимі в цьому стані. орігінал.ru джерело

сайт оригінал сайт джерело

Після виконання специфічних для свого стану дій ці операції сайт оригінал джерело сайт

викликають ChangeState для зміни стану об'єкта TCPConnection. У нього ж самого немає ніякої інформації про протокол TCP. Саме підкласи TCPState визначають переходи між станами та дії, які диктуються протоколом. сайт сайт оригінал джерело

Ru оригінал

Відомі застосування патерну Стан (State)

Ральф Джонсон і Джонатан Цвейг характеризують патерн стан і описують його стосовно протоколу TCP.
Найбільш популярні інтерактивні програми малювання надають «інструменти» для виконання операцій прямим маніпулюванням. Наприклад, інструмент для малювання ліній дозволяє користувачеві клацнути в довільній точці мишею, а потім, переміщаючи миша, провести з цієї точки лінію. Інструмент для вибору дозволяє вибирати деякі фігури. Зазвичай всі наявні інструменти розміщуються в палітрі. Робота користувача полягає в тому, щоб вибрати і застосувати інструмент, але насправді поведінка редактора варіюється при зміні інструменту: за допомогою інструменту для малювання ми створюємо фігури, за допомогою інструменту вибору - вибираємо їх і т.д. орігінал.ru джерело

Щоб відобразити залежність поведінки редактора від поточного інструменту, можна скористатися патерном стан. сайт сайт оригінал джерело

Можна визначити абстрактний клас Tool, Підкласи якого реалізують залежне від інструменту поведінку. Графічний редактор зберігає посилання на поточний об'єкт Tool і делегує йому надходять запити. При виборі інструмента редактор використовує інший об'єкт, що призводить до зміни поведінки. істочнік.ru

Дана техніка використовується в каркасах графічних редакторів HotDraw і Unidraw. Вона дозволяє клієнтам легко визначати нові види інструментів. В HotDraw клас DrawingController переадресує запити поточного об'єкту Tool. В Unidraw відповідні класи називаються Viewer і Tool. На наведеній нижче діаграмі класів схематично представлені інтерфейси класів Tool

сайт джерело сайт оригінал

Призначення паттерна State

  • Патерн State дозволяє об'єкту змінювати свою поведінку в залежності від внутрішнього стану. Створюється враження, що об'єкт змінив свій клас.
  • Патерн State є об'єктно-орієнтованої реалізацією кінцевого автомата.

розв'язувана проблема

Поведінка об'єкта залежить від його стану і має змінюватися під час виконання програми. Таку схему можна реалізувати, застосувавши безліч умовних операторів: на основі аналізу поточного стану об'єкта робляться певні дії. Однак при великій кількості станів умовні оператори будуть розкидані по всьому коду, і таку програму буде важко підтримувати.

Обговорення паттерна State

Патерн State вирішує зазначену проблему наступним чином:

  • Вводить клас Context, в якому визначається інтерфейс для зовнішнього світу.
  • Вводить абстрактний клас State.
  • Представляє різні "стану" кінцевого автомата у вигляді підкласів State.
  • У класі Context є покажчик на поточний стан, який змінюється при зміні стану кінцевого автомата.

Патерн State не визначає, де саме визначається умова переходу в новий стан. Існує два варіанти: клас Context або підкласи State. Перевага останнього варіанта полягає в простоті додавання нових похідних класів. Недолік полягає в тому, що кожен підклас State для здійснення переходу в новий стан повинен знати про своїх сусідів, що вводить залежності між підкласами.

Існує також альтернативний таблично-орієнтований підхід до проектування кінцевих автоматів, заснований на використанні таблиці однозначного відображення вхідних даних на переходи між станами. Однак цей підхід має недоліки: важко додати виконання дій при виконанні переходів. Підхід, заснований на використанні паттерна State, для здійснення переходів між станами використовує код (замість структур даних), тому ці дії легко добавляеми.

Структура паттерна State

Клас Context визначає зовнішній інтерфейс для клієнтів і зберігає в собі посилання на поточний стан об'єкта State. Інтерфейс абстрактного базового класу State повторює інтерфейс Context за винятком одного додаткового параметра - покажчика на екземпляр Context. Похідні від State класи визначають поведінку, специфічне для конкретного стану. Клас "обгортка" Context делегує всі отримані запити об'єкту "поточний стан", який може використовувати отриманий додатковий параметр для доступу до примірника Context.

Патерн State дозволяє об'єкту змінювати свою поведінку в залежності від внутрішнього стану. Схожа картина може спостерігатися в роботі торгового автомата. Автомати можуть мати різні стани в залежності від наявності товарів, суми отриманих монет, можливості розміну грошей і т.д. Після того як покупець вибрав і оплатив товар, можливі наступні ситуації (стану):

  • Видати покупцеві товар, видавати здачу не потрібно.
  • Видати покупцеві товар і здачу.
  • Покупець товар не отримає через відсутність достатньої суми грошей.
  • Покупець товар не отримає через його відсутність.

Використання паттерна State

  • Визначте існуючий або створіть новий клас-"обгортку" Context, який буде використовуватися клієнтом як "кінцевого автомата".
  • Створіть базовий клас State, який повторює інтерфейс класу Context. Кожен метод приймає один додатковий параметр: екземпляр класу Context. Клас State може визначати будь-корисну поведінку "за замовчуванням".
  • Створіть похідні від State класи для всіх можливих станів.
  • Клас-"обгортка" Context має посилання на об'єкт "поточний стан".
  • Всі отримані від клієнта запити клас Context просто делегує об'єкту "поточний стан", при цьому в якості додаткового параметра передається адреса об'єкта Context.
  • Використовуючи цю адресу, якщо буде потреба методи класу State можуть змінити "поточний стан" класу Context.

Особливості паттерна State

  • Об'єкти класу State часто бувають одинаками.
  • Flyweight показує, як і коли можна розділяти об'єкти State.
  • Патерн Interpreter може використовувати State для визначення контекстів при синтаксичному розборі.
  • Патерни State і Bridge мають схожі структури за винятком того, що Bridge допускає ієрархію класів-конвертів (аналогів класів- "обгорток"), а State-ні. Ці патерни мають схожі структури, але вирішують різні завдання: State дозволяє об'єкту змінювати свою поведінку в залежності від внутрішнього стану, в той час як Bridge розділяє абстракцію від її реалізації так, що їх можна змінювати незалежно один від одного.
  • Реалізація патерну State заснована на паттерне Strategy. Відмінності полягають в їх призначенні.

Реалізація патерну State

Розглянемо приклад кінцевого автомата з двома можливими станами і двома подіями.

#include using namespace std; class Machine (class State * current; public: Machine (); void setCurrent (State * s) (current \u003d s;) void on (); void off ();); class State (public: virtual void on (Machine * m) (cout<< " already ON\n"; } virtual void off(Machine *m) { cout << " already OFF\n"; } }; void Machine::on() { current->on (this); ) Void Machine :: off () (current-\u003e off (this);) class ON: public State (public: ON () (cout<< " ON-ctor "; }; ~ON() { cout << " dtor-ON\n"; }; void off(Machine *m); }; class OFF: public State { public: OFF() { cout << " OFF-ctor "; }; ~OFF() { cout << " dtor-OFF\n"; }; void on(Machine *m) { cout << " going from OFF to ON"; m->setCurrent (new ON ()); delete this; )); void ON :: off (Machine * m) (cout<< " going from ON to OFF"; m->setCurrent (new OFF ()); delete this; ) Machine :: Machine () (current \u003d new OFF (); cout<< "\n"; } int main() { void(Machine:: *ptrs)() = { Machine::off, Machine::on }; Machine fsm; int num; while (1) { cout << "Enter 0/1: "; cin >\u003e Num; (Fsm. * Ptrs) (); ))

15.02.2016
21:30

Патерн Стан (State) призначений для проектування класів, які мають кілька незалежних логічних станів. Давайте відразу перейдемо до розгляду прикладу.

Припустимо, ми розробляємо клас управління веб-камерою. Камера може перебувати в трьох Станах:

  1. Чи не инициализирована. Назвемо NotConnectedState;
  2. Инициализирована і готова до роботи, але кадри ще не захоплюються. Нехай це буде ReadyState;
  3. Активний режим захоплення кадрів. Позначимо ActiveState.

Оскільки ми працюємо з паттером Стан, то краще за все почати з зображення Діаграми станів:

Тепер перетворимо цю діаграму в код. Щоб не ускладнювати реалізацію, код роботи з веб-камерами ми опускаємо. При необхідності ви самі можете додати відповідні виклики бібліотечних функцій.

Відразу наводжу повний лістинг з мінімальними коментарями. Далі ми обговоримо ключові деталі цієї реалізації докладніше.

#include #define DECLARE_GET_INSTANCE (ClassName) \\ static ClassName * getInstance () (\\ static ClassName instance; \\ return \\) class WebCamera (public: typedef std :: string Frame; public: // *********** *************************************** // Exceptions // ****** ******************************************** class NotSupported: public std: : exception (); public: // ***************************************** ********* // States // ************************************ ************** class NotConnectedState; class ReadyState; class ActiveState; class State (public: virtual ~ State () () virtual void connect (WebCamera *) (throw NotSupported ();) virtual void disconnect (WebCamera * cam) (std :: cout<< "Деинициализируем камеру..." << std::endl; // ... cam->changeState (NotConnectedState :: getInstance ()); ) Virtual void start (WebCamera *) (throw NotSupported ();) virtual void stop (WebCamera *) (throw NotSupported ();) virtual Frame getFrame (WebCamera *) (throw NotSupported ();) protected: State () () ); // ************************************************ ** class NotConnectedState: public State (public: DECLARE_GET_INSTANCE (NotConnectedState) void connect (WebCamera * cam) (std :: cout<< "Инициализируем камеру..." << std::endl; // ... cam->changeState (ReadyState :: getInstance ()); ) Void disconnect (WebCamera *) (throw NotSupported ();) private: NotConnectedState () ()); // ************************************************ ** class ReadyState: public State (public: DECLARE_GET_INSTANCE (ReadyState) void start (WebCamera * cam) (std :: cout<< "Запускаем видео-поток..." << std::endl; // ... cam->changeState (ActiveState :: getInstance ()); ) Private: ReadyState () ()); // ************************************************ ** class ActiveState: public State (public: DECLARE_GET_INSTANCE (ActiveState) void stop (WebCamera * cam) (std :: cout<< "Останавливаем видео-поток..." << std::endl; // ... cam-> << "Получаем текущий кадр..." << std::endl; // ... return "Current frame"; } private: ActiveState() { } }; public: explicit WebCamera(int camID) : m_camID(camID), m_state(NotConnectedState::getInstance()) { } ~WebCamera() { try { disconnect(); } catch(const NotSupported& e) { // Обрабатываем исключение } catch(...) { // Обрабатываем исключение } } void connect() { m_state->connect (this); ) Void disconnect () (m_state-\u003e disconnect (this);) void start () (m_state-\u003e start (this);) void stop () (m_state-\u003e stop (this);) Frame getFrame () (return m_state -\u003e getFrame (this);) private: void changeState (State * newState) (m_state \u003d newState;) private: int m_camID; State * m_state; );

Звертаю увагу на макрос DECLARE_GET_INSTANCE. Звичайно, використання макросів в C ++ не заохочується. Однак це стосується випадків, коли макрос виступає в ролі аналога шаблонної функції. В цьому випадку завжди віддавайте перевагу останнім.

У нашому випадку макрос призначений для визначення статичної функції, необхідної для реалізації. Тому його використання можна вважати виправданим. Адже воно дозволяє скоротити дублювання коду і не представляє будь-яких серйозних загроз.

Класи-Стану ми оголошуємо в головному класі - WebCamera. Для стислості я використовував inline -Визначення функцій-членів всіх класів. Однак в реальних додатках краще слідувати рекомендаціям про поділ оголошення і реалізації по h і cpp файлів.

Класи Станів оголошені всередині WebCamera для того, щоб вони мали доступ до закритих полів цього класу. Звичайно, це створює вкрай жорстку зв'язок між усіма цими класами. Але Стану виявляються настільки специфічними, що про їх повторному використанні в інших контекстах не може бути й мови.

Основу ієрархії класів станів утворює абстрактний клас WebCamera :: State:

Class State (public: virtual ~ State () () virtual void connect (WebCamera *) (throw NotSupported ();) virtual void disconnect (WebCamera * cam) (std :: cout<< "Деинициализируем камеру..." << std::endl; // ... cam->changeState (NotConnectedState :: getInstance ()); ) Virtual void start (WebCamera *) (throw NotSupported ();) virtual void stop (WebCamera *) (throw NotSupported ();) virtual Frame getFrame (WebCamera *) (throw NotSupported ();) protected: State () () );

Всі його функції-члени відповідають функціям самого класу WebCamera. Відбувається безпосереднє делегування:

Class WebCamera (// ... void connect () (m_state-\u003e connect (this);) void disconnect () (m_state-\u003e disconnect (this);) void start () (m_state-\u003e start (this);) void stop () (m_state-\u003e stop (this);) Frame getFrame () (return m_state-\u003e getFrame (this);) // ... State * m_state;)

Ключовою особливістю є те, що об'єкт Стану приймає покажчик на що викликає його екземпляр WebCamera. Це дозволяє мати всього три об'єкти Станів для як завгодно великого числа камер. Досягається така можливість за рахунок використання патерну Сінглтон. Звичайно, в рамках прикладу істотного виграшу ви від цього не отримаєте. Але знати такий прийом все одно корисно.

Сам по собі клас WebCamera робить практично нічого. Він повністю залежить від своїх Станів. А ці Стани, в свою чергу, визначають умови виконання операцій і забезпечують потрібний контекст.

Більшість функцій-членів WebCamera :: State викидають наше власне WebCamera :: NotSupported. Це цілком доречне поведінка за умовчанням. Наприклад, якщо хтось спробує ініціювати камеру, коли вона вже инициализирована, то цілком закономірно отримає виняток.

При цьому для WebCamera :: State :: disconnect () ми передбачаємо реалізацію за замовчуванням. Така поведінка підійде для двох станів з трьох. В результаті ми запобігаємо дублювання коду.

Для зміни стану призначена закрита функція-член WebCamera :: changeState ():

Void changeState (State * newState) (m_state \u003d newState;)

Тепер до реалізації конкретних Станів. Для WebCamera :: NotConnectedState досить перевизначити операції connect () і disconnect ():

Class NotConnectedState: public State (public: DECLARE_GET_INSTANCE (NotConnectedState) void connect (WebCamera * cam) (std :: cout<< "Инициализируем камеру..." << std::endl; // ... cam->changeState (ReadyState :: getInstance ()); ) Void disconnect (WebCamera *) (throw NotSupported ();) private: NotConnectedState () ());

Для кожного Стани можна створити єдиний екземпляр. Це нам гарантує оголошення закритого конструктора.

Іншим важливим елементом представленої реалізації є те, що в нове Стан ми переходимо лише в разі успіху. Наприклад, якщо під час ініціалізації камери відбудеться збій, то в Стан ReadyState переходити рано. Головна думка - повна відповідність фактичного стану камери (в нашому випадку) і об'єкта-Стану.

Отже, камера готова до роботи. Заведемо відповідний клас Стану WebCamera :: ReadyState:

Class ReadyState: public State (public: DECLARE_GET_INSTANCE (ReadyState) void start (WebCamera * cam) (std :: cout<< "Запускаем видео-поток..." << std::endl; // ... cam->changeState (ActiveState :: getInstance ()); ) Private: ReadyState () ());

З Стану готовності ми можемо потрапити в активну Стан захоплення кадрів. Для цього передбачена операція start (), яку ми і реалізували.

Нарешті ми дійшли до останнього логічного Стану роботи камери WebCamera :: ActiveState:

Class ActiveState: public State (public: DECLARE_GET_INSTANCE (ActiveState) void stop (WebCamera * cam) (std :: cout<< "Останавливаем видео-поток..." << std::endl; // ... cam->changeState (ReadyState :: getInstance ()); ) Frame getFrame (WebCamera *) (std :: cout<< "Получаем текущий кадр..." << std::endl; // ... return "Current frame"; } private: ActiveState() { } };

У цьому Стані можна перервати захоплення кадрів з допомогою stop (). В результаті ми потрапимо назад в Стан WebCamera :: ReadyState. Крім того, ми можемо отримувати кадри, які накопичуються в буфері камери. Для простоти під "кадром" ми розуміємо звичайну рядок. У реальності це буде деякий байтовий масив.

А тепер ми можемо записати типовий приклад роботи з нашим класом WebCamera:

Int main () (WebCamera cam (0); try (// cam в Стані NotConnectedState cam.connect (); // cam в Стані ReadyState cam.start (); // cam в Стані ActiveState std :: cout<< cam.getFrame() << std::endl; cam.stop(); // Можно было сразу вызвать disconnect() // cam в Состоянии ReadyState cam.disconnect(); // cam в Состоянии NotConnectedState } catch(const WebCamera::NotSupported& e) { // Обрабатываем исключение } catch(...) { // Обрабатываем исключение } return 0; }

Ось що в результаті буде виведено на консоль:

Ініціалізіруем камеру ... Запускаємо відео-потік ... Отримуємо поточний кадр ... Current frame Зупиняємо відео-потік ... Деініціалізіруем камеру ...

А тепер спробуємо спровокувати помилку. Викличемо connect () два рази поспіль:

Int main () (WebCamera cam (0); try (// cam в Стані NotConnectedState cam.connect (); // cam в Стані ReadyState // Але для цього Стану операція connect () не передбачена! Cam.connect (); // Викидає виняток NotSupported) catch (const WebCamera :: NotSupported & e) (std :: cout<< "Произошло исключение!!!" << std::endl; // ... } catch(...) { // Обрабатываем исключение } return 0; }

Ось що з цього вийде:

Ініціалізіруем камеру ... Сталося виняток !!! Деініціалізіруем камеру ...

Зверніть увагу, що камера все ж була деініціалізірована. Виклик disconnect () стався в деструкції WebCamera. Тобто внутрішнє Стан об'єкта залишилося абсолютно коректним.

висновки

За допомогою паттерна Стан ви можете однозначно перетворити діаграму станів в код. На перший погляд реалізація вийшла багатослівній. Однак ми дійшли чіткого поділу за можливим контекстам роботи з основним класом WebCamera. В результаті при написанні кожного окремого Стану ми змогли сконцентруватися на вузькій задачі. А це кращий спосіб написати ясний, зрозумілий і надійний код.