안녕하세요. 이번 포스팅은 디자인 패턴의 템플릿 메서드 패턴(Template Method Pattern)에 대해 포스팅 해보겠습니다.
템플릿 메서드 패턴은 소스코드 상에서 중복되는 부분과 중복되지 않는 부분을 분리시키는 디자인 패턴을 의미합니다.
바로 예제를 통해 알아보겠습니다.
예제 1. 템플릿 메서드 패턴이 적용 전
@Slf4j
public class TemplateMethodTest {
@Test
void templateMethodV1() {
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);
}
}

위 예제 소스코드는 비즈니스 로직 수행 시간을 측정하고 있습니다.
비즈니스 로직 부분은 log.info("비즈니스 로직1 실행"), log.info("비즈니스 로직2 실행") 밖에 없습니다.
하지만, 위아래로 로직 수행 시간을 측정하는 부가적인 소스코드가 중복되어 있습니다.
비즈니스 로직 부분과 로직 수행 시간 측정 부분을 분리시켜 소스코드를 작성하면 유지보수에 좋을 것 같다는 생각이 듭니다.
예제 2. 템플릿 메서드 패턴 적용
추상클래스를 하나 생성 후 execute 메서드를 생성합니다.
execute 메서드는 중복된 부분인 로직 시간 측정하는 역할을 담당합니다.
그리고 중복되지 않은 부분인 비즈니스 로직 실행 역할을 담당하는 추상메서드 call을 생성합니다.
@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();
}
위에서 생성한 추상 클래스 AbstractTemplate을 상속받은 후 추상메서드인 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 실행");
}
}
위에서 중복된 부분과 중복되지 않은 부분을 분리시켜 보았습니다. 위 같은 패턴이 템플릿 메서드 패턴입니다.
테스트를 해보겠습니다.
@Slf4j
public class TemplateMethodTest {
/**
* 템플릿 메서드 적용
*/
@Test
void templateMethodV2() {
AbstractTemplate template = new SubClassLogic1();
template.execute();
AbstractTemplate template2 = new SubClassLogic2();
template2.execute();
}
}

테스트가 정상적으로 성공한 것을 확인했습니다.
익명 내부 클래스를 사용하면 굳이 자식 클래스 여러 개를 만들 필요가 없습니다.
@Slf4j
public class TemplateMethodTest {
/**
* 템플릿 메서드 적용, 익명 내부 클래스 적용
*/
@Test
void templateMethodV3() {
AbstractTemplate template = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
};
template.execute();
AbstractTemplate template2 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직2 실행");
}
};
template2.execute();
}
}
템플릿 메서드 패턴의 한계
템플릿 메서드는 상속과 오버라이딩을 통한 다형성을 활용하여 중복된 부분과 중복되지 않은 부분을 깔끔하게 나눌 수 있다는 면에서 유지보수를 편리하게 할 수 있다는 장점이 존재합니다.
하지만 템플릿 메서드는 상속을 사용하기 때문에 의존 관계에 대한 문제점이 존재합니다.
자식 클래스의 입장에서는 전혀 부모 클래스의 기능을 사용하지 않지만 템플릿 메서드 패턴을 위해 부모 클래스의 기능을 모두 상속받고 있습니다. 이는 좋은 설계가 아니며, 만약 부모 클래스가 수정이 된다면 자식 클래스에도 영향을 받을 수도 있습니다.
추가적으로 내부 익명 클래스를 사용하면 소스코드가 복잡해지는 감도 없지 않아 있습니다.
템플릿 메서드 패턴과 비슷한 역할을 하면서 상속의 단점을 제거할 수 있는 디자인 패턴인 전략 패턴(Strategy Pattern)이 존재합니다.
다음 포스팅은 전략 패턴에 대해 포스팅해보겠습니다.
해당 포스팅은 인프런 - 스프링 핵심 원리 고급편을 듣고 정리한 내용입니다.