❓
물음표살인마 블로그
  • README
  • ALGORITHM
    • Sieve of Eratosthenes
    • Round Up
    • Binary Search
    • Union Find
    • Sorting Array
    • Lcm, Gcd
  • TechTalk Review
    • Template
  • Books
    • CS Note for Interview
      • Ch1. Design Pattern & Programming paradigm
        • 1.1.1 Singleton Pattern
        • 1.1.2 Factory Pattern
        • 1.1.4 Observer Pattern
        • 1.1.5 Proxty Pattern & Proxy Server
        • 1.1.8 Model-View-Controller Pattern
        • 1.2.1 Declarative and Functional Programming
        • 1.2.2 Object Oriented Programming
      • Ch2. Network
        • 2.2.1 TCP/IP Four-Layer Model
        • 2.2.1-1 TCP 3, 4 way handshake
        • 2.3 Network Devices L4, L7
        • 2.4.1 ARP, RARP
        • 2.4.2 Hop By Hop Communication
        • 2.4.3 IP Addressing Scheme
      • Ch3. Operating System
        • 3.1.1 Roles and Structure of Operating Systems
        • 3.2.1 Memory Hierarchy
        • 3.2.2 Memory Management
        • 3.3.1 Processes and Compilation Process
        • 3.3.3 Memory Structure of a Process
        • 3.3.4 Process Control Block (PCB)
        • 3.3.5 Multiprocessing
        • 3.3.6 Threads and Multithreading
        • 3.3.7 Shared Resources and Critical Sections
        • 3.3.8 Deadlock
        • 3.4 CPU Scheduling Algorithm
      • Ch4. Database
        • 4.1 Database Basic
        • 4.2 Normalization
        • 4.3 Transaction and Integrity
        • 4.4 Types of Databases
        • 4.5 Indexes
        • 4.6 Types of Joins
        • 4.7 Principles of Joins
      • Ch5. Data Structure
    • Learning the Basics of Large-Scale System Design through Virtual Interview Cases
      • 1. Scalability based on user counts(1/2)
      • 1. Scalability based on user counts(2/2)
      • 2.Back-of-the-envelope estimation
      • 3. Strategies for System Design Interviews
      • 4. Rate Limiter
      • 5. Consistent Hashing
      • 6. Key-Value System Design
      • 7. Designing a Unique ID Generator for Distributed Systems
      • 8. Designing a URL Shortener
      • 9. Designing a Web Crawler
      • 10. Notification System Design
      • 11. Designing a News Feed System
      • 12. Chat System Design
      • 13. AutoComplete
      • 14. Design YouTube
      • 15. Design Google Drive
      • Loadbalancer Algorithms
      • Cache tier
      • CDN, Content Delivery Network
      • Stateless Web tier
    • Computer System A programmer's perspective
    • Effective Java
      • Item 1. Consider Static Factory Methods Instead of Constructors
      • Item 2. Consider a Builder When Faced with Many Constructor Parameters
      • Item 3. Ensure Singleton with Private Constructor or Enum Type
      • Item 4. Enforce Noninstantiability with a Private Constructor
      • Item 5. Prefer Dependency Injection to Hardwiring Resources
      • Item 6. Avoid Creating Unnecessary Objects
      • Item 7. Eliminate Obsolete Object References
      • Item 8. Avoid Finalizers and Cleaners
      • Item 9.Prefer try-with-resources to try-finally
      • Item10. Adhering to General Rules When Overriding equals
        • Handling Transitivity Issues
        • Ensuring Consistency
      • Item11. Override hashCode When You Override equals
      • Item12. Always Override toString
        • Always Override toString
      • Item13. Override Clone Judiciously
      • Item14. Consider Implementing Comparable
      • Item15. Minimize the Accessibility of Classes and Members
      • Item16. Accessor Methods Over Public Fields
      • Item17. Minimize Mutability
      • Item18. Composition over inherentance
      • Item19. Design and Document for Inheritance, or Else Prohibit It
      • Item20. Prefer Interfaces to Abstract Classes
      • Item21. Design Interfaces with Implementations in Mind
      • Item22. Use Interfaces Only to define Types
      • Item23. Prefer Class Hierarchies to Tagged Classes
      • Item24. Favor Static Member Classes Over Non-Static
      • Item28. Use Lists Instead of Arrays
      • Item29. Prefer Generic Types
      • Item30. Favor Generic Methods
    • Head First Design Patterns
      • Ch1. Strategy Pattern
      • Ch2. Observer Pattern
        • Ver1. Ch2. Observer Pattern
      • Ch3. Decorator Pattern
        • Ch3. Decorator Pattern
      • Ch4. Factory Pattern
      • Ch5. Singleton Pattern
      • Ch6. Command Pattern
      • Ch7. Adapter and Facade Pattern
      • Ch8. Template Method Pattern
    • Digging Deep into JVM
      • Chapter 2. Java Memory Area & Memory Overflow
      • Chapter 3. Garbage Collector & Memory Allocation Strategy (1/2)
      • Chapter 3. Garbage Collector & Memory Allocation Strategy (2/2)
      • Chapter 5. Optimization Practice
      • Chapter 6. Class file structure
      • Chapter 8. Bytecode Executor Engine (1/2)
  • Interview Practices
    • Restful API Practices
      • Url Shortener API
      • Event Ticket Reservation API
      • Course Management API
      • Search posts by tags API
      • Online Code platform API
      • Simple Task Management API
      • Event Participation API
      • Review System API
      • Car management API
      • Online Library
    • Tech Review
      • if(kakao)
        • Kakao Account Cache Migration / if(kakao)2022
        • Improving the Anomaly Detection System for KakaoTalk Messaging Metrics / if(kakao) 2022
        • Standardizing API Case Handling Without Redeployment / if(kakaoAI)2024
        • JVM warm up / if(kakao)2022
    • Naver Computer Science
      • Process & Thread
      • TCP & UDP
      • Spring & Servlet
      • Filter & Interceptor & AOP
      • Equals() & ==
      • Dependency Injection
      • Object Oriented Programming
  • F-Lab
    • Week1
      • Client & Server
      • HTTP
      • TCP/UDP
      • REST API
      • Questions
        • Object Oriented Programming
        • HTTP
        • Process & Thread
        • Data Structure
    • Week2
      • OSI 7 layer
      • Web vs WAS
    • Week3
      • RDB vs NoSQL
      • RDB Index
      • Cache
      • Redis
      • Messaging Queue
    • Week4
      • Project - Ecommerce
    • Week5
      • ERD - 1
    • Week6
      • Ecommerce - 2
      • Role
      • pw hashing && Salt
      • CreatedAt, ModifiedAt
      • JWT
      • Copy of ERD - 1
    • Week7
      • Vault (HashiCorp Vault)
    • Week 8
      • Api Endpoints
    • Week10
      • Product Create Workflow
  • TOY Project
    • CodeMentor
      • Implementation of Kafka
      • Project Improvement (Architectural Enhancements)
      • Communication between servers in msa
  • JAVA
    • MESI protocol in CAS
    • CAS (Compare and Set)
    • BlockingQueue
    • Producer & Consumer
    • Synchronized && ReentrantLock
    • Memory Visibility
    • Checked vs Unchecked Exception
    • Thread
    • Batch delete instead of Cascade
    • Java Questions
      • Week 1(1/2) - Basic Java
      • Week 1(2/2) - OOP
      • Week 2(1/2) - String, Exception, Generic
      • Week2(2/2) Lambda, Stream, Annotation, Reflection
      • Week3(1/2) Collections
      • Week3(2/2) Threads
      • Week4 Java Concurrency Programming
      • Week5 JVM & GC
    • Java 101
      • JVM Structure
      • Java Compiles and Execution Method
      • Override, Overload
      • Interface vs Abstract Class
      • Primitive vs Object Type
      • Identity and equality
      • String, StringBuilder, StringBuffer
      • Checked Exceptions and Unchecked Exceptions
      • Java 8 methods
      • Try-with-reources
      • Strong Coupling and Loose Coupling
      • Serialization and Deserialization
      • Concurrency Programming in Java
      • Mutable vs Immutable
      • JDK vs JRE
  • SPRING
    • DIP. Dependency Inversion Principal
    • Ioc container, di practice
    • @Transactional
    • Proxy Pattern
    • Strategy Pattern
    • Template Method Pattern
    • using profile name as variable
    • Spring Questions
      • Spring Framework
      • Spring MVC & Web Request
      • AOP (Aspect-Oriented Programming)
      • Spring Boot
      • ORM & Data Access
      • Security
      • ETC
  • DATABASE
    • Enhancing Query Performance & Stability - User list
    • Ensuring Data Consistency, Atomicity and UX Optimization (feat.Firebase)
    • Redis: Remote Dictionary Server
    • Database Questions
      • Week1 DBMS, RDBMS basics
      • Week2 SQL
      • Week3 Index
      • Week4 Anomaly, Functional Dependency, Normalization
      • Week5 DB Transaction, Recovery
    • Normalization
      • 1st Normal Form
      • 2nd Normal Form
      • 3rd Normal Form
  • NETWORK
    • HTTP & TCP head of line blocking
    • HTTP 0.9-3.0
    • Blocking, NonBlocking and Sync, Async
    • Network Questions
      • Week1 Computer Network Basic
      • Week2(1/3) Application Layer Protocol - HTTP
      • Week2(2/3) Application Layer Protocol - HTTPS
      • Week2(3/3) Application Layer Protocol - DNS
      • Week3 Application Layer
      • Week4 Transport Layer - UDP, TCP
      • Week5 Network Layer - IP Protocol
    • Network 101
      • https://www.google.com
      • TCP vs UDP
      • Http vs Https
      • TLS Handshake 1.2
      • HTTP Method
      • CORS & SOP
      • Web Server Software
  • OS
    • Operating System Questions
      • Week1 OS & How Computer Systems Work
      • Week2(1/2) Process
      • Week2(2/2) Thread
      • Week3 CPU Scheduling
      • Week4 Process Synchronize
      • Week5 Virtual Memory
    • Operating System 101
      • Operating system
        • The role of the operating system
        • The composition of the operating system.
      • Process
        • In Linux, are all processes except the initial process child processes?
        • Zombie process, orphan process
        • (Linux) Daemon process
        • Process address space
        • Where are uninitialized variables stored?
        • Determination of the size of the Stack and Heap
        • Access speed of Stack vs Heap
        • Reason for memory space partitioning
        • Process of compiling a process
        • sudo kill -9 $CURRENT_PID
      • Thread
        • Composition of a thread's address space
      • Process vs Thread
        • Creation of processes and threads in Linux
      • Multiprocessing
        • Web Browser
        • Implementation of multiprocessing
        • Application areas of multiprocessing
      • Multithreading
        • Application areas of multithreading
      • Interrupt
        • HW / SW Interrupt
        • Method of handling interrupts
        • Occurrence of two or more interrupts simultaneously
      • Polling
      • Dual Mode
        • Reason for distinguishing between user mode and kernel mode
      • System call
        • Differentiation between system calls
        • Types of system calls
        • Execution process of a system call
      • Process Control Block (PCB)
        • PCB의 구조
        • 쓰레드는 PCB를 갖고 있을까?
        • 프로세스 메모리 구조
      • Context switching
        • Timing of context switching
        • Registers saved during context switching
        • Context switching in processes
        • Context switching in threads
        • Difference between context switching in processes and threads
        • Information of the current process during context switching
      • Interprocess Communication (IPC)
        • Cases where IPC is used
        • Process address space in IPC Shared Memory technique
        • Types of IPC
  • COMPUTER SCIENCE
    • Computer Architecture 101
      • 3 components of a computer
      • RAM vs ROM
      • CPU vs GPU
      • SIMD
      • Two's complement
      • Harvard Architecture vs. von Neumann Architecture
      • The structure of a CPU.
      • Instruction cycle (CPU operation method)
      • Instruction pipelining
      • Bus
      • Memory area
      • Memory hierarchy structure
        • Reason for using memory hierarchy structure
      • Cache memory
      • L1, L2, L3 Cache
      • Locality of reference (cache)
      • Fixed-point vs Floating-point
        • epresentation of infinity and NaN (Not a Number) in floating-point
      • RISC vs CISC
      • Hamming code
      • Compiler
      • Linking
      • Compiler vs Interpreter
      • Mutex vs Semaphore
      • 32bit CPU and 64bit CPU
      • Local vs Static Variable
      • Page
  • Programming Paradigm
    • Declarative vs Imperative
  • JPA, QueryDsl
    • why fetchResults() is deprecated
  • PYTHON
    • Icecream
  • FASTAPI
    • Template Page
  • LINUX
    • Template Page
  • DATA STRUCTURE
    • Counting Sort
    • Array vs Linked List
  • GIT, Github
    • git clone, invalid path error
  • INFRA
    • Template Page
  • AWS
    • Server Log Archive Pipeline
    • Image Processing using Lambda
  • DOCKER
    • Docker and VM
    • Python Executable Environment
    • Docker commands
  • docker-compose
    • Kafka, Multi Broker
  • KUBERNATES
    • !Encountered Errors
      • my-sql restarts
      • kafka producer: disconnected
    • Kubernetes Components
    • Helm
      • Helm commands
    • Pod network
    • Service network
      • deployment.yaml
      • services.yaml
    • Service type
      • Cluster IP
      • NodePort
    • service-name-headless?
    • kube-proxy
  • GraphQL
    • Template Page
  • WEB
    • Template Page
  • Reviews
    • Graphic Intern Review
    • Kakao Brain Pathfinder Review
    • JSCODE 자바 1기 Review
  • 😁Dev Jokes
    • Image
      • Plot twist
      • Priorities
      • SQL join guide
      • Google is generous
      • Genie dislikes cloud
      • buggy bugs
      • last day of unpaid internship
      • what if clients know how to inspect
      • its just game
      • how i wrote my achievement on resume
      • self explanatory
      • chr(sum(range(ord(min(str(not))))))
