Menu
Coddy logo textTech

커맨드 패턴

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

커맨드 패턴(Command Pattern)은 요청을 요청에 대한 모든 정보를 포함하는 독립적인 객체로 변환하는 행동 디자인 패턴입니다. 이러한 변환을 통해 요청을 메서드 인자로 전달하거나, 요청의 실행을 지연 또는 대기열에 넣을 수 있으며, 취소 가능한 작업을 지원할 수 있습니다.

레스토랑을 예로 들어 보겠습니다. 요리사에게 무엇을 요리할지 직접 말하는 대신, 주문서에 주문 내용을 적습니다. 웨이터(invoker)는 이 주문서(command)를 주방으로 가져가며, 주방에서는 이를 대기열에 추가하거나, 우선순위를 정하거나, 심지어 취소할 수도 있습니다. 이 패턴은 작업을 호출하는 객체와 작업을 수행하는 객체를 분리합니다.

이 패턴은 네 가지 핵심 구성 요소로 이루어져 있습니다: execute 메서드를 선언하는 Command 인터페이스, 특정 작업을 구현하는 ConcreteCommand 클래스들, 실제 작업을 수행하는 Receiver, 그리고 명령을 트리거하는 Invoker입니다:

interface Command {
    void execute();
}
class Light {  // 수신자
    public void turnOn() {
        System.out.println("Light is ON");
    }
    public void turnOff() {
        System.out.println("Light is OFF");
    }
}
class LightOnCommand implements Command {
    private Light light;
    
    public LightOnCommand(Light light) {
        this.light = light;
    }
    
    public void execute() {
        light.turnOn();
    }
}
class RemoteControl {  // 호출자
    private Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void pressButton() {
        command.execute();
    }
}

인보커(invoker)는 어떤 작업이 수행될지 알지 못하며, 단지 execute()를 호출할 뿐입니다:

Light light = new Light();
Command lightOn = new LightOnCommand(light);

RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton();  // 출력: 전등이 켜졌습니다

커맨드 패턴은 작업을 큐에 넣거나, 실행 취소/다시 실행 기능을 구현하거나, 나중에 실행할 작업을 예약해야 할 때 탁월합니다. 각각의 커맨드는 작업을 수행하는 데 필요한 모든 것을 캡슐화하여 시스템을 더욱 유연하고 확장 가능하게 만듭니다.

challenge icon

챌린지

쉬움

커맨드 패턴(Command Pattern)을 사용하여 스마트 홈 자동화 시스템을 구축해 봅시다! 다양한 가전 기기를 커맨드를 통해 제어할 수 있는 시스템을 만들게 됩니다. 이를 통해 각 기기가 내부적으로 어떻게 작동하는지 알 필요 없이, 마치 유니버설 리모컨처럼 동작을 큐에 추가하거나 실행하고, 나중에 사용하기 위해 저장할 수도 있습니다.

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

  • Command.java: 모든 기기 커맨드가 구현할 Command 인터페이스를 정의합니다. 커맨드의 동작을 수행하는 단일 메서드 execute()를 선언해야 합니다.
  • Devices.java: 스마트 홈 기기를 나타내는 두 개의 수신자(receiver) 클래스를 생성합니다:

    Television - TV is now ON을 출력하는 powerOn() 메서드, TV is now OFF를 출력하는 powerOff() 메서드, 그리고 TV channel set to [channel]을 출력하는 setChannel(int channel) 메서드를 가집니다.

    Thermostat - Thermostat set to [temp] degrees를 출력하는 setTemperature(int temp) 메서드와 Thermostat turned OFF를 출력하는 turnOff() 메서드를 가집니다.

  • Commands.java: 기기 조작을 캡슐화하는 구체적인 커맨드 클래스들을 생성합니다:

    TVOnCommand - 생성자에서 Television을 인자로 받고, 실행 시 powerOn()을 호출합니다.

    TVOffCommand - 생성자에서 Television을 인자로 받고, 실행 시 powerOff()을 호출합니다.

    TVChannelCommand - 생성자에서 Televisionint channel을 인자로 받고, 실행 시 해당 채널로 setChannel()을 호출합니다.

    ThermostatSetCommand - 생성자에서 Thermostatint temperature를 인자로 받고, 실행 시 해당 값으로 setTemperature()를 호출합니다.

  • Main.java: 커맨드를 저장하는 setCommand(Command cmd) 메서드와 저장된 커맨드를 실행하는 pressButton() 메서드를 가진 RemoteControl 클래스(호출자, invoker)를 생성합니다.

    main 메서드에서는 TV 채널 번호(정수)와 서모스탯 온도(정수)라는 두 개의 입력을 받습니다.

    TelevisionThermostat 객체를 생성합니다. RemoteControl을 생성하고 다음 순서대로 커맨드 패턴을 시연합니다:

    1. TVOnCommand를 설정하고 버튼을 누릅니다.
    2. 입력받은 채널로 TVChannelCommand를 설정하고 버튼을 누릅니다.
    3. 입력받은 온도로 ThermostatSetCommand를 설정하고 버튼을 누릅니다.
    4. TVOffCommand를 설정하고 버튼을 누릅니다.

