템플릿 메서드란?
부모 클래스에서 알고리즘의 구조를 정의하지만, 해당 알고리즘의 구조를 변경하지 않고 자식 클래스들이 알고리즘의 특정 단계들을 오버라이드하여 행동하는 디자인 패턴
Why
어떤 문제가 있길래?
- 전체적인 동작은 같지만, 세부적인 동작이 다를때
예를들어, 레스토랑에서 주문을 한다고 생각해 보겠습니다. 한정된 수량만 받기 때문에 예약만 받는다고 가정하겠습니다.
public class AppOrder {
public void processOrder() {
takeOrder(); // 주문 받기
provideOrderSummary(); // 주문 요약
provideReservationNo(); // 예약 번호 제공
}
}
public class CallOrder {
public void processOrder() {
takeOrder(); // 주문 받기
verifyOrder(); // 메뉴 확인
provideOrderSummary(); // 주문 요약
getReservationName(); // 예약자 정보 얻기
}
}
public class DirectOrder {
public void processOrder() {
takeOrder(); // 주문 받기
verifyOrder(); // 메뉴 확인
provideOrderSummary(); // 주문 요약
}
}
이렇게 3가지 방법으로 할 수 있습니다.
이렇게 하다보니 중복되는 코드도 많아지고, 나중에 하나를 고치더라도 공통된 코드를 고쳐야해서 유연성도 떨어지게 됩니다. 결국 에러가 발생할 확률이 높아지게 되죠.
여기서 생각을 해봅시다.
- 자신만의 주문 시스템을 가지고 있는 식당들인가? -> X
- 하나의 식당인데 각 주문 방식이 완전 다른가? -> X
- 전체적인 서비스 방향은 같은가? -> O
- 아 근데 상황에 따라서 다른동작이 있네? -> O
어떻게 할 수 있을까?
What
또 생각해볼 시간입니다.
1. 추상클래스
공통된 기능이니까 한번에 관리하면 좋겠다. 인터페이스? 애매한데,,, 어짜피 전체적인 서비스 방향은 같은데 세부 동작만 다른거자나?
코드의 중복성을 최대한 없애기 위해 추상 클래스로 만들자!
2. 구조 재사용
저 서비스 방향은 같고 확장하더라도 세부적으로만 바뀌지 저기에서 크게 바뀌지 않을거 같다.
그럼 메서드내에서 공통적인 부분만 빼면 되겠다.
따라서 takeOrder(), providerOrderSummary()의 경우 공통된 기능이고, verifyOrder(), providerReservationNo() 는 동작이 달라 따로 구현을 해야 합니다. 그리고 processOrder의 레시피는 전체적인 방향을 가지고 있기 때문에 변경이 되면 안됩니다.
How
먼저 공통으로 사용하는 코드와 서로 다른 기능으로 동작하는 메서드를 분리하겠습니다.
public abstract class Order {
public final void processOrder() {
takeOrder(); // 주문 받기
verifyOrder(); // 메뉴 확인
provideOrderSummary(); // 주문 요약
provideReservationNo(); // 예약 번호 제공
}
void takeOrder() {
System.out.println("주문을 받습니다.");
}
void provideOrderSummary() {
System.out.println("주문 요약");
}
abstract void verifyOrder();
abstract void provideReservationNo();
}
이젠 verifyOrder에 집중해보겠습니다.
verify의 경우 직접 주문이나 전화 주문으로는 메뉴가 주문 가능한 것인지 확인시켜주기 위해 필요하지만 앱같은 경우 이미 선택할 수 없게 되어 있을 것입니다.
이렇게 선택을 해야할 때는 어떻게 해야할 까?
Hook
Hook은 동작에 대해 도중에 끼어들어 동작을 제어할 때 사용하게 됩니다. 다양한 용도로 사용되는데 공통적인 목적은 결국 동작의 변경을 위해서 사용합니다.
이러한 hook기능을 사용해서 제어를 할 수 있습니다.
public abstract class Order {
public final void processOrder() {
takeOrder(); // 주문 받기
if (haveToVerify()) {
verifyOrder(); // 메뉴 확인
}
provideOrderSummary(); // 주문 요약
provideReservationNo(); // 예약 번호 제공
}
abstract void verifyOrder();
abstract void provideReservationNo();
void takeOrder() {
System.out.println("주문을 받습니다.");
}
void provideOrderSummary() {
System.out.println("주문 요약");
}
boolean haveToVerify() {
return true; // Hook 메서드
}
}
마지막으로 예약 정보를 받는 providerReservationNo()와 providerReservationName()을 선택해서 받게 됩니다.
물론 하나만 받아서 할 수 있지만, 핸드폰은 예약 번호를 통해 사용자의 정보를 알 수 있어 예약 번호만 남겨져야하고, 전화는 이름 정보가 필요하다고 가정해 보겠습니다. 또한 직접온 경우 만석인지에 따라 다르게 됩니다.
따라서 행동에 따른 동작을 수행하게 해야하는데 여기서도 hook을 사용하겠습니다.
- Call : 항상 예약
- App : 항상 예약
- Direct : 상황에 따라 다름
이므로
public abstract class Order {
public final void processOrder() {
takeOrder(); // 주문 받기
if (haveToVerify()) {
verifyOrder(); // 메뉴 확인
}
provideOrderSummary(); // 주문 요약
if (haveToCheckReservation()) {
provideReservationInfo(); // 예약 번호 제공
}
}
abstract void verifyOrder();
abstract void provideReservationInfo();
void takeOrder() {
System.out.println("주문을 받습니다.");
}
void provideOrderSummary() {
System.out.println("주문 요약");
}
boolean haveToVerify() {
return true; // Hook 메서드
}
boolean haveToCheckReservation() {
return true;
}
}
public class DirectOrder extends Order{
@Override
void verifyOrder() {
System.out.println("직접 확인");
}
@Override
void provideReservationInfo() {
System.out.println("예약자 성함");
}
@Override
boolean haveToCheckReservation() {
if (isFull()) {
return true;
}
return false;
}
private boolean isFull() {
return false;
}
}
위와 같이 기능 세부 동작을 조정할 수 있습니다.
장단점
장점
- 전체적인 방향 or 알고리즘이 같을 때 : 세부적인 구현부만 변경하면 되기 때문에 중복도 적어지고 일관성이 있다고 볼 수 있다.
단점
- 제공된 골격에 의해 제한될 수 있다.
- 기능이 많아질 수록 제한된다.
- (많은 기능들이 있다면 하위 클래스는 동작에만 맞춰서 해야하기 때문에 재사용하기에도 힘들고 변경에도 힘들다.)
사용되는 곳
- 프레임워크 및 라이브러리: 하위 클래스를 작성하면서 상위 클래스에 대한 전체적인 방향은 따라가지만, 특정 부분에 대해서 필요한 것만 구현하여 추가한다는 느낌
- 게임 개발: 게임 루프, 이벤트 처리등과 같은 곳에서 사용
- 특정 이벤트에서 직업별 다른 씬을 구현한다는 느낌
- 생명관리 주기: 초기화, 처리 및 정리 단계 정리 후, 서브 클래스에서 구체적인 구현
*추가
코드를 작성하면서 생각했던 곳 중에 함수형 인터페이스의 사용이 템플릿 메서드와 동작 순서는 반대이지만 유사하다는 느낌도 받았는데 어떻게 생각하는지도 궁금합니다.
'디자인패턴' 카테고리의 다른 글
[디자인 패턴 - 번외] 플라이웨이트 (0) | 2023.07.28 |
---|---|
[디자인 패턴] - 컴포지트 패턴 (0) | 2023.06.30 |
[디자인 패턴] - 반복자 패턴 (2) | 2023.06.30 |
[디자인 패턴] - 전략 패턴 (0) | 2023.06.16 |