본문 바로가기
소프트웨어공학/디자인 패턴

Command Pattern 커맨드(명령) 패턴 - 행동, 메소드 호출의 캡슐화 | [Design pattern] 디자인 패턴

by javapp 자바앱 2021. 12. 31.
728x90

 

 

Command Pattern

 

메소드 호출을 캡슐화(encapsulation) 한다.

 

 

  • 여러 기능을 실행할 수 있도록 재사용성이 높은 클래스를 설계하는 패턴
  • 서비스를 호출할 때 융통성을 높이려고 사용
  • 매개 변수를 사용하여 여러가지 다른 요구사항을 처리

 

 

문제점)

GUI toolkit

GUI 컴포넌트(버튼, 텍스트필드) 들이 Client 가 설정한 임의의 작업을 수행하도록 하려면 어떻게 해야할까

 

Universal remote controller

 

각 버튼이 임의의 가전제품을 제어하게 하려면 어떻게 해야 할까

Unknown consumer electronic devices

 

 

 

 

 

 

 

 

 

 


 

 

객체간 상호작용

  • Receiver : 서비스 제공자
  • Invoker : Client의 일부가 될 수 있다.

 

 

invoker 와 receiver 사이에

Direct Couping이 발생

 

 

 

 

문제가 있는 간단한 코드

class ButtonListener implements ActionListener {
  	void actionPerformed(ActionEvent e) {
		String button = e.getActionCommand();
		
                switch (button) {
                  case "버튼1" : 거실등.On();	break;
                  case "버튼2" : 거실등.Off();	break;
                  case "버튼3" : 차고문.up(); 	break;
                  case "버튼4" : 차고문.down();	break; // 새로운 서비스 추가시 이 부분이 변경
                  case "버튼5" : 차고문.stop();	break; // -> OCP 에 위배됨
                  default : ...       
                } // type checking 필요 
	}
}

 


 

해결)

  • DIP
  • 변경되는 부분을 추상화
  • 메소드 호출 부분을 추상화
  • "명령 객체"

차고문.down();
차고문.stop();

 

 

(명령 객체) 명령에 대한 호출을 캡슐화

 

 

 

 

 

 

Command 객체 사용으로 invoker & receiver 디커플링

		if (requestType == TypeA) commandA.execute();
		else if (requestType == TypeB) commandB.execute();
		else if (requestType == TypeC) commandC.execute();

.execute()

 
주의'
public interface Command {
	public void execute();
}


class LightOffBtn extends JButton implements Command {
   public void execute() { 거실등.Off(); }
}
// 버튼을 Command 클래스를 상속받아 구현하면 문제 발생

button인 동시에 command: user interface와 business logic이 분리되지 않음

 
 

해결2)

명령 객체를 분리

Command Pattern Diagram

 

Command

public interface Command {
	public void execute();
}

class LightOnCmd implements Command {

  private 거실등 거실등; // Receiver를 구성
  
  public void execute() { 
	거실등.On(); // 위임
  }
}
class LightOffCmd implements Command {

  private 거실등 거실등; // Receiver를 구성
  
  public void execute() { 
	거실등.Off(); 
  }
}

 

CommandHolder

public interface CommandHolder {
	public void registerCommand(Command cmd);
	public Command getCommand();
}

// 명령 객체를 가지는 GUI 컴포넌트
class BtnHoldingCmd extends JButton implements CommandHolder {
    private Command 명령; 
   
    public void registerCommand(Command cmd) {
        this.명령 = 명령; 
    } 
    //✓장치 변경에 영향 받지 않음. (동적 장치 변경 가능)
    //✓business logic과 분리됨

   public Command getCommand() {
	  return cmd;
   }
}

 

Invoker

class ButtonListener implements ActionListener {

	void actionPerformed(ActionEvent event) { // event (=버튼)
		CommandHolder 버튼 = (CommandHolder)event.getSource();
		Command 명령 = 버튼.getCommand();
		명령.execute();
	}
} // 새로운 기능 추가 될때 , 변경에 대해 저항력 가진다.

 

Receiver

public class 거실등
{
	public void turnOn() { 
    	System.out.println("The light is on"); 
	}

	public void turnOff() { 
		System.out.println("The light is off"); 
	}
}

 

 


 

 

참가자

Command, Invoker, Receiver

 

 

Receiver : 요청과 연관된 작업을 실제로 수행하는 객체

Command : 메소드 호출을 추상화

ConcreteCommand : 메소드 호출을 캡슐화 ( 특정수신자.특정메소드() )

Invoker : 여러 타입의 command를 실행

- DIP 적용 설계 : Invoker는 Command의 interface or abstract 에 의존

 

 


 

 

예제

 

간단한 스위치 예시 코드)

 

Receiver 수신자

// Receiver
public class Light
{
    public void turnOn(){
        System.out.println("the light is on");
    }
    public void turnOff(){
        System.out.println("the light is off");
    }
}

 

Command

public interface Command
{
    void execute();
}
public class OnCmd implements Command
{
    private Light light; // 구성

    public OnCmd(Light light) { // 주입
        this.light = light;
    }

    @Override
    public void execute() {
        light.turnOn(); // 위임
    }
}

 

Invoker 클라이언트 요청 실행

public class Switch
{
    private List<Command> history = new ArrayList<>(); // (옵션) 어떤 명령이 있었는지

