어떠한 카페를 운영하고 있다고 가정하고, 카페 메뉴의 레시피를 출력하는 프로그램을 만들려고 한다.
메뉴는 다음과 같이 세 종류가 있다.
이들 음료는 서로 매우 다르지만 만드는 방법은 굉장히 비슷한 부분이 많다.
각 음료들은 위의 기본적인 과정으로 만들어지지만 각각의 과정에서 세부적으로 하게 되는 일에는 조금씩 차이가 난다. 이러한 상황에서 템플릿 메소드 패턴을 사용하여 레시피를 출력하는 프로그램을 간단하게 구현해보았다.
//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
@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
를 상속받고 과정별 각 추상 함수를 구현하였다. 뜨거운 물을 준비하기 위해서는 MakeBevageService
의 PurifierService
를 사용하였고, 커피를 가는데에 사용할 GrinderService
를 주입받아 사용하였다.
//GrinderService.java
@Service
public class GrinderService {
public String grind(){
return "커피 콩을 갈아서 가루를 냅니다.";
}
}
//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
에서는 각 음료의 타입을 파라미터로 전달받아 레시피를 반환한다.
@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
메소드에서 별도의 타입별 분기 없이 사용할 서비스를 바로 맵에서 가져올 수 있다.
이렇게 템플릿 메소드 패턴을 사용하여 레시피 출력 프로그램을 만들어보았다.
코드는 아래의 저장소에 있다.
저장소