커맨드 패턴
Coddy Dart 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 110개 중 94번째.
커맨드 패턴(Command pattern)은 요청을 객체로 캡슐화하여, 서로 다른 요청을 가진 클라이언트를 매개변수화하거나, 작업을 큐에 저장하거나, 실행 취소 기능을 지원할 수 있게 합니다. 메서드를 직접 호출하는 대신, 작업을 커맨드 객체로 감싸서 저장하거나 전달하거나 나중에 실행할 수 있도록 합니다.
이 패턴은 세 가지 핵심 구성 요소를 포함합니다: execute 메서드가 있는 Command 인터페이스, 특정 작업을 구현하는 Concrete Commands, 그리고 실제 작업을 수행하는 Receiver입니다. 선택 사항인 Invoker는 명령이 무엇을 하는지 알지 못한 채 명령을 트리거합니다:
abstract class Command {
void execute();
}
class Light {
void turnOn() => print('Light is ON');
void turnOff() => print('Light is OFF');
}
class TurnOnCommand implements Command {
final Light light;
TurnOnCommand(this.light);
@override
void execute() => light.turnOn();
}
class TurnOffCommand implements Command {
final Light light;
TurnOffCommand(this.light);
@override
void execute() => light.turnOff();
}
class RemoteControl {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void pressButton() {
_command?.execute();
}
}
void main() {
var light = Light();
var remote = RemoteControl();
remote.setCommand(TurnOnCommand(light));
remote.pressButton(); // Light is ON
remote.setCommand(TurnOffCommand(light));
remote.pressButton(); // Light is OFF
}RemoteControl은 조명에 대해 아무것도 모릅니다. 단지 주어진 명령이 무엇이든 실행할 뿐입니다. 이러한 디커플링(decoupling) 덕분에 인보커(invoker)를 수정하지 않고도 새로운 명령을 쉽게 추가할 수 있습니다. 커맨드 패턴은 작업을 일급 객체(first-class objects)로 취급해야 하는 실행 취소/다시 실행 시스템, 작업 큐 또는 매크로 기록 등을 구현하는 데 이상적입니다.
챌린지
쉬움커맨드 패턴을 사용하여 텍스트 에디터 명령 시스템을 구축해 봅시다! 텍스트 작업을 커맨드 객체로 캡슐화하는 시스템을 만들게 됩니다. 이를 통해 에디터는 각 작업의 세부 사항을 알지 못해도 다양한 동작을 실행할 수 있습니다. 이것은 커맨드 패턴의 전형적인 사용 사례입니다. 각 동작이 객체로 저장되는 실행 취소/다시 실행(undo/redo) 시스템을 생각하면 이해하기 쉽습니다.
코드는 다음 두 개의 파일로 구성합니다:
commands.dart: 여기에 커맨드 시스템을 만듭니다. 먼저execute()메서드를 선언하는 추상 클래스Command를 시작으로 작성하세요. 그런 다음String content필드(초기값은 빈 문자열)를 가지고 다음 세 가지 메서드를 포함하는TextDocument클래스(수신자, receiver)를 만듭니다:append(String text)- content의 끝에 텍스트를 추가합니다.clear()- content를 빈 문자열로 설정합니다.show()-Document: [content]를 출력합니다.
TextDocument와 함께 작동하는 세 가지 구체적인 커맨드(concrete commands)를 구현하세요:AppendCommand- 생성자에서 문서와 텍스트를 전달받고, 실행 시 해당 텍스트를 추가합니다.ClearCommand- 생성자에서 문서를 전달받고, 실행 시 문서를 비웁니다.ShowCommand- 생성자에서 문서를 전달받고, 실행 시 문서의 내용을 보여줍니다.
TextEditor클래스(호출자, invoker)를 만듭니다. 커맨드를 큐에 추가하는addCommand(Command command)메서드와 큐에 있는 모든 커맨드를 순서대로 실행하는runAll()메서드가 있어야 합니다.main.dart: 커맨드 파일을 임포트하고 커맨드 패턴을 시연합니다.TextDocument와TextEditor를 생성하세요. 다음 커맨드들을 순서대로 큐에 추가합니다:Hello추가,World추가, 문서 표시,!추가, 다시 표시, 문서 비우기, 그리고 마지막으로 한 번 더 표시합니다. 그런 다음 대기 중인 모든 커맨드를 실행합니다.
TextEditor가 추가, 비우기, 표시하기에 대해 전혀 알지 못하고 단지 주어진 커맨드를 실행할 뿐이라는 점에 주목하세요. 이러한 디커플링(결합도 낮추기)이 커맨드 패턴의 핵심입니다!
예상 출력:
Document: Hello World
Document: Hello World!
Document: 치트 시트
커맨드 패턴(Command pattern)은 요청을 객체로 캡슐화하여, 서로 다른 요청으로 클라이언트를 매개변수화하거나, 작업을 큐에 넣거나, 실행 취소 기능을 지원할 수 있게 합니다.
이 패턴은 네 가지 핵심 구성 요소를 포함합니다:
execute메서드를 가진 Command 인터페이스- 특정 작업을 구현하는 Concrete Commands
- 실제 작업을 수행하는 Receiver
- 명령이 무엇을 하는지 알지 못한 채 명령을 트리거하는 Invoker (선택 사항)
기본 구조:
abstract class Command {
void execute();
}
class Receiver {
void action() => print('Action performed');
}
class ConcreteCommand implements Command {
final Receiver receiver;
ConcreteCommand(this.receiver);
@override
void execute() => receiver.action();
}
class Invoker {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void trigger() {
_command?.execute();
}
}조명 제어 시스템을 사용한 예시:
class Light {
void turnOn() => print('Light is ON');
void turnOff() => print('Light is OFF');
}
class TurnOnCommand implements Command {
final Light light;
TurnOnCommand(this.light);
@override
void execute() => light.turnOn();
}
class RemoteControl {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void pressButton() {
_command?.execute();
}
}
void main() {
var light = Light();
var remote = RemoteControl();
remote.setCommand(TurnOnCommand(light));
remote.pressButton(); // Light is ON
}인보커(invoker)는 리시버(receiver)에 대해 아무것도 알지 못하며, 단지 주어진 명령을 실행할 뿐입니다. 이러한 디커플링(decoupling) 덕분에 인보커를 수정하지 않고도 새로운 명령을 쉽게 추가할 수 있습니다.
직접 해보기
import 'commands.dart';
void main() {
// TODO: TextDocument 인스턴스 생성
// TODO: TextEditor 인스턴스 생성
// TODO: 다음 명령들을 순서대로 큐에 추가하세요:
// 1. "Hello" 추가
// 2. " World" 추가
// 3. 문서 표시
// 4. "!" 추가
// 5. 문서를 다시 표시
// 6. 문서 내용 지우기
// 7. 마지막으로 문서 표시
// TODO: 대기열의 모든 명령 실행
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.