"로그인 기능을 만들 때도, 게시판 기능을 만들 때도, 항상 시작할 때 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) 사용하는 것이 훨씬 테스트하기 쉽고 유연하기 때문입니다.
요약: 핵심은 "누가 빈칸을 채우는가?"
- **상속(extends)**을 통해 자식이 빈칸을 채운다면? → 고전적 템플릿 메서드 패턴
- **파라미터(Lambda)**를 통해 외부에서 빈칸을 채운다면? → 템플릿 콜백 패턴
결론
단순히 메서드 몇 개를 강제하고 싶다면 고전적 방식(추상 클래스)도 나쁘지 않습니다. 하지만 비즈니스 로직의 유연함과 재사용성이 중요하다면, 스프링 스타일의 템플릿 콜백 패턴으로 구현하는 것을 강력히 추천합니다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 이터레이터 패턴(Iterator Pattern) 완벽 정리 (0) | 2025.12.07 |
|---|---|
| 상태 패턴(State Pattern) 완벽 정리 (0) | 2025.12.07 |
| 빌더 패턴(Builder Pattern) 총정리 (0) | 2025.12.07 |
| 컴포지트 패턴(Composite Pattern) (0) | 2025.12.07 |
| 어댑터 패턴(Adapter Pattern) 완벽 정리 (1) | 2025.12.07 |