Powered by GitBook
On this page
  • 1. 커맨드 패턴 정의와 기본 개념
  • 2. 커맨드 패턴의 구조
  • 3. 음식 주문 과정 자세히 살펴보기
  • 4. 객체마을 식당 등장인물의 역할
  • 5. 객체마을 식당과 커맨드 패턴의 연관성
  • 6. 첫 번째 커맨드 객체 만들기
  • 8. 커맨드 객체 사용하기
  • 9. 커맨드 패턴 클래스 다이어그램 살펴보기
  • 10. 슬롯에 명령 할당하기
  • 11. 리모컨 코드 만들기
  • 12. 커맨드 클래스 만들기
  • 13. 리모컨 테스트
  • 14. API 문서 만들기
  • 15. 작업 취소 기능 추가하기
  • 16. 작업 취소 기능 테스트하기
  • 17. 작업 취소 기능을 구현할 때 상태를 사용하는 방법
  • 18. 선풍기 명령어에 작업 취소 기능 추가하기
  • 19. 선풍기 테스트 코드 만들기
  • 20. 선풍기 코드 테스트
  • 21. 여러 동작을 한 번에 처리하기
  • 22. 매크로 커맨드 사용하기
  • 23. 커맨드 패턴 활용하기
  • 24. 커맨드 패턴 더 활용하기
  • 25. 실전 적용! 커맨드 패턴
  • 종합
  1. Books
  2. Head First Design Patterns

