프로그래밍/디자인패턴

템플릿 메서드 패턴(Template Method Pattern) 총정리

Jinwookoh 2025. 12. 7. 17:58

"로그인 기능을 만들 때도, 게시판 기능을 만들 때도, 항상 시작할 때 DB 연결하고 끝날 때 연결 끊는 코드가 중복되네?"

이처럼 **변하지 않는 부분(뼈대)**과 **변하는 부분(핵심 로직)**을 분리하여 코드 중복을 없애는 것이 템플릿 메서드 패턴의 핵심입니다. 하지만 이를 구현하는 방식은 자바와 스프링의 발전과 함께 크게 변해왔습니다.


1. 방식 1: 고전적 템플릿 메서드 (Classic GoF) - "상속"

가장 기본적인 형태로, 부모 클래스(추상 클래스)에 전체적인 흐름(Template)을 정의하고, 자식 클래스에서 구체적인 로직을 채워 넣는 방식입니다.

구조

  • AbstractClass: 뼈대(execute)와 추상 메서드(call) 정의.
  • ConcreteClass: 추상 메서드 구현.

코드 예시

Java
// 1. 추상 클래스 (뼈대)
public abstract class FileProcessor {
    // 템플릿 메서드 (전체 흐름, final로 오버라이딩 막음)
    public final void process(String path) {
        System.out.println("1. 파일 열기: " + path); // 공통 로직
        
        String content = readFile(path); // 변하는 부분 (Hook)
        
        System.out.println("3. 결과 저장: " + content); // 공통 로직
    }

    // 자식이 구현해야 할 부분
    protected abstract String readFile(String path);
}

// 2. 자식 클래스 (구체적 구현)
public class JsonFileProcessor extends FileProcessor {
    @Override
    protected String readFile(String path) {
        return "JSON 데이터 파싱 완료"; // 핵심 비즈니스 로직
    }
}

장단점

  • 장점: 알고리즘의 구조를 부모가 꽉 잡고 있어서, 전체 흐름을 일관성 있게 유지할 수 있습니다.
  • 단점:
    • 상속의 단점: 자식 클래스가 부모 클래스에 강하게 결합됩니다.
    • 유연성 부족: 로직 하나 바꾸자고 매번 클래스를 상속받아 새로 만들어야 합니다. (클래스 폭발)

2. 방식 2: 템플릿 콜백 패턴 (Template Callback) - "합성" (Spring Style)

상속의 단점을 해결하기 위해 "전략 패턴(Strategy Pattern)"을 섞어서 진화시킨 형태입니다. 스프링의 3대 템플릿(JdbcTemplate, RestTemplate, TransactionTemplate)이 모두 이 방식을 씁니다.

특징

  • 상속(extends)을 쓰지 않습니다.
  • 변하는 부분(핵심 로직)을 인터페이스(Callback)로 만들어 파라미터로 넘깁니다. (Java 8 람다와 찰떡궁합)

코드 예시

Java
// 1. 콜백 인터페이스 (Functional Interface)
@FunctionalInterface
public interface FileCallback {
    String readFile(String path);
}

// 2. 템플릿 클래스 (상속 필요 없는 일반 클래스)
public class FileTemplate {
    public void process(String path, FileCallback callback) {
        System.out.println("1. 파일 열기: " + path); // 공통
        
        // 파라미터로 받은 로직 실행 (위임)
        String content = callback.readFile(path); 
        
        System.out.println("3. 결과 저장: " + content); // 공통
    }
}

// 3. 사용 (Client) - 클래스를 새로 만들 필요가 없음!
public class Client {
    public static void main(String[] args) {
        FileTemplate template = new FileTemplate();

        // 람다를 통해 로직을 주입 (이게 바로 콜백)
        template.process("data.json", path -> "JSON 데이터 파싱 완료");
        template.process("data.csv", path -> "CSV 데이터 파싱 완료");
    }
}

장단점

  • 장점:
    • 유연성: 별도의 클래스를 만들 필요 없이, 실행 시점에 람다로 로직을 바로 꽂아 넣을 수 있습니다.
    • 결합도 감소: 상속 관계가 없으므로 FileTemplate은 싱글톤 빈으로 등록해서 재사용하기 좋습니다.
  • 단점: 콜백 인터페이스를 정의하고 사용하는 코드가 처음에는 낯설 수 있습니다.

3. 실무 비교: 언제 무엇을 쓰는가?

구분 Classic Template Method (GoF) Template Callback (Spring)
구현 방식 상속 (Inheritance) 합성 (Composition) + 위임
변하는 부분 abstract method 오버라이딩 메서드 파라미터로 Interface 주입
클래스 수 로직마다 하위 클래스 필요 템플릿 클래스 하나로 끝 (익명함수 사용)
주요 사례 HttpServlet (doGet, doPost) JdbcTemplate, RedisTemplate

왜 스프링은 콜백 방식을 선호할까?

스프링은 **"상속보다는 합성을 사용하라"**는 객체지향 원칙을 철저히 따릅니다.

JdbcTemplate을 상속받아 MyUserDao를 만드는 것보다, JdbcTemplate을 빈(Bean)으로 주입받아(@Autowired) 사용하는 것이 훨씬 테스트하기 쉽고 유연하기 때문입니다.


요약: 핵심은 "누가 빈칸을 채우는가?"

  1. **상속(extends)**을 통해 자식이 빈칸을 채운다면? → 고전적 템플릿 메서드 패턴
  2. **파라미터(Lambda)**를 통해 외부에서 빈칸을 채운다면? → 템플릿 콜백 패턴

결론

단순히 메서드 몇 개를 강제하고 싶다면 고전적 방식(추상 클래스)도 나쁘지 않습니다. 하지만 비즈니스 로직의 유연함과 재사용성이 중요하다면, 스프링 스타일의 템플릿 콜백 패턴으로 구현하는 것을 강력히 추천합니다.