알고리즘의 뼈대는 상위가 고정하고, 일부 단계만 하위가 채우는 패턴.
abstract class DataMiner {
public final void mine(String path) { // final — 순서를 못 바꾸게 한다
String raw = openFile(path);
String data = extractData(raw); // 하위가 채운다
Object parsed = parseData(data); // 하위가 채운다
analyze(parsed);
closeFile();
}
protected abstract String extractData(String raw);
protected abstract Object parseData(String data);
protected void analyze(Object data) { ... } // 훅 — 필요하면 재정의
}
class PdfMiner extends DataMiner {
protected String extractData(String raw) { ... }
protected Object parseData(String d) { ... }
}
핵심은 제어의 방향
"부르지 마라, 우리가 부르겠다" (헐리우드 원칙)
- 일반 라이브러리 — 내가 라이브러리를 부른다
- 템플릿 메서드 — 프레임워크가 내 코드를 부른다
프레임워크와 라이브러리를 가르는 성질이 바로 이것이다
어디에 있나
- 프레임워크의 생명주기 콜백 (초기화 → 실행 → 정리)
- 테스트 프레임워크의 setUp / test / tearDown
- 추상 클래스로 제공되는 기반 클래스 대부분
Strategy와의 차이
- Template Method — 상속. 컴파일 시점에 고정된다. 알고리즘의 '일부' 를 바꾼다
- Strategy — 합성. 런타임에 교체된다. 알고리즘 '전체' 를 바꾼다
Template Method 는 뼈대를 강제할 수 있고, Strategy 는 유연하지만 뼈대를 강제하지 못한다
상속의 대가
-
하위 클래스가 상위의 구현에 묶인다 (상위를 바꾸면 전부 영향)
-
다중 상속이 안 되므로 다른 계층과 조합할 수 없다
-
상속 깊이가 깊어지면 흐름을 따라가기 어렵다
-
요즘은 같은 목적을 '함수를 인자로 받는' 방식으로 푸는 경우가 많다
- 뼈대 함수가 단계별 람다를 받는다
면접 함정
- ❌ "템플릿 메서드는 재정의하라고 있는 것" → 템플릿 메서드 자체는
final로 막고, 단계만 재정의하게 하는 것이 의도다. - ❌ "훅 메서드는 필수 구현" → 훅은 선택이다. 기본 구현을 두고 필요할 때만 바꾼다.
함수를 인자로 받는 형태로 바꾸기
// 상속 대신 람다로 단계를 주입한다
public <T> T withTransaction(Supplier<T> work) {
tx.begin();
try { T result = work.get(); tx.commit(); return result; }
catch (Exception e) { tx.rollback(); throw e; }
}
withTransaction(() -> repository.save(order));
- 상속 기반 — 컴파일 시점 고정 · 클래스가 늘어난다 · 뼈대를 강제할 수 있다
- 함수 주입 기반 — 런타임 조합 · 클래스가 안 는다 · 뼈대 강제는 약하다
단계가 하나뿐이면 함수 주입이 거의 항상 낫다 단계가 여럿이고 순서가 중요하면 상속 기반이 여전히 유효하다
훅을 설계할 때
protected void beforeSave() { } // 기본은 아무것도 안 한다
protected void afterSave() { }
// 하위 클래스가 필요한 것만 재정의한다
// 추상 메서드로 만들면 전부 구현해야 해서 부담이 된다
추상 메서드는 "반드시 채워야 하는 구멍", 훅은 "선택적 확장점" 으로 나눠 설계한다.