Ch6. Command Pattern

호출 캡슐화하기. 커멘드 패턴

1. 커맨드 패턴 정의와 기본 개념

**커맨드 패턴(Command Pattern)**은 요청을 객체로 캡슐화하여, 요청자와 수신자 간의 결합을 느슨하게 만드는 디자인 패턴이다. 이 패턴은 사용자가 요청을 발송하는 방식과 그 요청을 처리하는 방식 사이에 명확한 구분을 두며, 이를 통해 실행 취소, 작업 기록 및 작업 큐 같은 기능을 손쉽게 구현할 수 있다.

이 패턴의 핵심은 **명령(Command)**이라는 객체를 생성하여, 요청을 포함한 모든 정보를 캡슐화하는 것이다. 명령 객체는 특정 작업을 수행할 수 있는 메서드를 포함하며, 이러한 작업을 언제든지 실행, 취소, 다시 실행할 수 있다.

2. 커맨드 패턴의 구조

커맨드 패턴의 구조는 네 가지 주요 요소로 구성된다:

  1. Command(커맨드): 실행될 작업을 캡슐화한 인터페이스 혹은 추상 클래스.

  2. ConcreteCommand(구체 커맨드): Command 인터페이스를 구현한 클래스. 실제 작업의 구현 내용을 포함.

  3. Invoker(호출자): 명령을 요청하는 객체. ConcreteCommand 객체에 작업을 요청한다.

  4. Receiver(수신자): 실제로 작업을 수행하는 객체. ConcreteCommand 객체로부터 작업을 전달받아 수행.