TV 채널(정수)과 서모스탯 온도(정수) 순서대로 두 개의 입력을 받게 됩니다.

예를 들어, 입력값이 572인 경우 출력은 다음과 같습니다:

TV is now ON
TV channel set to 5
Thermostat set to 72 degrees
TV is now OFF

RemoteControl이 기기와 직접 상호작용하지 않고 오직 Command 인터페이스만 알고 있다는 점에 주목하세요. 각 커맨드는 동작을 수행하는 데 필요한 모든 것을 캡슐화하므로, 호출자를 변경하지 않고도 새로운 기기와 커맨드를 쉽게 추가할 수 있습니다!

치트 시트

커맨드 패턴(Command Pattern)은 요청을 객체로 캡슐화하여, 서로 다른 요청을 가진 클라이언트를 매개변수화하고, 요청을 큐에 저장하거나 로그로 기록하며, 취소 가능한 작업을 지원할 수 있게 하는 행동 디자인 패턴입니다.

이 패턴은 작업을 호출하는 객체(invoker)와 작업을 수행하는 객체(receiver)를 분리합니다.

주요 구성 요소

  • Command: execute() 메서드를 선언하는 인터페이스
  • ConcreteCommand: 특정 작업을 구현하는 클래스들
  • Receiver: 실제 작업을 수행하는 객체
  • Invoker: 커맨드를 트리거하는 객체

기본 구조

// Command 인터페이스
interface Command {
    void execute();
}

// Receiver
class Light {
    public void turnOn() {
        System.out.println("Light is ON");
    }
    public void turnOff() {
        System.out.println("Light is OFF");
    }
}

// ConcreteCommand
class LightOnCommand implements Command {
    private Light light;
    
    public LightOnCommand(Light light) {
        this.light = light;
    }
    
    public void execute() {
        light.turnOn();
    }
}

// Invoker
class RemoteControl {
    private Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void pressButton() {
        command.execute();
    }
}

사용법

Light light = new Light();
Command lightOn = new LightOnCommand(light);

RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton();  // 출력: Light is ON

호출자(invoker)는 어떤 작업이 수행될지 알지 못하며, 단지 execute()를 호출할 뿐입니다. 이는 시스템을 유연하고 확장 가능하게 만들며, 작업 큐잉, 실행 취소/다시 실행 기능 구현 또는 작업 스케줄링에 이상적입니다.

직접 해보기

import java.util.Scanner;

// TODO: 다음을 포함하는 RemoteControl 클래스(invoker)를 생성하세요:
// - 명령을 저장하는 setCommand(Command cmd) 메서드
// - 저장된 명령을 실행하는 pressButton() 메서드

class RemoteControl {
    // TODO: RemoteControl 구현하기
}

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 입력 읽기
        int channel = scanner.nextInt();
        int temperature = scanner.nextInt();
        
        // TODO: Television과 Thermostat 생성하기
        
        // TODO: RemoteControl 생성하기
        
        // TODO: Command 패턴 시연하기:
        // 1. TVOnCommand를 설정하고 버튼을 누릅니다
        // 2. 입력받은 채널로 TVChannelCommand를 설정하고 버튼을 누릅니다
        // 3. 입력받은 온도로 ThermostatSetCommand를 설정하고 버튼을 누릅니다
        // 4. TVOffCommand를 설정하고 버튼을 누릅니다
        
        scanner.close();
    }
}
quiz icon실력 점검

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

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