본문 바로가기

디자인 패턴/디자인 패턴

[디자인 패턴] 커맨드 패턴 문제 - 커피공장 (커맨드 패턴, 데코레이터 패턴)

책을 공부하며 새로운 문제를 만들고 구현한것을 기록한 게시글입니다.

(이해한 내용을 바탕으로 작성했기 때문에, 내용이 정확하지않고 틀린부분이 있을수있으며, 예시가 패턴을 사용하기에 알맞지 않을수도있습니다)


커맨드 패턴

- 메소드를 캡슐화하여 매개변수화 하는 패턴. 

- Invoker(메소드 요청자)는 메소드의 정보를 알 필요없이, 매개변수를 입력받아 execute를 실행하기만하면 된다.(즉, Invoker는 메소드 클래스가 execute를 구현한다는 사실 외엔 아무것도 몰라도된다.)

- OCP원칙을 지키며, 코드의 수정을 최소화함


문제

커피 공장에서 일하고있는 준영이는 일이 너무 힘들어, 로봇을 만들어 자신의 작업을 자동화 해놓을려고한다.

(준영이의 공장은 커피를 섞는 작업까지도 손으로 하는 수작업 공장이다)

어느날 우연히 "맛있는 커피를 만드는 비법" 이라는 책을 얻게되었고, 이 과정을 토대로 로봇을 만들것이다.

 

"맛있는 커피를 만드는 비법"에는 다음과 같은 커피들이 들어있다.

(현실과는 무관함)

1. 아이스 아메리카노 (IceAmericano)

- 얼음 넣기

- 원두 갈기

- 에스프레소 넣기

- 물 넣기

 

2. 카페라떼 (Caffelatte)

- 물 넣기

- 우유넣기

- 원두 갈기

- 에스프레소 넣기

 

3. 에스프레소 (Espresso)

- 원두 갈기

- 에스프레소 넣기

 

입력

 

만들 커피가 입력된다. 그럼 로봇은 해당 커피를 만든다.

 

입력 : Caffelatte

 

출력 : 

물 넣기...

우유 넣기...

원두 갈기...

에스프레소 넣기...

카페라떼 완성.


구현

책에 수록된 커피마다 만드는 방법이 다른걸 알수있다.

이 메소드들을 로봇에 전부 구현해넣는다면, (혹은 커피에 따라 다른 메소드를 호출해야한다면) 의존성이 너무 높아지고, 이에따라 새로운 커피가 추가된다면, 코드 수정이 불가피할것이다.

이를 피하기위해, 커맨드 패턴을 이용해 커피만드는 메소드를 캡슐화 하자.

추가로 일련의 동작들 (원두 갈기, 에스프레소넣기...)는 반복적으로 사용되는게 보인다. 이를 데코레이터 패턴으로 만들자.

 

중요한 점은 로봇이 모든 커피들은 같은 interface를 구현한다는것만 알고있으면 된다.

 

우선, 동작들을 데코레이터 패턴으로 만들어야한다. 이를위해, Command interface에 execute 메소드 말고, 추가로 동작들의 실행을 구하는 getCoffee 메소드를 넣자.

 

getCoffee 메소드는 String을 반환하며, 각각의 데코레이터와 중첩된 커피는 차례대로 getCoffee메소드를 호출하면서 최종 결과를 출력한다.

 

아이스 아메리카노를 만드는 과정과, 에스프레소를 넣는 데코레이션은 다음과 같다.

 

// IceAmericano를 만드는 클래스

package prob5_CoffeeFactory.CoffeeFactory;

import prob5_CoffeeFactory.Decorator.*;

public class IceAmericano implements Command{
    
    private Command command;
    
    public String getCoffee(){
        return "IceAmericano!";
    }
    
    public void execute(){
        command = new AddWater(new AddEspresso(new GrindBeans(new AddIce(this))));
        System.out.println(command.getCoffee());
    }
    
}
// 에스프레소를 넣는 클래스

package prob5_CoffeeFactory.Decorator;

import prob5_CoffeeFactory.CoffeeFactory.*;

public class AddEspresso implements Command{
    
    Command command;
    
    public AddEspresso(Command command){
        this.command = command;
    }
    
    public void execute(){}
    
    public String getCoffee(){
        return "AddEspresso...\n" + command.getCoffee();
    }
    
}

 

로봇은 execute를 호출하기만 하면 커피를 만들수있다.

(로봇의 dripCoffee 메소드를 호출하면 execute를 호출한다. dripCoffee 는 execute와 같다)

// 커피 자동생성 로봇

package prob5_CoffeeFactory.Robot;

import prob5_CoffeeFactory.CoffeeFactory.*;

public class Robot {
    private Command coffee;
    
    public void setCoffee(Command coffee){
        this.coffee = coffee;
    }
    
    public void dripCoffee(){
        coffee.execute();
    }
    
}

 

이제, 다른 커피가 추가되고, 이에 따라 다른 과정(뜨거운 물 붓기등 과같은..)이 추가되더라도, 기존 코드의 변경없이 확장할수있게되었다. 또한, 로봇은 그대로 dripCoffee만 호출하면된다.


소스코드

https://github.com/devxb/DesignPatterns/tree/main/DesignPatterns/prob5_CoffeeFactory

 

devxb/DesignPatterns

디자인 패턴 🤔. Contribute to devxb/DesignPatterns development by creating an account on GitHub.

github.com