배워서 남 주자

디자인 패턴 - Command 패턴

미래에서 온 개발자 2024. 1. 22. 21:05

들어가기에 앞서

https://www.patterns.dev/vanilla/command-pattern 에 있는 설명과 예시를 바탕으로 하여 관련 주제에 대해 이제까지 공부한 내용을 개인적으로 정리한 포스팅임을 밝힙니다.

 


Command 패턴

 

  • 명령을 처리하는 객체를 통해 메소드와 실행되는 동작의 결합도를 낮출 수 있다. 
  • 특정 작업을 실행하는 객체과 메소드를 호출하는 객체를 분리할 수 있다. 
  • 주요 세 가지 클래스: invoker, receiver, command
    • invoker : command를 생성하고 실행하는 역할 
      나중에 실행할 수 있도록 명령을 대기열(queue)에 넣거나 이미 실행된 명령을 실행 취소하는 데 사용할 수도 있다. 
    • receiver : 특정 command를 수신하고 처리
      애플리케이션의 다른 클래스나 메소드에 위임하여 수행할 수 있다.
    • command : void를 반환하는 매개변수가 없는 메소드를 포함하는 인터페이스 (또는 추상 클래스) 
      입력 및 출력 매개변수의 데이터 유형과 같이 command를 실행하는데 필요한 모든 매개변수도 포함된다.

 

 

사용 예제) 온라인 음식 배달 플랫폼 개발 

사용자는 주문, 주문 음식 확인, 주문 취소를 할 수 있다.

 

class OrderManaer() {
	constructor() {
    	this.orders = []
    }
    
    placeOrder(order, id) {
    	this.orders.push(id);
        return `You have successfully ordered ${order} (${id})`;
    }
    
    trackOrder(id) {
    	return `Your order ${id} will arrive in 20 minutes.`
    }
    
    cancelOrder(id) {
    	this.orders = this.orders.filter(order => order.id !== id);
       	return `You have canceled your order ${id}`
    }
}

// Usage
const manager = new OrderManager();

manager.placeORder('Pad Thai', '1234');
manager.trackOrder('1234');
manager.cancelOrder('1234');

 

 

이렇게 manager의 메소드를 직접 사용해서 개발하다가 특정 메소드의 이름을 변경하거나 메소드의 기능을 변경해야 하는 경우가 생길 수 있다. 예를 들어 placeOrder 대신 addOrder 로 메소드 이름을 바꾼다면? 앱의 규모가 크면 까다로운 작업이 될 것이다. 

 

위의 방식처럼 코드를 작성하는 대신 manager 객체로부터 메소드를 분리하고 각각의 명령을 처리하는 함수를 만들 수 있다. 

 

1. OrderManager 클래스를 리팩토링한다. 

placeOrder, trackOrder, cancelOrder 를 직접 구현하는 대신, execute 라는 하나의 메소드만 가진다. 이 메소드는 인자로 주어진 어떤 명령이든 실행할 수 있다. 

 

class OrderManager {
	constructor() {
    	this.orders = []
    }
    
    execute(command, ...args) {
    	return command.execute(this.orders, ...args)
    }
}

 

 

2.  메소드를 3개의 Command 로 만든다. 

class Command {
	constructor(execute) {
    	this.execute = execute
    }
}

function PlaceOrderCommand(order, id) {
	return new Command(orders => {
    	orders.push(id);
        return `You have successfully ordered ${order} (${id})`;
    }
}

function TrackOrderCommand(id) {
	return new Command(() => `Your order ${id} will arrive in 20 minutes.`);
}

function CancelOrderCommand(id) {
	return new Command(orders => {
    	orders = orders.filter(order => order.id !== id);
        return `You have canceled your order ${id}`
    })
}

 

 

3.  사용부 

const manager = new OrderManager();

manager.execute(new PlaceOrderCommand("Pad Thai", "1234"));
manager.execute(new TrackOrderCommand("1234"));
manager.execute(new CancelOrderCommand("1234"));

 

위의 예제에서 OrderManager가 명령을 만들고 실행하는 invoker의 역할을 맡고 있다. execute 메소드를 통해 명령을 실행하며, 명령 실행 시 필요한 추가적인 인자들을 받아서 전달한다. Command 클래스는 다양한 구체적인 명령들을 생성하기 위한 기반 클래스로 사용된다. 마지막으로 PlaceOrderCommand, TrackOrderCommand, CancelOrderCommand 함수가 receiver 역할을 한다. 

 

 

장점

  • 객체와 메소드를 분리할 수 있다.
  • 객체와 메소드를 분리하면 수명이 지정된 명령을 만들거나, 명령들을 큐에 담아 특정한 시간대에 처리하는 게 가능해진다. 

 

단점

  • 커맨드 패턴을 쓸만한 상황이 딱히 많지 않고 종종 불필요한 코드가 만들어지곤 한다. 

 

 

📚 참고자료

https://www.ramotion.com/blog/frontend-design-patterns/