Стан (шаблон проєктування)
Стан (англ. 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 Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.