상태 는 객체의 내부 상태가 변경될 때 해당 객체가 행동을 변경할 수 있도록 하는 행동 디자인 패턴입니다.
패턴은 상태 관련 행동들을 별도의 상태 클래스들로 추출하며 또 원래 객체가 자체적으로 작동하는 대신 위에 언급된 클래스들에 작업을 위임하도록 강제합니다.
개념적인 예시
상태 패턴을 자판기의 맥락에서 적용해 봅시다. 편의상 자판기에는 한 가지 유형의 품목 또는 제품만 있고 자판기는 4가지 다른 상태에 있을 수 있다고 가정해 봅시다.
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 {
return 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