이 구조를 통해 작업 요청과 실행이 분리되며, 명령의 실행을 유연하게 관리할 수 있다.

3. 음식 주문 과정 자세히 살펴보기

객체마을 식당에서의 음식 주문 과정은 커맨드 패턴의 좋은 예시이다. 고객이 음식을 주문하면, 그 주문은 커맨드 객체로 캡슐화된다. 이 커맨드 객체는 주문 정보를 담고 있으며, 주방 혹은 바텐더 같은 수신자 객체에 전달되어 실제로 주문이 처리된다.

예를 들어, 고객이 "피자 주문"을 요청하면, 해당 요청은 PizzaOrderCommand라는 구체 커맨드 객체로 생성된다. 이 객체는 피자 조리법과 같은 정보를 포함하고 있으며, 주방 클래스에 전달되어 피자 조리 작업이 실행된다.

4. 객체마을 식당 등장인물의 역할

커맨드 패턴에서는 등장인물들이 명확한 역할을 맡는다:

  • 고객(Client): 커맨드를 생성하는 역할을 담당한다. 이 예시에서 고객은 특정 음식을 주문하는 사람이다.

  • 서빙 담당자(Invoker): 커맨드를 실행하도록 요청하는 역할을 한다. 서빙 담당자는 고객의 주문을 받아 주방에 전달한다.

  • 주방장(Receiver): 실제로 주문을 처리하는 역할을 담당한다. 주방장은 커맨드를 받아 음식을 조리한다.

5. 객체마을 식당과 커맨드 패턴의 연관성

객체마을 식당의 시스템은 커맨드 패턴을 완벽하게 반영한다. 고객이 음식을 주문하고, 그 주문이 커맨드 객체로 캡슐화되어, 서빙 담당자와 주방장을 통해 실행된다. 이러한 구조는 커맨드 패턴의 핵심 아이디어인 "호출의 캡슐화"를 잘 보여준다.

6. 첫 번째 커맨드 객체 만들기

커맨드 패턴을 이해하기 위해 먼저 커맨드 객체를 만들어보자. 예를 들어, 객체마을 식당에서 '주문'을 처리하는 커맨드 객체를 만들어 보겠다. 이를 위해 Command라는 인터페이스를 정의하고, 이 인터페이스를 구현하는 구체적인 커맨드 클래스를 작성해야 한다.

Command 인터페이스 정의
public interface Command {
    void execute();
}

이 인터페이스는 execute() 메서드를 가지고 있다. 이는 명령을 실행할 때 호출되는 메서드로, 모든 커맨드 객체는 이 메서드를 구현해야 한다.

구체적인 커맨드 클래스 작성

public class PizzaOrderCommand implements Command {
    private KitchenReceiver kitchen;

    public PizzaOrderCommand(KitchenReceiver kitchen) {
        this.kitchen = kitchen;
    }

    @Override
    public void execute() {
        kitchen.cookPizza();
    }
}

위 코드에서 PizzaOrderCommand 클래스는 Command 인터페이스를 구현하며, 주문이 들어왔을 때 주방에서 피자를 조리하는 명령을 수행하는 역할을 한다.

8. 커맨드 객체 사용하기

이제 생성된 커맨드 객체를 사용하는 방법을 살펴보겠다. 커맨드 객체는 Invoker에 의해 호출되어야 한다. 이 Invoker는 명령을 실행하는 주체이며, 예를 들어, 리모컨이나 서빙 담당자가 이 역할을 할 수 있다.

Invoker 클래스
public class WaiterInvoker {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void placeOrder() {
        command.execute();
    }
}

서빙 담당자(WaiterInvoker)는 고객이 주문한 커맨드를 받아서, 이를 주방에 전달하는 역할을 한다. placeOrder() 메서드에서 커맨드 객체의 execute() 메서드를 호출하여 실제 작업을 수행한다.

9. 커맨드 패턴 클래스 다이어그램 살펴보기

커맨드 패턴의 구조를 시각적으로 이해하기 위해 클래스 다이어그램을 살펴보자.

다이어그램에는 다음과 같은 클래스들이 포함되어 있다:

  • Command: 명령의 추상화된 인터페이스.

  • ConcreteCommand: 명령의 구체적인 구현.

  • Invoker: 명령을 실행하는 주체.

  • Receiver: 명령을 실제로 처리하는 객체.

이 다이어그램을 통해 각 클래스가 어떻게 상호작용하는지, 그리고 커맨드 패턴이 구조적으로 어떻게 동작하는지를 이해할 수 있다.

10. 슬롯에 명령 할당하기