    public void storeAndFire(Command cmd){
        history.add(cmd);
        cmd.execute(); // 위임
    }
    public void store(Command cmd) {
        history.add(cmd);
    }

    public void fire(Command cmd) {
        cmd.execute();
    }
}
 
메인
public class SwitchTest {
    public static void main(String[] args) {
        Light lamp = new Light(); // 리시버

        Command switchOnCmd = new OnCmd(lamp); // 구성


        Switch sw = new Switch(); // InVoker 클라이언트 요청
        String input = new Scanner(System.in).next();
        switch(input)
        {
            case "ON": sw.storeAndFire(switchOnCmd); // 명령 객체 구성
            // OFF
            default:
                System.out.println("The end");
        }
    }
}

실행 결과

 


 

Universal Remote Controller

예제 코드

 

 

 

 

가상 주식 거래 상황에 커맨트 패턴 적용해보기

 

다이어그램

 

  • client는 주식 매수 주문서 (concrete command: BuyStockOrder), 주식 매도 주문서 (concrete command SellStockOrder) 생성
  • 주문서를 대행사(invoker: Agent)에게 전송
  • 대행사는 받은 주문들을 주식거래시스템(receiver: StockTrade)에 전달
  • 단 대행사는 주식거래시스템이 개장할 때까지 자신의 queue에 주문서를 저장

 

invoker

// invoker
public class Agent
{
    private Queue<Order> ordersQueue;

    public Agent() {
        ordersQueue = new LinkedList<>();
    }

    //  주문서 저장
    public void placeOrder(Order order){
        ordersQueue.add(order);
    }

    public void sell(){
        ordersQueue.poll().execute();
    }

    public int orderCount(){
        return ordersQueue.size();
    }
}

 

Stock

public abstract class Stock
{
    int price;
    int numOfShares; // 주식수

    public Stock(int price, int numOfShares) {
        this.price = price;
        this.numOfShares = numOfShares;
    }

    //주식을 샀다.
    public void bought(int num){
        numOfShares -= num;
    }
    // 주식을 팔았다.
    public void sold(int num){
        numOfShares += num;
    }

    public int getPrice() {
        return price;
    }

    public int getNumOfShares() {
        return numOfShares;
    }
    
    public abstract void description();
}

// 여러 종목들
public class Samsung extends Stock
{
    public static final String CODE = "005930";

    public Samsung( int price, int numOfShares) {
        super(price,numOfShares);
    }

    @Override
    public void description() {
        System.out.println("삼성전자 설명");
    }
}
//
// 하이닉스, ...

 

receiver

// 주식 거래 시스템
public class StockTrade
{
    Map<String, Stock> stocks = new HashMap<>();

    public StockTrade() {
        // 종목 셋팅
        stocks.put(Samsung.CODE,new Samsung(70000,10000000));
    }

    public void buy(String code, int num){
        stocks.get(code).bought(num);
    }
    public void sell(String code, int num){
        stocks.get(code).sold(num);
    }

    public void displayShares(String code){
        System.out.println("주식수: "+stocks.get(code).getNumOfShares());
    }
}

 

Command

// command
public interface Order
{
    public void execute();
}

public class BuyStockOrder implements Order
{
    private StockTrade stockTrade;
    private String code="";
    private int num=0;

    public BuyStockOrder(StockTrade stockTrade) {
        this.stockTrade = stockTrade;
    }

    public BuyStockOrder(StockTrade stockTrade, String code, int num) {
        this.stockTrade = stockTrade;
        this.code = code;
        this.num = num;
    }

    public void setOrder(String code, int num) {
        this.code = code;
        this.num = num;
    }

    @Override
    public void execute() {
        if(!(code.equals("")&&num==0)) stockTrade.buy(code,num);
        else System.out.println("코드와 갯수 확인해주세요");
        setOrder("",0);
    }
}

public class SellStockCommand implements Order
{
    // receiver
    private StockTrade stockTrade;
    private String code="";
    private int num=0;

    public SellStockCommand(StockTrade stockTrade) {
        this.stockTrade = stockTrade;
    }

    public SellStockCommand(StockTrade stockTrade, String code, int num) {
        this.stockTrade = stockTrade;
        this.code = code;
        this.num = num;
    }

    public void setOrder(String code, int num) {
        this.code = code;
        this.num = num;
    }
    @Override
    public void execute() {
        if(!(code.equals("")&&num==0)) stockTrade.sell(code,num);
        else System.out.println("코드와 갯수 확인해주세요");
        setOrder("",0);
    }
}

 

메인

public class Client {
    public static void main(String[] args) {
        StockTrade stockTrade = new StockTrade();

        Agent agent = new Agent();
        agent.placeOrder(new BuyStockOrder(stockTrade));
        agent.placeOrder(new BuyStockOrder(stockTrade,Samsung.CODE,100));
        agent.placeOrder(new SellStockCommand(stockTrade,Samsung.CODE,5));


        System.out.println(agent.orderCount());
        //
        agent.sell();

        stockTrade.displayShares(Samsung.CODE);
        agent.sell();

        stockTrade.displayShares(Samsung.CODE);
        agent.sell();

        stockTrade.displayShares(Samsung.CODE);
        //
    }
}

실행결과

 

 
 

댓글