Menu
Coddy logo textTech

커맨드 패턴

Coddy GO 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 107개 중 91번째.

커맨드 패턴(Command pattern)은 요청을 객체로 캡슐화하여, 연산을 매개변수화하거나 요청을 큐에 저장하거나 실행 취소 기능을 지원할 수 있게 합니다. 전략(Strategy) 패턴이 알고리즘을 캡슐화하는 반면, 커맨드 패턴은 작업 전체와 그에 필요한 매개변수를 함께 캡슐화합니다.

Go에서, 우리는 Execute 메서드를 가진 Command 인터페이스를 정의한 다음, 작업을 수행하는 데 필요한 모든 정보를 보유하는 구체적인 명령(concrete commands)을 생성합니다:

type Command interface {
    Execute() string
}

type Light struct {
    IsOn bool
}

type TurnOnCommand struct {
    light *Light
}

func (c *TurnOnCommand) Execute() string {
    c.light.IsOn = true
    return "Light turned on"
}

type TurnOffCommand struct {
    light *Light
}

func (c *TurnOffCommand) Execute() string {
    c.light.IsOn = false
    return "Light turned off"
}

인보커(invoker)는 명령이 무엇을 하는지 알지 못한 채 명령을 저장하고 실행합니다:

type RemoteControl struct {
    command Command
}

func (r *RemoteControl) SetCommand(c Command) {
    r.command = c
}

func (r *RemoteControl) PressButton() string {
    return r.command.Execute()
}

인보커(invoker)는 리시버(receiver, Light)와 완전히 분리되어 있습니다:

light := &Light{}
remote := &RemoteControl{}

remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton())  // 전등이 켜졌습니다

remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton())  // 전등이 꺼졌습니다

커맨드 패턴은 작업을 저장, 지연 또는 재실행해야 하는 실행 취소/다시 실행 시스템, 작업 큐 또는 매크로 기록을 구현하는 데 이상적입니다.

challenge icon

챌린지

쉬움

커맨드 패턴을 사용하여 실행 취소(undo) 기능이 있는 텍스트 에디터를 만들어 봅시다! 텍스트 작업을 캡슐화하는 커맨드를 생성하여 에디터가 작업을 실행하고 되돌릴 수 있도록 할 것입니다. 이는 커맨드 패턴이 왜 강력한지를 보여주는 전형적인 사례입니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • command.go: 커맨드 인터페이스와 구체적인 텍스트 편집 커맨드들을 정의합니다.

    작업을 수행하는 Execute() string과 작업을 되돌리는 Undo() string이라는 두 개의 메서드를 가진 Command 인터페이스를 생성하세요.

    *TextEditor에서 작동하는 두 가지 커맨드 타입을 구현하세요:

    • InsertCommand — 에디터 포인터와 삽입할 텍스트를 가집니다. Execute는 에디터의 내용 끝에 텍스트를 추가하고 Inserted: [text]를 반환합니다. Undo는 끝에서 해당 텍스트를 제거하고 Undone insert: [text]를 반환합니다.
    • DeleteCommand — 에디터 포인터와 끝에서 삭제할 문자 수를 가집니다. Execute는 해당 문자들을 제거하고(실행 취소를 위해 저장함) Deleted: [removed text]를 반환합니다. Undo는 삭제된 텍스트를 복구하고 Undone delete: [restored text]를 반환합니다.
  • editor.go: 텍스트 에디터(수신자, receiver)와 커맨드 이력을 관리하는 인보커(invoker)를 생성합니다.

    Content 필드(string)와 Append(text string)DeleteLast(count int) string(삭제된 텍스트를 반환함) 메서드를 가진 TextEditor 구조체를 만드세요.

    실행된 커맨드들을 이력(history)으로 보관하는 슬라이스를 가진 EditorInvoker 구조체를 만드세요. 다음 메서드들을 추가하세요:

    • ExecuteCommand(cmd Command) string — 커맨드를 실행하고 이력에 추가한 뒤 결과를 반환합니다.
    • UndoLast() string — 이력에서 마지막 커맨드를 제거하고, 해당 커맨드의 Undo를 호출한 뒤 결과를 반환합니다. 이력이 비어 있으면 Nothing to undo를 반환합니다.
  • main.go: 일련의 작업을 통해 커맨드 시스템을 시연합니다.

    작업 횟수를 읽습니다. 각 작업에 대해 작업 유형(insert, delete, 또는 undo)을 읽습니다. insert의 경우 삽입할 텍스트도 읽습니다. delete의 경우 문자 수를 읽습니다. 인보커를 통해 각 작업을 실행하고 결과를 출력하세요. 모든 작업이 끝난 후, 최종 에디터 내용을 Final: [content] 형식으로 출력하세요.

