
템플릿 메서드 패턴

- 정의
- 부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것이다. 이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다. 결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.
- 단점
- 상속 관계이기 때문에 자식이 부모를 의존하게 된다
- 부모 클래스의 코드가 바뀌면 자식 클래스도 그에 대한 영향을 받게된다
- 단점 해결을 위해 전략 패턴을 사용한다
- 이름 그대로 템플릿을 사용하는 방식
- 템플릿은 기준이 되는 거대한 틀
- 템플릿이라는 틀에 변하지 않는 부분을 몰아둔다. 그리고 일부 변하는 부분을 별도로 호출해서 해결
- 추상클래스와 추상메소드를 이용하여 해결함
- 동일한 패턴에 대하여 중복을 제거하고 수정 시에도 템플릿만 수정하면 되기 때문에 유지보수도 용이해짐
예)
@Test
void templateMethodV0() {
logic1();
logic2();
}
private void logic1() {
long startTime = System.currentTimeMillis();
log.info("비즈니스 로직1 실행");
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
private void logic2() {
long startTime = System.currentTimeMillis();
log.info("비즈니스 로직2 실행");
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
@Slf4j
public abstract class AbstractTemplate {
public void execute() {
long startTime = System.currentTimeMillis();
call();
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
protected abstract void call();
}
@Slf4j
public class SubClassLogic1 extends AbstractTemplate {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
}
@Slf4j
public class SubClassLogic2 extends AbstractTemplate {
@Override
protected void call() {
log.info("비즈니스 로직2 실행");
}
}
@Test
void templateMethodV1() {
AbstractTemplate template1 = new SubClassLogic1();
template1.execute();
AbstractTemplate template2 = new SubClassLogic1();
template2.execute();
}
익명 내부 클래스 이용
- 기존 코드는 클래스를 계속 만들어서 상속받아 사용해야하는 불편함이 있음
- 이를 익명 내부 클래스를 이용하여 해결할 수 있다
@Test
void templateMethodV2() {
AbstractTemplate template1 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
};
template1.execute();
AbstractTemplate template2 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직2 실행");
}
};
template2.execute();
}
제네릭 사용
- 리턴값이 다를 수 있기 때문에 제네릭을 사용하여 해결
public abstract class AbstractTemplate<T> {
private final LogTrace trace;
public AbstractTemplate(LogTrace trace) {
this.trace = trace;
}
public T excute(String message) {
TraceStatus status = null;
try {
status = trace.begin(message);
T result = call();
trace.end(status);
return result;
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
protected abstract T call();
}
public String request(String itemId) {
AbstractTemplate<String> template = new AbstractTemplate<String>(trace) {
@Override
protected String call() {
orderService.orderItem(itemId);
return "ok";
}
};
return template.excute("OrderController.request()");
}
public void orderItem(String itemId) {
AbstractTemplate<Void> template = new AbstractTemplate<>(trace) {
@Override
protected Void call() {
orderRepository.save(itemId);
return null;
}
};
template.excute("OrderService.orderItem()");
}