리모컨처럼 여러 명령을 한 번에 관리할 수 있는 상황을 가정해 보자. 각각의 명령은 리모컨의 슬롯에 할당될 수 있다.

리모컨 클래스
public class RemoteControlInvoker {
    private Command[] onCommands;
    private Command[] offCommands;

    public RemoteControlInvoker() {
        onCommands = new Command[7];
        offCommands = new Command[7];
    }

    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    public void pressOnButton(int slot) {
        onCommands[slot].execute();
    }

    public void pressOffButton(int slot) {
        offCommands[slot].execute();
    }
}

이 리모컨 클래스(RemoteControlInvoker)는 여러 슬롯을 가지고 있으며, 각 슬롯에 명령을 할당할 수 있다. pressOnButton()이나 pressOffButton() 메서드를 통해 특정 슬롯에 할당된 명령을 실행할 수 있다.

11. 리모컨 코드 만들기

리모컨에 명령을 할당하고, 이를 실행하는 예제
public class RemoteControlTest {
    public static void main(String[] args) {
        RemoteControlInvoker remote = new RemoteControlInvoker();

        KitchenReceiver kitchen = new KitchenReceiver();
        PizzaOrderCommand pizzaOrder = new PizzaOrderCommand(kitchen);

        remote.setCommand(0, pizzaOrder, null);

        System.out.println("Pressing the On button for slot 0...");
        remote.pressOnButton(0);
    }
}

위 코드에서, 리모컨의 첫 번째 슬롯에 피자 주문 명령을 할당하고, 버튼을 눌러 피자를 주문하는 과정을 보여준다. 이로써 커맨드 패턴의 기본 개념과 이를 실제 코드로 구현하는 방법을 확인할 수 있다.

12. 커맨드 클래스 만들기

커맨드 패턴에서 핵심적인 역할을 하는 클래스가 바로 커맨드 클래스이다. 이 클래스는 실제로 실행될 작업을 캡슐화하여, 호출자(Invoker)가 특정 작업을 요청할 때 이를 실행하거나 취소할 수 있도록 한다.

커맨드 클래스를 만드는 과정에서 주의해야 할 몇 가지 사항이 있다. 우선, 각 커맨드 클래스는 Command 인터페이스를 구현해야 하며, 기본적으로 execute()와 undo() 메서드를 포함해야 한다. 이는 명령을 실행하고, 필요할 경우 이를 취소할 수 있는 기능을 제공하기 위함이다.

LightOnCommand 클래스
public class LightOnCommand implements Command {
    private LightReceiver light;

    public LightOnCommand(LightReceiver light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }

    @Override
    public void undo() {
        light.off();
    }
}

이 클래스는 LightReceiver라는 수신자 객체의 on() 메서드를 호출하여 불을 켜는 명령을 수행한다. undo() 메서드는 그 반대로 불을 끄는 역할을 한다. 이러한 방식으로, 커맨드 클래스는 작업을 캡슐화하여 호출자와 수신자 간의 결합도를 낮출 수 있다.

13. 리모컨 테스트

이제 커맨드 클래스가 제대로 동작하는지 테스트해보자. 리모컨을 사용하여 여러 명령을 실행하고 취소하는 과정을 구현할 수 있다.

리모컨 테스트 코드
public class RemoteControlTest {
    public static void main(String[] args) {
        RemoteControlInvoker remote = new RemoteControlInvoker();

        LightReceiver livingRoomLight = new LightReceiver("Living Room");
        LightOnCommand lightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand lightOff = new LightOffCommand(livingRoomLight);

        remote.setCommand(0, lightOn, lightOff);

        System.out.println("Turning the light on...");
        remote.pressOnButton(0);
        System.out.println("Undoing the operation...");
        remote.pressUndoButton();
        System.out.println("Turning the light off...");
        remote.pressOffButton(0);
    }
}

위 코드는 거실 조명에 대한 커맨드 객체를 리모컨에 할당하고, 버튼을 눌러 조명을 켜거나 끄는 작업을 실행한다. 또한 pressUndoButton() 메서드를 통해 직전에 실행된 명령을 취소하는 기능도 확인할 수 있다.

14. API 문서 만들기

커맨드 패턴을 사용한 코드를 작성한 후에는, 그에 대한 API 문서를 만드는 것이 중요하다. API 문서는 다른 개발자들이 해당 패턴을 어떻게 사용할지 이해할 수 있도록 돕는다. API 문서를 작성할 때는 각 클래스의 역할과 메서드의 기능을 명확히 설명해야 한다.

예시: API 문서 구성

  • Command 인터페이스: 명령을 실행하는 메서드 execute()와 명령을 취소하는 메서드 undo()를 정의합니다.

  • ConcreteCommand 클래스: LightOnCommand, LightOffCommand 등 구체적인 명령을 실행하는 클래스로, 수신자 객체의 메서드를 호출하여 작업을 수행합니다.

  • Invoker 클래스: RemoteControlInvoker 클래스는 여러 명령을 관리하며, 특정 슬롯에 명령을 할당하고, 이를 실행하거나 취소할 수 있습니다.

  • Receiver 클래스: LightReceiver 클래스는 실제로 작업을 수행하는 클래스입니다. 이 클래스는 on() 및 off() 메서드를 통해 조명을 제어합니다.

이와 같은 문서를 작성하면 다른 개발자들이 커맨드 패턴을 쉽게 이해하고, 적절하게 활용할 수 있다.