다음과 같은 입력이 제공됩니다:

  • 줄 1: 작업 횟수
  • 각 작업당: 작업 유형, 필요한 경우 추가 데이터(insert는 텍스트, delete는 개수)

예를 들어, 다음과 같이 입력되면:

5
insert
Hello
insert
 World
delete
3
undo
undo

출력은 다음과 같아야 합니다:

Inserted: Hello
Inserted:  World
Deleted: rld
Undone delete: rld
Undone insert:  World
Final: Hello

그리고 다음과 같이 입력되면:

4
insert
Go
insert
Lang
undo
insert
!

출력은 다음과 같아야 합니다:

Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!

그리고 다음과 같이 입력되면:

2
undo
insert
Test

출력은 다음과 같아야 합니다:

Nothing to undo
Inserted: Test
Final: Test

각 커맨드가 작업을 수행하고 되돌리는 데 필요한 모든 것을 어떻게 캡슐화하는지 주목하세요. 인보커는 커맨드가 무엇을 하는지 알 필요가 없습니다. 그저 커맨드를 실행하고 실행 취소 지원을 위해 이력을 유지할 뿐입니다!

치트 시트

커맨드 패턴(Command pattern)은 요청을 객체로 캡슐화하여, 연산을 매개변수화하거나, 요청을 큐에 저장하거나, 실행 취소(undo) 기능을 지원할 수 있게 합니다.

Execute 메서드를 가진 Command 인터페이스를 정의합니다:

type Command interface {
    Execute() string
}

작업을 수행하는 데 필요한 모든 정보를 보유하는 구체적인 커맨드(concrete commands)를 생성합니다:

type Light struct {
    IsOn bool
}

type TurnOnCommand struct {
    light *Light
}

func (c *TurnOnCommand) Execute() string {
    c.light.IsOn = true
    return "Light turned on"
}

type TurnOffCommand struct {
    light *Light
}

func (c *TurnOffCommand) Execute() string {
    c.light.IsOn = false
    return "Light turned off"
}

인보커(invoker)는 커맨드가 무엇을 하는지 알지 못한 채 커맨드를 저장하고 실행합니다:

type RemoteControl struct {
    command Command
}

func (r *RemoteControl) SetCommand(c Command) {
    r.command = c
}

func (r *RemoteControl) PressButton() string {
    return r.command.Execute()
}

인보커와 수신자(receiver) 사이의 디커플링(decoupling)을 보여주는 사용 예시입니다:

light := &Light{}
remote := &RemoteControl{}

remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton())  // Light turned on

remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton())  // Light turned off

실행 취소(undo) 기능을 위해, Command 인터페이스에 Undo 메서드를 추가하고 인보커에서 커맨드 이력을 관리합니다.

직접 해보기

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// 연산 횟수 읽기
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// editor와 invoker 생성
	editor := &TextEditor{}
	invoker := &EditorInvoker{}
	
	for i := 0; i < numOps; i++ {
		// 연산 타입 읽기
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		var result string
		
		switch opType {
		case "insert":
			// 삽입할 텍스트 읽기
			textLine, _ := reader.ReadString('\n')
			text := strings.TrimSuffix(textLine, "\n")
			
			// TODO: InsertCommand를 생성하고 invoker를 통해 실행하세요
			_ = text
			_ = editor
			
		case "delete":
			// 삭제할 문자 개수 읽기
			countLine, _ := reader.ReadString('\n')
			count, _ := strconv.Atoi(strings.TrimSpace(countLine))
			
			// TODO: DeleteCommand를 생성하고 invoker를 통해 실행하세요
			_ = count
			
		case "undo":
			// TODO: invoker의 UndoLast를 호출하세요
			
		}
		
		fmt.Println(result)
	}
	
	// 최종 내용 출력
	fmt.Printf("Final: %s\n", editor.Content)
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