템플릿 메소드 패턴 사용하기

hksdpr·2023년 11월 11일
0
post-thumbnail

레시피 출력 프로그램

어떠한 카페를 운영하고 있다고 가정하고, 카페 메뉴의 레시피를 출력하는 프로그램을 만들려고 한다.
메뉴는 다음과 같이 세 종류가 있다.

  • 커피
  • 에이드

이들 음료는 서로 매우 다르지만 만드는 방법은 굉장히 비슷한 부분이 많다.

  1. 잔을 준비한다.
  2. 베이스가 되는 기본 음료를 준비한다.(찬 물, 뜨거운 물 등)
  3. 기본 음료를 잔에 넣는다.
  4. 재료를 기본 음료에 녹여낸다.
  5. 장식을 추가한다.
  6. 손님에게 제공한다.

각 음료들은 위의 기본적인 과정으로 만들어지지만 각각의 과정에서 세부적으로 하게 되는 일에는 조금씩 차이가 난다. 이러한 상황에서 템플릿 메소드 패턴을 사용하여 레시피를 출력하는 프로그램을 간단하게 구현해보았다.

MakeBeverageService

//MakeBeverageService
@RequiredArgsConstructor
public abstract class MakeBeverageService {

  protected final PurifierService purifierService;

  public String makeBeverage(){
    return prepareGlass() + "\n" +
      prepareBase() + "\n" +
      pourBaseIntoGlass() + "\n" +
      infuseGradient() + "\n" +
      addGarnish() + "\n" +
      serve();
  }
  public abstract String prepareGlass();
  public abstract String prepareBase();
  public abstract String pourBaseIntoGlass();
  public abstract String infuseGradient();
  public abstract String addGarnish();
  public String serve(){
    return "서빙합니다.";
  }
}

각 음료를 만들 때의 공통적인 과정을 MakeBeverageService 라는 추상 클래스에 정의하였다. 그리고 각 과정을 순차적으로 실행하는 makeBeverage 메소드를 작성하였다. 그리고 각각의 과정을 음료별로 구현할 수 있도록 추상 함수로 정의해두었다. 베이스가 되는 기본 음료(찬 물, 뜨거운 물 등)는 커피, 차, 에이드 모두 정수기에서 가져오기 때문에 PurifierService를 주입받았다.

//PurifierService
@Service
public class PurifierService {
  public String getColdWater(){
    return "차가운 물";
  }

  public String getHotWater(){
    return "뜨거운 물";
  }

  public String getIce(){
    return "얼음";
  }
}

각 음료 별 구체 클래스

MakeCoffeeService

//MakeCoffeeService
@Service
public class MakeCoffeeService extends MakeBeverageService{
  private final GrinderService grinderService;

  public MakeCoffeeService(
    PurifierService purifierService,
    GrinderService grinderService
  ) {
    super(purifierService);
    this.grinderService = grinderService;
  }

  @Override
  public String prepareGlass() {
    return "커피잔을 준비합니다.";
  }

  @Override
  public String prepareBase() {
    return purifierService.getHotWater()+"를 준비합니다.";
  }

  @Override
  public String pourBaseIntoGlass() {
    return "잔에 "+purifierService.getHotWater()+"를 넣습니다.";
  }

  @Override
  public String infuseGradient() {
    return grinderService.grind()+" 그 후 에스프레소를 준비해 잔에 넣습니다.";
  }

  @Override
  public String addGarnish() {
    return "시나몬 가루를 뿌립니다.";
  }

  @Override
  public String serve() {
    return "잔받침과 함께 서빙합니다.";
  }
}

커피 레시피를 출력하는 서비스를 구현하기 위해 MakeBeverageService를 상속받고 과정별 각 추상 함수를 구현하였다. 뜨거운 물을 준비하기 위해서는 MakeBevageServicePurifierService를 사용하였고, 커피를 가는데에 사용할 GrinderService를 주입받아 사용하였다.

//GrinderService.java
@Service
public class GrinderService {
  public String grind(){
    return "커피 콩을 갈아서 가루를 냅니다.";
  }
}

MakeTeaService, MakeAdeService

//MakeTeaService.java
@Service
public class MakeTeaService extends MakeBeverageService{
  public MakeTeaService(PurifierService purifierService) {
    super(purifierService);
  }

  @Override
  public String prepareGlass() {
    return "찻잔을 준비합니다.";
  }

  @Override
  public String prepareBase() {
    return purifierService.getHotWater()+"를 준비합니다.";
  }

  @Override
  public String pourBaseIntoGlass() {
    return "잔에 "+purifierService.getHotWater()+"를 넣습니다.";
  }

  @Override
  public String infuseGradient() {
    return "티백을 잔에 넣고 우립니다.";
  }

  @Override
  public String addGarnish() {
    return "";
  }
}
@Service
public class MakeAdeService extends MakeBeverageService{

  public MakeAdeService(PurifierService purifierService) {
    super(purifierService);
  }

  @Override
  public String prepareGlass() {
    return "얼음잔을 준비합니다."+
      purifierService.getIce()+"를 넣고 "+
      purifierService.getColdWater()+"를 반 채웁니다.";
  }

  @Override
  public String prepareBase() {
    return "사이다를 잔에 넣습니다.";
  }

  @Override
  public String pourBaseIntoGlass() {
    return "에이드 시럽을 잔에 넣습니다.";
  }

  @Override
  public String infuseGradient() {
    return "잘 저어줍니다.";
  }

  @Override
  public String addGarnish() {
    return "라임 슬라이스 하나를 넣어둡니다.";
  }
}

두 서비스 모두 MakeCoffeeService와 마찬가지로 MakeBeverageService의 각 추상 함수를 구현하였다.

BeverageController

이제 이 서비스들을 사용할 BeverageController에서는 각 음료의 타입을 파라미터로 전달받아 레시피를 반환한다.

@RestController
@RequestMapping
@RequiredArgsConstructor
public class BeverageController {

  private final MakeAdeService makeAdeService;
  private final MakeCoffeeService makeCoffeeService;
  private final MakeTeaService makeTeaService;

  private final Map<BeverageType, MakeBeverageService> serviceMap = new HashMap<>();

  @PostConstruct
  private void initServiceMap(){
    serviceMap.put(BeverageType.Ade, makeAdeService);
    serviceMap.put(BeverageType.Coffee, makeCoffeeService);
    serviceMap.put(BeverageType.Tea, makeTeaService);
  }

  @GetMapping("/beverages")
  public BeverageResponse getBeverage(BeverageType type){
    BeverageResponse response = new BeverageResponse();
    response.setBeverageType(type.name());
    response.setResult(serviceMap.get(type).makeBeverage());
    return response;
  }
}

각 서비스를 주입받고 @PostConstruct 어노테이션을 사용하여 주입받은 서비스들을 음료의 타입별로 맵에 저장한다. 이렇게 하면 getBeverage 메소드에서 별도의 타입별 분기 없이 사용할 서비스를 바로 맵에서 가져올 수 있다.

이렇게 템플릿 메소드 패턴을 사용하여 레시피 출력 프로그램을 만들어보았다.
코드는 아래의 저장소에 있다.
저장소

0개의 댓글