15. 작업 취소 기능 추가하기

커맨드 패턴의 주요 장점 중 하나는 **작업 취소 기능(Undo)**을 쉽게 구현할 수 있다는 것이다. 이 기능을 추가하기 위해서는 각 커맨드 객체에 undo() 메서드를 구현하고, 호출자가 이를 호출할 수 있도록 해야 한다.

Undo 기능 구현
public class RemoteControlInvoker {
    private Command[] onCommands;
    private Command[] offCommands;
    private Command undoCommand;

    public RemoteControlInvoker() {
        onCommands = new Command[7];
        offCommands = new Command[7];
        undoCommand = new NoCommand(); // 초기에는 아무 명령도 없음
    }

    public void pressOnButton(int slot) {
        onCommands[slot].execute();
        undoCommand = onCommands[slot];
    }

    public void pressOffButton(int slot) {
        offCommands[slot].execute();
        undoCommand = offCommands[slot];
    }

    public void pressUndoButton() {
        undoCommand.undo();
    }
}

RemoteControlInvoker 클래스에 undoCommand 변수를 추가하여, 직전에 실행된 명령을 추적하고, 이를 취소할 수 있도록 구현했다. pressUndoButton() 메서드는 이 undoCommand에 저장된 명령을 실행하여 작업을 취소한다.

16. 작업 취소 기능 테스트하기

이제 구현한 작업 취소 기능이 제대로 동작하는지 테스트해보자.

Undo 기능 테스트 코드

public class RemoteControlTest {
    public static void main(String[] args) {
        RemoteControlInvoker remote = new RemoteControlInvoker();

        LightReceiver livingRoomLight = new LightReceiver("Living Room");
        LightOnCommand lightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand lightOff = new LightOffCommand(livingRoomLight);

        remote.setCommand(0, lightOn, lightOff);

        System.out.println("Turning the light on...");
        remote.pressOnButton(0);
        System.out.println("Turning the light off...");
        remote.pressOffButton(0);
        System.out.println("Undoing the last operation...");
        remote.pressUndoButton();
    }
}

위 코드에서, 불을 켜고 끈 후에 마지막 작업을 취소하여 불이 다시 켜지는 과정을 확인할 수 있다. 이로써 커맨드 패턴의 유용성, 특히 작업 취소 기능의 유용성을 확인할 수 있다.

17. 작업 취소 기능을 구현할 때 상태를 사용하는 방법

커맨드 패턴에서 작업 취소(Undo) 기능을 구현할 때, 작업의 상태를 추적하는 방법이 매우 중요하다. 상태를 관리함으로써, 특정 시점으로 돌아가 작업을 되돌리는 기능을 효과적으로 구현할 수 있다.

작업의 상태를 관리하는 일반적인 방법은 수신자(Receiver)의 상태를 보관하고, Undo 작업 시 이전 상태로 복구하는 것이다. 예를 들어, 선풍기의 속도를 제어하는 기능에서 작업 취소 기능을 구현한다고 가정해보자. 이 경우, 선풍기의 속도 상태를 저장해두었다가, Undo 작업 시 이전 속도로 되돌릴 수 있다.

예시: 선풍기 상태 관리
public class CeilingFanReceiver {
    public static final int HIGH = 3;
    public static final int MEDIUM = 2;
    public static final int LOW = 1;
    public static final int OFF = 0;

    private int speed;

    public CeilingFanReceiver() {
        speed = OFF;
    }

    public void high() {
        speed = HIGH;
        System.out.println("Ceiling fan is on high.");
    }

    public void medium() {
        speed = MEDIUM;
        System.out.println("Ceiling fan is on medium.");
    }

    public void low() {
        speed = LOW;
        System.out.println("Ceiling fan is on low.");
    }

    public void off() {
        speed = OFF;
        System.out.println("Ceiling fan is off.");
    }

    public int getSpeed() {
        return speed;
    }
}

위 코드에서, CeilingFanReceiver 클래스는 선풍기의 속도를 나타내는 speed 변수를 관리한다. 속도를 변경할 때마다 해당 상태를 기록하고, 필요시 이전 상태로 복구할 수 있다.

18. 선풍기 명령어에 작업 취소 기능 추가하기

이제 선풍기 명령어에 작업 취소 기능을 추가하겠다. CeilingFanReceiver의 상태를 활용해 현재 속도를 기록하고, Undo 시 이전 속도로 복구하는 방식으로 구현할 수 있다.

예시: 선풍기 명령어 클래스
public class CeilingFanHighCommand implements Command {
    private CeilingFanReceiver ceilingFan;
    private int prevSpeed;

    public CeilingFanHighCommand(CeilingFanReceiver ceilingFan) {
        this.ceilingFan = ceilingFan;
    }

    @Override
    public void execute() {
        prevSpeed = ceilingFan.getSpeed();
        ceilingFan.high();
    }

    @Override
    public void undo() {
        if (prevSpeed == CeilingFanReceiver.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFanReceiver.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFanReceiver.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFanReceiver.OFF) {
            ceilingFan.off();
        }
    }
}

이 클래스에서 execute() 메서드는 현재 선풍기의 속도를 기록한 후, 속도를 최고로 올린다. undo() 메서드는 기록된 이전 속도를 확인하고, 그 속도로 복구한다.

19. 선풍기 테스트 코드 만들기

이제 선풍기 명령어와 작업 취소 기능이 잘 동작하는지 테스트해보자.

