Стан (англ. state) — шаблон проєктування (належить до шаблонів поведінки), що реалізує скінченний автомат в обʼєктно-орієнтованому програмуванні.
Він реалізується шляхом створення для кожного стану скінченного автомата класу-спадкоємця інтерфейсу (або абстрактного класу) та дозволяє об'єктові варіювати свою поведінку залежно від внутрішнього стану.
Призначення
Дозволяє об'єктові варіювати свою поведінку залежно від внутрішнього стану.
Застосовність
Слід використовувати шаблон Стан у випадках, коли:
- поведінка об'єкта залежить від його стану та повинна змінюватись під час виконання програми;
- у коді операцій зустрічаються складні умовні оператори, у котрих вибір гілки залежить від стану. Зазвичай у такому разі стан представлено константами, що перелічуються. До того ж часто одна й та ж структура умовного оператору повторюється у декількох операціях. Шаблон Стан пропонує замінити кожну гілку окремим класом. Це дозволить трактувати стан об'єкта як самостійний об'єкт, котрий може змінитися незалежно від інших.
Структура
- Context — контекст:
- визначає інтерфейс, що є корисним для клієнтів;
- зберігає екземпляр підкласу ConcreteState, котрим визначається поточний стан;
- State — стан:
- визначає інтерфейс для інкапсуляції поведінки, асоційованої з конкретним станом контексту Context;
- Підкласи ConcreteState — конкретні стани:
- кожний підклас реалізує поведінку, асоційовану з деяким станом контексту Context.
Відносини
- клас Context делегує залежні від стану запити до поточного об'єкта ConcreteState;
- контекст може передати себе як аргумент об'єкта State, котрий буде обробляти запит. Це надає можливість об'єкта-стану при необхідності отримати доступ до контексту;
- Context — це головний інтерфейс для клієнтів. Клієнти можуть конфігурувати контекст об'єктами стану State. Один раз сконфігурувавши контекст, клієнти вже не повинні напряму зв'язуватися з об'єктами стану;
- або Context, або підкласи ConcreteState можуть вирішити, за яких умов та у якій послідовності відбувається зміна станів.
Переваги та недоліки
Переваги
- переваги застосування поліморфної поведінки очевидні, а також легше додавати стан для підтримки додаткової поведінки.
- поведінка об'єкта є результатом функції свого стану, і поведінка змінюється під час виконання в залежності від стану.
- покращує згуртованість, оскільки специфічні для стану особливості поведінки об'єднуються в класи ConcreteState, які розміщуються в одному місці в коді.
Недоліки
- Зростає кількість класів
Зв'язок з іншими патернами
- В стратегії користувач знає про класи стратегій і міняє їх самостійно, в стані різноманітні стани приховані від користувача, а за їх заміну відповідає сам клас
Реалізація
C++
#include <iostream> #include <string> using namespace std; class Creature { private: struct State { virtual string response() = 0; }; struct Frog : public State { string response() { return " Ribbet!"; } }; struct Prince : public State { string response() { return " Darling!"; } }; State* state; public: Creature() : state(new Frog()) {} void greet() { cout << state->response() << endl; } void kiss() { delete state; state = new Prince; } }; void main() { Creature creature; creature.greet(); creature.kiss(); creature.greet(); }
C#
namespace StatePattern { class Mario { abstract class HeroStateBase { protected readonly Mario _mario; public HeroStateBase(Mario mario) { _mario = mario; } public abstract void HandleInput(string input); public abstract string Show(); } #region WalkingState abstract class WalkingState : HeroStateBase { protected WalkingState(Mario mario) : base(mario) { } } class StandingState : WalkingState { public StandingState(Mario mario) : base(mario) { } public override void HandleInput(string input) { if (input == "up") { _mario.ChangeState(new JumpingState(_mario)); } if (input == "down") { _mario.ChangeState(new DuckingState(_mario)); } } public override string Show() { return "Standing"; } } class JumpingState : WalkingState { private int _time = 3; public JumpingState(Mario mario) : base(mario) { } public override void HandleInput(string input) { _time--; if (_time == 0) { _mario.ChangeState(new StandingState(_mario)); } } public override string Show() { return "Jumping"; } } class DuckingState : WalkingState { public DuckingState(Mario mario) : base(mario) { } public override void HandleInput(string input) { if (input != "down") { _mario.ChangeState(new StandingState(_mario)); } } public override string Show() { return "Ducking"; } } #endregion #region AttackingState abstract class AttackingState : HeroStateBase { protected AttackingState(Mario mario) : base(mario) { } } class DisarmedState : AttackingState { public DisarmedState(Mario mario) : base(mario) { } public override void HandleInput(string input) { if (input == "mushroom") { _mario.ChangeState(new FireState(_mario)); } } public override string Show() { return "Disarmed"; } } class FireState : AttackingState { private int _time = 5; public FireState(Mario mario) : base(mario) { } public override void HandleInput(string input) { _time--; if (input == "attack") { System.Console.WriteLine("Throw fire ball"); } if (_time == 0) { _mario.ChangeState(new DisarmedState(_mario)); } } public override string Show() { return "Fire"; } } #endregion private WalkingState _walkingState; private AttackingState _equipment; public Mario() { _walkingState = new StandingState(this); _equipment = new DisarmedState(this); } public void HandleInput(string input) { // для того щоб не "засмічувати" логіку // багатьма умовними операторами та змінними // винисемо її в окремі стани _walkingState.HandleInput(input); _equipment.HandleInput(input); } public string Show() { return _walkingState.Show() + " " + _equipment.Show(); } private void ChangeState(WalkingState newState) { _walkingState = newState; } private void ChangeState(AttackingState newState) { _equipment = newState; } } class Program { static void Main(string[] args) { var mario = new Mario(); while (true) { var input = System.Console.ReadLine(); mario.HandleInput(input); var state = mario.Show(); System.Console.WriteLine(state); } } } }
Java
class Car { private CarState carState; public Car() { off(); } public void on() { carState = new CarOn(); System.out.println("The car is on!"); } public void off() { carState = new CarOff(); System.out.println("The car is off!"); } public void start() { carState = new CarMoving(); System.out.println("The car is moving!"); } public void openWindow() { carState.openWindow(); } public void openDoor() { carState.openDoor(); } } abstract class CarState { abstract public void openWindow(); abstract public void openDoor(); } class CarOff extends CarState { public void openWindow() { System.out.println("Can't open the window! Switch the car on!"); } public void openDoor() { System.out.println("The door is being opened ..."); } } class CarOn extends CarState { public void openWindow() { System.out.println("The window is being opened ..."); } public void openDoor() { System.out.println("The door is being opened ..."); } } class CarMoving extends CarState { public void openWindow() { System.out.println("The window is being opened ..."); } public void openDoor() { System.out.println("Can't open the door while moving!"); } } class Main { public static void main(String[] args) { Car car = new Car(); car.openWindow(); car.openDoor(); car.on(); car.openWindow(); car.openDoor(); car.start(); car.openWindow(); car.openDoor(); } }
Python
class BaseState(object): def open_window(self): raise NotImplementedError() def open_door(self): raise NotImplementedError() class CarOff(BaseState): def open_door(self): print("Can't open the window. Switch the car on") def open_window(self): print("The door is being opened...") class CarOn(BaseState): def open_window(self): print("The window is being opened...") def open_door(self): print("The door is being opened...") class CarMoving(BaseState): def open_window(self): print("The window is being opened") def open_door(self): print("Can't open the door while moving...") class Car(object): _state = None def __init__(self): self._state = CarOff() def off(self): self._state = CarOff() print("Car is off") def on(self): self._state = CarOn() print("Car is on") def start(self): self._state = CarMoving() print("Car is moving...") def open_window(self): self._state.open_window() def open_door(self): self._state.open_door() if __name__ == '__main__': car = Car() car.open_window() car.open_door() car.on() car.open_window() car.open_door() car.start() car.open_door() car.open_window()
TypeScript
class StateMachine<TState, TCommand> { private currentState: TState; private states: Map<TState, State<TState, TCommand>>; constructor(currentState: TState) { this.currentState = currentState; this.states = new Map<TState, State<TState, TCommand>>(); } public addState(state: TState): State<TState, TCommand> { const newState = new State<TState, TCommand>(state); this.states.set(state, newState); return this.states.get(state) as State<TState, TCommand>; } public handle(command: TCommand): void { const state = this.states.get(this.currentState) as State<TState, TCommand>; const newState = state.handle(command); this.currentState = newState; } public getCurrentState(): TState { return this.currentState; } } class State<TState, TCommand> { private state: TState; private transitionMap: Map<TCommand, TState>; constructor(state: TState) { this.state = state; this.transitionMap = new Map<TCommand, TState>(); } public configureTransition(command: TCommand, newState: TState): State<TState, TCommand> { this.transitionMap.set(command, newState); return this; } public handle(command: TCommand): TState { return this.transitionMap.get(command) as TState; } } enum TvState { On = "TvOn", Off = "TvOff", } enum TvCommand { TurnOn = "TurnOn Command", TurnOff= "TurnOff Command", } // використання const tvState = new StateMachine<TvState, TvCommand>(TvState.Off); tvState .addState(TvState.Off) .configureTransition(TvCommand.TurnOn, TvState.On); tvState .addState(TvState.On) .configureTransition(TvCommand.TurnOff, TvState.Off); console.log(`Current state = ${tvState.getCurrentState()}`); tvState.handle(TvCommand.TurnOn); console.log(`Current state = ${tvState.getCurrentState()}`); tvState.handle(TvCommand.TurnOff); console.log(`Current state = ${tvState.getCurrentState()}`);
Джерела
- Design Patterns: Elements of Reusable Object-Oriented Software
- Stop Using If Else Statements
- State
Література
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — .
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
U Vikipediyi ye statti pro inshi znachennya cogo termina Stan Stan angl state shablon proyektuvannya nalezhit do shabloniv povedinki sho realizuye skinchennij avtomat v obʼyektno oriyentovanomu programuvanni Vin realizuyetsya shlyahom stvorennya dlya kozhnogo stanu skinchennogo avtomata klasu spadkoyemcya interfejsu abo abstraktnogo klasu ta dozvolyaye ob yektovi variyuvati svoyu povedinku zalezhno vid vnutrishnogo stanu PriznachennyaDozvolyaye ob yektovi variyuvati svoyu povedinku zalezhno vid vnutrishnogo stanu ZastosovnistSlid vikoristovuvati shablon Stan u vipadkah koli povedinka ob yekta zalezhit vid jogo stanu ta povinna zminyuvatis pid chas vikonannya programi u kodi operacij zustrichayutsya skladni umovni operatori u kotrih vibir gilki zalezhit vid stanu Zazvichaj u takomu razi stan predstavleno konstantami sho perelichuyutsya Do togo zh chasto odna j ta zh struktura umovnogo operatoru povtoryuyetsya u dekilkoh operaciyah Shablon Stan proponuye zaminiti kozhnu gilku okremim klasom Ce dozvolit traktuvati stan ob yekta yak samostijnij ob yekt kotrij mozhe zminitisya nezalezhno vid inshih StrukturaUML diagrama sho opisuye strukturu shablonu proyektuvannya Stan Context kontekst viznachaye interfejs sho ye korisnim dlya kliyentiv zberigaye ekzemplyar pidklasu ConcreteState kotrim viznachayetsya potochnij stan State stan viznachaye interfejs dlya inkapsulyaciyi povedinki asocijovanoyi z konkretnim stanom kontekstu Context Pidklasi ConcreteState konkretni stani kozhnij pidklas realizuye povedinku asocijovanu z deyakim stanom kontekstu Context Vidnosiniklas Context deleguye zalezhni vid stanu zapiti do potochnogo ob yekta ConcreteState kontekst mozhe peredati sebe yak argument ob yekta State kotrij bude obroblyati zapit Ce nadaye mozhlivist ob yekta stanu pri neobhidnosti otrimati dostup do kontekstu Context ce golovnij interfejs dlya kliyentiv Kliyenti mozhut konfiguruvati kontekst ob yektami stanu State Odin raz skonfiguruvavshi kontekst kliyenti vzhe ne povinni napryamu zv yazuvatisya z ob yektami stanu abo Context abo pidklasi ConcreteState mozhut virishiti za yakih umov ta u yakij poslidovnosti vidbuvayetsya zmina staniv Perevagi ta nedolikiPerevagi perevagi zastosuvannya polimorfnoyi povedinki ochevidni a takozh legshe dodavati stan dlya pidtrimki dodatkovoyi povedinki povedinka ob yekta ye rezultatom funkciyi svogo stanu i povedinka zminyuyetsya pid chas vikonannya v zalezhnosti vid stanu pokrashuye zgurtovanist oskilki specifichni dlya stanu osoblivosti povedinki ob yednuyutsya v klasi ConcreteState yaki rozmishuyutsya v odnomu misci v kodi Nedoliki Zrostaye kilkist klasivZv yazok z inshimi paternamiV strategiyi koristuvach znaye pro klasi strategij i minyaye yih samostijno v stani riznomanitni stani prihovani vid koristuvacha a za yih zaminu vidpovidaye sam klasRealizaciyaC Priklad realizaciyi movoyu S include lt iostream gt include lt string gt using namespace std class Creature private struct State virtual string response 0 struct Frog public State string response return Ribbet struct Prince public State string response return Darling State state public Creature state new Frog void greet cout lt lt state gt response lt lt endl void kiss delete state state new Prince void main Creature creature creature greet creature kiss creature greet C Priklad realizaciyi movoyu C namespace StatePattern class Mario abstract class HeroStateBase protected readonly Mario mario public HeroStateBase Mario mario mario mario public abstract void HandleInput string input public abstract string Show region WalkingState abstract class WalkingState HeroStateBase protected WalkingState Mario mario base mario class StandingState WalkingState public StandingState Mario mario base mario public override void HandleInput string input if input up mario ChangeState new JumpingState mario if input down mario ChangeState new DuckingState mario public override string Show return Standing class JumpingState WalkingState private int time 3 public JumpingState Mario mario base mario public override void HandleInput string input time if time 0 mario ChangeState new StandingState mario public override string Show return Jumping class DuckingState WalkingState public DuckingState Mario mario base mario public override void HandleInput string input if input down mario ChangeState new StandingState mario public override string Show return Ducking endregion region AttackingState abstract class AttackingState HeroStateBase protected AttackingState Mario mario base mario class DisarmedState AttackingState public DisarmedState Mario mario base mario public override void HandleInput string input if input mushroom mario ChangeState new FireState mario public override string Show return Disarmed class FireState AttackingState private int time 5 public FireState Mario mario base mario public override void HandleInput string input time if input attack System Console WriteLine Throw fire ball if time 0 mario ChangeState new DisarmedState mario public override string Show return Fire endregion private WalkingState walkingState private AttackingState equipment public Mario walkingState new StandingState this equipment new DisarmedState this public void HandleInput string input dlya togo shob ne zasmichuvati logiku bagatma umovnimi operatorami ta zminnimi vinisemo yiyi v okremi stani walkingState HandleInput input equipment HandleInput input public string Show return walkingState Show equipment Show private void ChangeState WalkingState newState walkingState newState private void ChangeState AttackingState newState equipment newState class Program static void Main string args var mario new Mario while true var input System Console ReadLine mario HandleInput input var state mario Show System Console WriteLine state Java Priklad realizaciyi movoyu Java class Car private CarState carState public Car off public void on carState new CarOn System out println The car is on public void off carState new CarOff System out println The car is off public void start carState new CarMoving System out println The car is moving public void openWindow carState openWindow public void openDoor carState openDoor abstract class CarState abstract public void openWindow abstract public void openDoor class CarOff extends CarState public void openWindow System out println Can t open the window Switch the car on public void openDoor System out println The door is being opened class CarOn extends CarState public void openWindow System out println The window is being opened public void openDoor System out println The door is being opened class CarMoving extends CarState public void openWindow System out println The window is being opened public void openDoor System out println Can t open the door while moving class Main public static void main String args Car car new Car car openWindow car openDoor car on car openWindow car openDoor car start car openWindow car openDoor Python Priklad realizaciyi movoyu Python class BaseState object def open window self raise NotImplementedError def open door self raise NotImplementedError class CarOff BaseState def open door self print Can t open the window Switch the car on def open window self print The door is being opened class CarOn BaseState def open window self print The window is being opened def open door self print The door is being opened class CarMoving BaseState def open window self print The window is being opened def open door self print Can t open the door while moving class Car object state None def init self self state CarOff def off self self state CarOff print Car is off def on self self state CarOn print Car is on def start self self state CarMoving print Car is moving def open window self self state open window def open door self self state open door if name main car Car car open window car open door car on car open window car open door car start car open door car open window TypeScript Priklad realizaciyi movoyu TypeScript class StateMachine lt TState TCommand gt private currentState TState private states Map lt TState State lt TState TCommand gt gt constructor currentState TState this currentState currentState this states new Map lt TState State lt TState TCommand gt gt public addState state TState State lt TState TCommand gt const newState new State lt TState TCommand gt state this states set state newState return this states get state as State lt TState TCommand gt public handle command TCommand void const state this states get this currentState as State lt TState TCommand gt const newState state handle command this currentState newState public getCurrentState TState return this currentState class State lt TState TCommand gt private state TState private transitionMap Map lt TCommand TState gt constructor state TState this state state this transitionMap new Map lt TCommand TState gt public configureTransition command TCommand newState TState State lt TState TCommand gt this transitionMap set command newState return this public handle command TCommand TState return this transitionMap get command as TState enum TvState On TvOn Off TvOff enum TvCommand TurnOn TurnOn Command TurnOff TurnOff Command vikoristannya const tvState new StateMachine lt TvState TvCommand gt TvState Off tvState addState TvState Off configureTransition TvCommand TurnOn TvState On tvState addState TvState On configureTransition TvCommand TurnOff TvState Off console log Current state tvState getCurrentState tvState handle TvCommand TurnOn console log Current state tvState getCurrentState tvState handle TvCommand TurnOff console log Current state tvState getCurrentState DzherelaDesign Patterns Elements of Reusable Object Oriented Software Stop Using If Else Statements StateLiteraturaAlan Shallouej Dzhejms R Trott Shablony proektirovaniya Novyj podhod k obektno orientirovannomu analizu i proektirovaniyu Design Patterns Explained A New Perspective on Object Oriented Design M Vilyams 2002 288 s ISBN 0 201 71594 5