상태 패턴
Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 95번째.
상태 패턴(State pattern)은 객체의 내부 상태가 변경될 때 객체가 행동을 변경할 수 있도록 허용하며, 마치 객체의 클래스가 바뀐 것처럼 보이게 합니다. 템플릿 메서드(Template Method)가 알고리즘 단계를 제어하는 반면, 상태 패턴은 상태별 행동을 별도의 객체로 캡슐화하고 현재 상태에 위임합니다.
Go에서는 상태 인터페이스를 정의하고 서로 다른 동작을 구현하는 구체적인 상태들을 생성합니다:
type State interface {
Handle(d *Document) string
}
type Document struct {
state State
}
func (d *Document) SetState(s State) {
d.state = s
}
func (d *Document) Publish() string {
return d.state.Handle(d)
}각 상태는 어떤 일이 일어날지, 그리고 다음에는 어떤 상태가 올지를 결정합니다:
type DraftState struct{}
func (s DraftState) Handle(d *Document) string {
d.SetState(ModerationState{})
return "Draft submitted for moderation"
}
type ModerationState struct{}
func (s ModerationState) Handle(d *Document) string {
d.SetState(PublishedState{})
return "Moderation approved, now published"
}
type PublishedState struct{}
func (s PublishedState) Handle(d *Document) string {
return "Already published"
}동일한 메서드 호출이 현재 상태에 따라 서로 다른 결과를 생성합니다:
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // 초안이 검토를 위해 제출되었습니다
fmt.Println(doc.Publish()) // 검토가 승인되어 이제 게시되었습니다
fmt.Println(doc.Publish()) // 이미 게시되었습니다상태(State) 패턴은 주문 처리 워크플로우, UI 컴포넌트 또는 연결 핸들러와 같이 동작이 전적으로 현재 상태에 따라 달라지는, 뚜렷한 운영 모드를 가진 객체에 이상적입니다.
챌린지
쉬움상태(State) 패턴을 사용하여 티켓 지원 시스템을 구축해 봅시다! 티켓이 생성되고, 처리 중이며, 해결되는 등 다양한 단계를 거치는 지원 티켓을 만들게 됩니다. 각 상태는 어떤 작업이 가능한지, 그리고 다음에 어떤 일이 일어날지를 결정합니다.
코드는 다음 세 개의 파일로 구성됩니다:
state.go: 상태 인터페이스와 티켓 라이프사이클의 각 단계를 나타내는 구체적인 상태들을 정의합니다.티켓을 처리하고 잠재적으로 다음 상태로 전이시키는
Handle(t *Ticket) string메서드를 가진TicketState인터페이스를 생성하세요.세 가지 상태를 구현하세요:
OpenState— 처리될 때, 티켓을InProgressState로 전이시키고Ticket opened, assigning to support team을 반환합니다.InProgressState— 처리될 때,ResolvedState로 전이시키고Working on ticket, issue resolved를 반환합니다.ResolvedState— 처리될 때, 동일한 상태를 유지하고Ticket already resolved를 반환합니다.
ticket.go: 현재 상태를 보유하고 동작을 해당 상태에 위임하는 티켓 구조체를 생성합니다.ID필드(string)와state필드(TicketState)를 가진Ticket구조체를 만드세요. 다음 메서드들을 추가하세요:- 티켓의 현재 상태를 변경하는
SetState(s TicketState) - 현재 상태의 Handle 메서드에 위임하는
Process() string
OpenState에서 시작하는 티켓을 반환하는NewTicket(id string) *Ticket생성자를 만드세요.- 티켓의 현재 상태를 변경하는
main.go: 티켓의 현재 상태에 따라 동일한 작업이 어떻게 다른 결과를 생성하는지 보여줍니다.티켓 ID와 티켓을 처리할 횟수를 입력받습니다. 해당 ID로 새 티켓을 생성한 다음, 지정된 횟수만큼
Process()를 호출하여 각 결과를 별도의 줄에 출력하세요.
다음 입력이 제공됩니다:
- 줄 1: 티켓 ID
- 줄 2: 티켓을 처리할 횟수
예를 들어, 다음과 같이 주어지면:
TKT-001
3출력은 다음과 같아야 합니다:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved그리고 다음과 같이 주어지면:
TKT-500
5출력은 다음과 같아야 합니다:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolved그리고 다음과 같이 주어지면:
ISSUE-42
1출력은 다음과 같아야 합니다:
Ticket opened, assigning to support team동일한 티켓에 대해 Process()를 호출할 때마다 매번 다른 결과가 생성되는 방식에 주목하세요. 티켓이 상태를 거치면서 동작이 변합니다. 일단 해결되면(resolved), 몇 번을 처리하더라도 해결된 상태로 유지됩니다. 티켓 객체 자체가 동작을 바꾸는 것처럼 보이지만, 실제로는 서로 다른 상태 객체에 동작을 위임하고 있는 것입니다!
치트 시트
상태 패턴(State pattern)은 객체의 내부 상태가 변경될 때 객체가 동작을 변경할 수 있도록 합니다. 객체는 서로 다른 상태를 나타내는 별도의 상태 객체에 동작을 위임합니다.
상태 인터페이스를 정의합니다:
type State interface {
Handle(d *Document) string
}현재 상태를 유지하는 컨텍스트 객체를 생성합니다:
type Document struct {
state State
}
func (d *Document) SetState(s State) {
d.state = s
}
func (d *Document) Publish() string {
return d.state.Handle(d)
}상태별 동작을 캡슐화하는 구체적인 상태들을 구현합니다:
type DraftState struct{}
func (s DraftState) Handle(d *Document) string {
d.SetState(ModerationState{})
return "Draft submitted for moderation"
}
type ModerationState struct{}
func (s ModerationState) Handle(d *Document) string {
d.SetState(PublishedState{})
return "Moderation approved, now published"
}
type PublishedState struct{}
func (s PublishedState) Handle(d *Document) string {
return "Already published"
}각 상태는 어떤 일이 일어날지 결정하고 다음 상태로 전환합니다:
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // Draft submitted for moderation
fmt.Println(doc.Publish()) // Moderation approved, now published
fmt.Println(doc.Publish()) // Already published상태 패턴은 동작이 전적으로 현재 상태에 달려 있는, 뚜렷한 운영 모드를 가진 객체에 이상적입니다.
직접 해보기
package main
import (
"fmt"
)
func main() {
// 입력 읽기
var ticketID string
var numProcesses int
fmt.Scanln(&ticketID)
fmt.Scanln(&numProcesses)
// TODO: 주어진 ID로 새 티켓 생성
// TODO: 지정된 횟수만큼 티켓 처리
// 그리고 각 결과를 별도의 줄에 출력
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
8에러 처리와 OOP
error 인터페이스사용자 정의 에러 타입에러 래핑 (fmt.Errorf)센티넬 에러errors.Is()와 errors.As()Panic, Defer, Recover요약 - 파일 파서