예시: 선풍기 테스트 코드
public class RemoteControlTest {
    public static void main(String[] args) {
        RemoteControlInvoker remote = new RemoteControlInvoker();

        CeilingFanReceiver ceilingFan = new CeilingFanReceiver();
        CeilingFanHighCommand ceilingFanHigh = new CeilingFanHighCommand(ceilingFan);
        CeilingFanMediumCommand ceilingFanMedium = new CeilingFanMediumCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);

        remote.setCommand(0, ceilingFanHigh, ceilingFanOff);
        remote.setCommand(1, ceilingFanMedium, ceilingFanOff);

        System.out.println("Setting fan to high...");
        remote.pressOnButton(0);
        System.out.println("Undoing last operation...");
        remote.pressUndoButton();

        System.out.println("Setting fan to medium...");
        remote.pressOnButton(1);
        System.out.println("Undoing last operation...");
        remote.pressUndoButton();
    }
}

이 테스트 코드는 선풍기를 최고 속도와 중간 속도로 설정한 후, 각각의 작업을 취소하여 선풍기의 상태가 제대로 복구되는지 확인한다.

20. 선풍기 코드 테스트

위에서 작성한 테스트 코드를 실행하면, 다음과 같은 결과를 얻을 수 있다:

Setting fan to high...
Ceiling fan is on high.
Undoing last operation...
Ceiling fan is off.
Setting fan to medium...
Ceiling fan is on medium.
Undoing last operation...
Ceiling fan is off.

이 출력 결과를 통해 작업이 올바르게 실행되고, 취소되는지 확인할 수 있다. 이를 통해 커맨드 패턴을 활용한 상태 관리와 Undo 기능 구현이 성공적으로 이루어졌음을 알 수 있다.

21. 여러 동작을 한 번에 처리하기

마지막으로, 커맨드 패턴을 활용해 여러 동작을 한 번에 처리하는 방법을 알아보자. 이 기능은 매크로(Macro) 명령어를 통해 구현할 수 있으며, 여러 개의 커맨드 객체를 순차적으로 실행하거나 취소할 수 있다.

매크로 명령어 클래스
public class MacroCommand implements Command {
    private Command[] commands;

    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }

    @Override
    public void execute() {
        for (Command command : commands) {
            command.execute();
        }
    }

    @Override
    public void undo() {
        for (int i = commands.length - 1; i >= 0; i--) {
            commands[i].undo();
        }
    }
}

MacroCommand 클래스는 여러 커맨드 객체를 배열로 받아, 순차적으로 실행하거나 취소한다. execute() 메서드는 배열의 각 커맨드를 실행하고, undo() 메서드는 역순으로 커맨드를 취소한다.

매크로 명령어 테스트
public class RemoteControlTest {
    public static void main(String[] args) {
        RemoteControlInvoker remote = new RemoteControlInvoker();

        CeilingFanReceiver ceilingFan = new CeilingFanReceiver();
        LightReceiver livingRoomLight = new LightReceiver("Living Room");

        CeilingFanHighCommand ceilingFanHigh = new CeilingFanHighCommand(ceilingFan);
        LightOnCommand lightOn = new LightOnCommand(livingRoomLight);

        Command[] partyOn = { ceilingFanHigh, lightOn };
        MacroCommand partyOnMacro = new MacroCommand(partyOn);

        remote.setCommand(0, partyOnMacro, null);

        System.out.println("Executing macro command...");
        remote.pressOnButton(0);
        System.out.println("Undoing macro command...");
        remote.pressUndoButton();
    }
}

이 코드는 매크로 명령어를 사용해 선풍기를 최고 속도로 설정하고, 거실 조명을 켜는 작업을 한 번에 처리한다. 이후, 이 두 작업을 모두 취소하는 기능도 구현된다.

22. 매크로 커맨드 사용하기

이전 Part 4에서 매크로 명령어를 소개했으며, 이제 이 명령어를 활용하는 방법을 더 구체적으로 살펴보겠다. 매크로 커맨드는 여러 개의 명령을 묶어 한 번에 실행하거나 취소할 수 있는 강력한 도구이다.

매크로 커맨드를 통해 한 번에 여러 동작을 수행할 수 있다. 이는 특히 복잡한 작업이 필요한 상황에서 매우 유용하다. 예를 들어, 스마트 홈 시스템에서 여러 장치를 동시에 제어하는 작업이 필요할 때 매크로 커맨드를 사용하면 효과적이다.

23. 커맨드 패턴 활용하기

커맨드 패턴을 실전에서 활용할 수 있는 다양한 방법을 알아보자. 이 패턴은 사용자 인터페이스(UI) 이벤트 처리, 작업 큐 관리, 트랜잭션 관리 등에서 널리 사용된다.

사용자 인터페이스 이벤트 처리

예를 들어, GUI 프로그램에서 버튼 클릭 이벤트를 처리할 때 커맨드 패턴을 사용하면, 버튼 클릭 시 호출되는 작업을 객체로 캡슐화하여 관리할 수 있다. 이는 이벤트 처리 로직을 깔끔하게 유지하며, 다양한 이벤트를 일관되게 처리할 수 있게 한다.

작업 큐 관리

커맨드 패턴은 작업 큐를 관리하는 데에도 유용하다. 작업이 큐에 추가될 때마다 해당 작업을 커맨드 객체로 캡슐화하고, 큐에서 작업을 꺼내 실행하거나 취소할 수 있다. 이를 통해 작업 관리가 더욱 체계적으로 이루어진다.

