Привіт! Я знизив ціни на усі продукти. Час разом готувати свої програмерські скіли до пост-COVID-ної ери. Перевірити »
Стан

Стан на Go

Стан — це поведінковий патерн, що дозволяє динамічно змінювати поведінку об’єкта при зміні його стану.

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

Концептуальний приклад

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

  • hasItem (міститьПредмет)
  • noItem (неМіститьПредметів)
  • itemRequested (видаєПредмет)
  • hasMoney (отримавГроші)

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

  • Вибрати предмет
  • Додати предмет
  • Ввести гроші
  • Видати предмет

Патерн Стан потрібно використовувати у випадках, коли об’єкт може мати багато різних станів, які він мусить міняти залежно від конкретного отриманого запиту.

У нашому прикладі, автомат може бути в одному з безлічі станів, які безперервно змінюються. Припустимо, що торговий автомат перебуває в режимі itemRequested. Як тільки відбудеться дія «Ввести гроші», він одразу ж перейде у стан hasMoney.

Залежно від стану торгового автомата, в якому він перебуває на цей момент, він може по-різному відповідати на одні й ті самі запити. Наприклад, якщо користувач хоче купити предмет, машина виконає дію, якщо вона перебуває в режимі hasItemState, і відхилить запит в режимі noItemState.

Програма торгового автомата не захаращена цією логікою; весь режимозалежний код зберігається у реалізаціях відповідних станів.

vendingMachine.go: Контекст

package main

import "fmt"

type vendingMachine struct {
	hasItem       state
	itemRequested state
	hasMoney      state
	noItem        state

	currentState state

	itemCount int
	itemPrice int
}

func newVendingMachine(itemCount, itemPrice int) *vendingMachine {
	v := &vendingMachine{
		itemCount: itemCount,
		itemPrice: itemPrice,
	}
	hasItemState := &hasItemState{
		vendingMachine: v,
	}
	itemRequestedState := &itemRequestedState{
		vendingMachine: v,
	}
	hasMoneyState := &hasMoneyState{
		vendingMachine: v,
	}
	noItemState := &noItemState{
		vendingMachine: v,
	}

	v.setState(hasItemState)
	v.hasItem = hasItemState
	v.itemRequested = itemRequestedState
	v.hasMoney = hasMoneyState
	v.noItem = noItemState
	return v
}

func (v *vendingMachine) requestItem() error {
	return v.currentState.requestItem()
}

func (v *vendingMachine) addItem(count int) error {
	return v.currentState.addItem(count)
}

func (v *vendingMachine) insertMoney(money int) error {
	return v.currentState.insertMoney(money)
}

func (v *vendingMachine) dispenseItem() error {
	return v.currentState.dispenseItem()
}

func (v *vendingMachine) setState(s state) {
	v.currentState = s
}

func (v *vendingMachine) incrementItemCount(count int) {
	fmt.Printf("Adding %d items\n", count)
	v.itemCount = v.itemCount + count
}

state.go: Інтерфейс стану

package main

type state interface {
	addItem(int) error
	requestItem() error
	insertMoney(money int) error
	dispenseItem() error
}

noItemState.go: Конкретний стан

package main

import "fmt"

type noItemState struct {
	vendingMachine *vendingMachine
}

func (i *noItemState) requestItem() error {
	return fmt.Errorf("Item out of stock")
}

func (i *noItemState) addItem(count int) error {
	i.vendingMachine.incrementItemCount(count)
	i.vendingMachine.setState(i.vendingMachine.hasItem)
	return nil
}

func (i *noItemState) insertMoney(money int) error {
	return fmt.Errorf("Item out of stock")
}
func (i *noItemState) dispenseItem() error {
	return fmt.Errorf("Item out of stock")
}

hasItemState.go: Конкретний стан

package main

import "fmt"

type hasItemState struct {
	vendingMachine *vendingMachine
}

func (i *hasItemState) requestItem() error {
	if i.vendingMachine.itemCount == 0 {
		i.vendingMachine.setState(i.vendingMachine.noItem)
		return fmt.Errorf("No item present")
	}
	fmt.Printf("Item requestd\n")
	i.vendingMachine.setState(i.vendingMachine.itemRequested)
	return nil
}

func (i *hasItemState) addItem(count int) error {
	fmt.Printf("%d items added\n", count)
	i.vendingMachine.incrementItemCount(count)
	return nil
}

func (i *hasItemState) insertMoney(money int) error {
	return fmt.Errorf("Please select item first")
}
func (i *hasItemState) dispenseItem() error {
	return fmt.Errorf("Please select item first")
}

itemRequestedState.go: Конкретний стан

package main

import "fmt"

type itemRequestedState struct {
	vendingMachine *vendingMachine
}

func (i *itemRequestedState) requestItem() error {
	return fmt.Errorf("Item already requested")
}

func (i *itemRequestedState) addItem(count int) error {
	return fmt.Errorf("Item Dispense in progress")
}

func (i *itemRequestedState) insertMoney(money int) error {
	if money < i.vendingMachine.itemPrice {
		fmt.Errorf("Inserted money is less. Please insert %d", i.vendingMachine.itemPrice)
	}
	fmt.Println("Money entered is ok")
	i.vendingMachine.setState(i.vendingMachine.hasMoney)
	return nil
}
func (i *itemRequestedState) dispenseItem() error {
	return fmt.Errorf("Please insert money first")
}

hasMoneyState.go: Конкретний стан

package main

import "fmt"

type hasMoneyState struct {
	vendingMachine *vendingMachine
}

func (i *hasMoneyState) requestItem() error {
	return fmt.Errorf("Item dispense in progress")
}

func (i *hasMoneyState) addItem(count int) error {
	return fmt.Errorf("Item dispense in progress")
}

func (i *hasMoneyState) insertMoney(money int) error {
	return fmt.Errorf("Item out of stock")
}
func (i *hasMoneyState) dispenseItem() error {
	fmt.Println("Dispensing Item")
	i.vendingMachine.itemCount = i.vendingMachine.itemCount - 1
	if i.vendingMachine.itemCount == 0 {
		i.vendingMachine.setState(i.vendingMachine.noItem)
	} else {
		i.vendingMachine.setState(i.vendingMachine.hasItem)
	}
	return nil
}

main.go: Клієнтський код

package main

import (
	"fmt"
	"log"
)

func main() {
	vendingMachine := newVendingMachine(1, 10)

	err := vendingMachine.requestItem()
	if err != nil {
		log.Fatalf(err.Error())
	}

	err = vendingMachine.insertMoney(10)
	if err != nil {
		log.Fatalf(err.Error())
	}

	err = vendingMachine.dispenseItem()
	if err != nil {
		log.Fatalf(err.Error())
	}

	fmt.Println()

	err = vendingMachine.addItem(2)
	if err != nil {
		log.Fatalf(err.Error())
	}

	fmt.Println()

	err = vendingMachine.requestItem()
	if err != nil {
		log.Fatalf(err.Error())
	}

	err = vendingMachine.insertMoney(10)
	if err != nil {
		log.Fatalf(err.Error())
	}

	err = vendingMachine.dispenseItem()
	if err != nil {
		log.Fatalf(err.Error())
	}
}

output.txt: Результат виконання

Item requestd
Money entered is ok
Dispensing Item

Adding 2 items

Item requestd
Money entered is ok
Dispensing Item
На основі: Golang By Example

Стан іншими мовами програмування

Стан на Java Стан на C# Стан на C++ Стан на PHP Стан на Python Стан на Ruby Стан на Swift Стан на TypeScript