24. 커맨드 패턴 더 활용하기

커맨드 패턴은 단순히 명령을 캡슐화하는 것을 넘어, 복잡한 기능을 더욱 쉽게 구현할 수 있게 돕는다. 다음은 커맨드 패턴을 더 활용할 수 있는 몇 가지 예이다.

트랜잭션 관리

여러 작업을 하나의 트랜잭션으로 묶어 관리할 때, 커맨드 패턴을 사용하면 각 작업을 독립적으로 관리하면서도 전체 트랜잭션의 일관성을 유지할 수 있다. 트랜잭션 실패 시 각 작업을 개별적으로 취소할 수 있어, 복구 작업이 간편해진다.

리모컨 시스템 확장

스마트 홈 시스템에서 여러 가전제품을 제어하는 리모컨을 예로 들자면, 새로운 가전제품이 추가될 때마다 해당 제품에 맞는 커맨드 클래스를 작성하고, 이를 리모컨에 쉽게 통합할 수 있다. 이는 시스템 확장을 유연하게 처리할 수 있도록 돕는다.

25. 실전 적용! 커맨드 패턴

마지막으로, 실제 프로젝트에 커맨드 패턴을 적용하는 방법을 살펴보겠다. 커맨드 패턴을 실전에서 효과적으로 사용하기 위해서는 다음과 같은 사항을 고려해야 한다.

  1. 작업의 복잡도: 커맨드 패턴은 작업이 복잡해질수록 그 진가를 발휘한다. 단순한 작업에는 오히려 과도한 설계가 될 수 있으므로, 적용할 때 작업의 복잡도를 고려해야 한다.

  2. 확장성: 커맨드 패턴은 시스템 확장에 유리하다. 새로운 명령이 필요할 때, 기존 코드를 수정할 필요 없이 새로운 커맨드 클래스를 추가하여 쉽게 확장할 수 있다.

  3. 유지보수성: 커맨드 패턴을 사용하면, 코드의 유지보수가 용이해진다. 각 커맨드가 독립적인 클래스로 구현되기 때문에, 특정 작업의 로직을 수정해야 할 경우 해당 커맨드 클래스만 수정하면 된다.

  4. 테스트 용이성: 각 커맨드 객체를 독립적으로 테스트할 수 있어, 시스템의 각 구성 요소를 개별적으로 검증할 수 있다. 이는 테스트 자동화 및 품질 관리를 더욱 쉽게 만들어준다.

종합

interface Command
public interface Command {
    void execute();
    void undo();
}
class Light (수신자)
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class Light {
    public void on() {
        log.info("The light is ON");
    }

    public void off() {
        log.info("The light is OFF");
    }
}

3. Li

LightOnCommand 클래스 (전등 켜기 명령)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class LightOnCommand implements Command {

    private final Light light;

    @Autowired
    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }

    @Override
    public void undo() {
        light.off();
    }
}
LightOffCommand 클래스 (전등 끄기 명령)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class LightOffCommand implements Command {

    private final Light light;

    @Autowired
    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }

    @Override
    public void undo() {
        light.on();
    }
}
5. RemoteControl 클래스 (발신자)
public class RemoteControl {
    private Command[] onCommands;
    private Command[] offCommands;
    private Command undoCommand;

    public RemoteControl() {
        onCommands = new Command[7];
        offCommands = new Command[7];
        undoCommand = new NoCommand(); // 초기에는 아무 명령도 없음
    }

    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    public void pressOnButton(int slot) {
        onCommands[slot].execute();
        undoCommand = onCommands[slot];
    }

    public void pressOffButton(int slot) {
        offCommands[slot].execute();
        undoCommand = offCommands[slot];
    }

    public void pressUndoButton() {
        undoCommand.undo();
    }
}
RemoteLoader 클래스 (클라이언트)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RemoteLoader implements CommandLineRunner {

    private final RemoteControl remoteControl;
    private final LightOnCommand lightOnCommand;
    private final LightOffCommand lightOffCommand;

    @Autowired
    public RemoteLoader(RemoteControl remoteControl, LightOnCommand lightOnCommand, LightOffCommand lightOffCommand) {
        this.remoteControl = remoteControl;
        this.lightOnCommand = lightOnCommand;
        this.lightOffCommand = lightOffCommand;
    }

    public static void main(String[] args) {
        SpringApplication.run(RemoteLoader.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        remoteControl.setCommand(0, lightOnCommand, lightOffCommand);

        // 전등을 켭니다
        remoteControl.pressOnButton(0);
        // 전등을 끕니다
        remoteControl.pressOffButton(0);
        // 마지막 명령 취소 (전등 켜기)
        remoteControl.pressUndoButton();
    }
}

스마트 홈 애플리케이션 개발
public class SmartHomeRemoteControl {
    private Command[] onCommands;
    private Command[] offCommands;

    public SmartHomeRemoteControl() {
        onCommands = new Command[5];
        offCommands = new Command[5];
    }

    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    public void pressOnButton(int slot) {
        onCommands[slot].execute();
    }

    public void pressOffButton(int slot) {
        offCommands[slot].execute();
    }
}

이와 같은 코드 구조를 통해, 스마트 홈 시스템에서 다양한 장치를 손쉽게 제어할 수 있으며, 새로운 장치가 추가되더라도 확장이 용이하다.

PreviousCh5. Singleton PatternNextCh7. Adapter and Facade Pattern

Last updated 9 months ago