템플릿·콜백·데코레이터 패턴 — *시니어가 *''*'주니어에게*''* 친절하게 풀어보는 Java/Spring 의 *''*'세 가지 변주*''*
’‘‘주니어가 처음 ’‘‘JdbcTemplate’’ 을 만났을 때 — ’‘’‘’
jdbcTemplate.query(sql, rowMapper)’‘’’ 의 ’‘‘두 인자 중 ’‘‘어느 게 ’‘‘템플릿’’ 이고 어느 게 ’‘‘콜백’’ 인지 ’‘‘한 번에 안 보인다’‘’‘. 그리고 *’‘‘2 년 뒤 ’‘‘Spring Security 필터 체인을 디버깅하다가 ’‘’HttpServletRequestWrapper* 를 만났을 때 — ’‘‘그게 ’‘‘데코레이터’‘’’* 라는 걸 ’‘‘배운 적은 있지만 ’‘‘자기 코드와 어떻게 연결되는지* 까지 가는 데 ’‘‘또 ’‘‘한 학기’‘’‘’‘’‘’‘’’* 가 걸린다. 이 ’‘‘세 패턴은 ’‘‘한 가족’‘’‘’‘’‘’‘’‘’‘’‘’’ — ’‘‘모두 ’‘‘Hollywood Principle (’‘‘Don’t call us, we’ll call you’‘’‘) 의 변주다’‘’‘’‘’‘’‘’‘. 시니어가 주니어에게 ’‘‘한 번에*** 풀어보는 글.
이 글은 ’‘‘Java/Spring 코드를 만지는 1-2 년차 주니어’‘’‘’‘’‘’‘’‘’‘’’* 가 ’‘‘세 패턴을 ’‘‘자기 손가락 끝에서 ’‘‘연결’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’‘** 시킬 수 있도록 ’‘‘짧은 코드 + 실전 인용’‘’‘’‘’‘’‘’‘’‘’’ 로 구성. ’‘‘ASCII 다이어그램 / Spring 의 실제 클래스 / 본인 코드의 ’‘‘*적용 가능 지점’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’ 까지.
1. ’‘‘한 가족인 이유 — ’‘‘Hollywood Principle’‘’‘’‘’‘’‘’‘’’
주니어가 쓰는 코드: "내가 라이브러리를 *''*'**호출*''*''*''* 한다."
시니어가 쓰는 코드: "라이브러리가 내 코드를 *''*'**호출*''*''*''* 한다."
↑
"Don't call us, we'll call you."
이 ’‘‘역방향 흐름’‘’‘’’ 이 ’‘‘세 패턴의 공통 본질’‘’‘’‘’‘’‘’‘*:
| 패턴 | 누가 누구를 호출하는가 |
|---|---|
| 템플릿 메소드 | 추상 클래스의 ’‘‘고정 메소드(템플릿) 가 ’‘‘서브클래스의 hook 을 호출’‘’‘’‘’‘’‘’‘’‘’‘’’ |
| 콜백 | 라이브러리의 ’‘‘고정 메소드가 ’‘‘클라이언트가 전달한 함수를 호출’‘’‘’‘’‘’‘’‘’‘’‘’’ |
| 데코레이터 | ’‘‘래퍼가 ’‘‘원본 호출을 ’‘‘*가로채서 *’‘‘추가 작업 후 위임’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’* |
= ’‘‘제어 흐름이 ’‘‘클라이언트 → 라이브러리 ’‘‘단방향이 아니라 ’‘‘상호 호출’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. *’‘‘프레임워크는 항상 이런 패턴을 쓴다’‘’‘’‘’‘’‘’‘’‘. ’‘‘Spring 도, Servlet 도, JDBC 도’‘’‘’‘’‘*.
2. ’‘‘템플릿 메소드 패턴 — ’‘‘‘뼈대를 잡고 ’‘‘빈칸만 채우게 한다’’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
2.1. 정의 — ’‘‘*고전적 GoF 정의’‘’‘’’*
’‘‘알고리즘의 ’‘‘뼈대’‘’’* 를 ’‘‘상위 클래스에 정의’‘’’* 하고, ’‘‘구체적 단계는 ’‘‘서브클래스에서 ’‘‘오버라이드’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’* 하게 한다’‘’‘. — *’‘‘*GoF, Design Patterns (1994)’‘’‘’’*
2.2. ’‘‘*가장 단순한 코드 예시’‘’‘’’*
// 추상 템플릿 — 알고리즘 *''*'전체 흐름*''*''* 을 *''*'고정*''*''*
abstract class HttpRequestProcessor {
// ★ 템플릿 메소드 — *''*'final 로 *''*'오버라이드 금지*''*''*''*''*''*''*
public final HttpResponse process(HttpRequest req) {
if (!authenticate(req)) {
return HttpResponse.unauthorized();
}
log(req);
var result = handle(req); // ← *''*'서브클래스 책임*''*''*
return wrap(result);
}
protected abstract HttpResponse handle(HttpRequest req); // hook
protected boolean authenticate(HttpRequest req) { // default
return req.hasHeader("Authorization");
}
private void log(HttpRequest req) { /* 공통 로깅 */ }
private HttpResponse wrap(HttpResponse r) { /* 공통 래핑 */ return r; }
}
// 서브클래스 — *''*'**빈칸*''*''* 만 채움
class UserController extends HttpRequestProcessor {
@Override
protected HttpResponse handle(HttpRequest req) {
// 사용자 핸들러
return HttpResponse.ok(...);
}
}
2.3. ’‘‘실전 — Spring 의 ’‘‘JdbcTemplate’‘’‘’‘’‘’‘’’*
JdbcTemplate.query(sql, rowMapper) 의 ’‘‘*내부’‘’‘’‘*:
1. Connection 획득 ← 공통 (인프라)
2. PreparedStatement 준비 ← 공통
3. SQL 실행 ← 공통
4. ResultSet → 객체 ← ★ rowMapper 가 *''*'**책임*''*''*''*
5. ResultSet close ← 공통
6. Connection close ← 공통
’‘‘6 단계 중 ’‘‘4 번만 ’‘‘호출자가 채우면 됨’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. *’‘‘나머지 5 단계는 ’‘‘JdbcTemplate 이 ’‘‘보장’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘*.
List<User> users = jdbc.query(
"SELECT id, name FROM users WHERE active = true",
(rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"))
);
여기서 ’‘‘람다’‘’‘’’ 가 ’‘‘RowMapper 의 콜백’‘’‘’‘’‘. → *’‘‘자연스럽게 ’‘‘3 절 (콜백) 로 연결’‘’‘’‘’‘’‘’‘’‘’‘’‘.
2.4. ’‘‘Spring 의 ’‘‘OncePerRequestFilter’‘’‘’‘’‘’‘’’*
public abstract class OncePerRequestFilter extends GenericFilterBean {
public final void doFilter(...) throws ... {
if (isAlreadyFiltered(request)) {
chain.doFilter(request, response);
return;
}
try {
request.setAttribute(FILTERED, Boolean.TRUE);
doFilterInternal(request, response, chain); // ← hook
} finally {
request.removeAttribute(FILTERED);
}
}
protected abstract void doFilterInternal(...);
}
= ’‘‘doFilter 가 ’‘‘final’‘’‘’’ + ’‘‘중복 호출 방지가 ’‘‘보장’‘’‘’‘’‘’‘’‘’‘’‘’‘. ’‘‘doFilterInternal 만 ’‘‘서브클래스가 채움’‘’‘’‘’‘’‘’‘’‘. ’‘‘Spring Security 의 ’‘‘모든 필터’‘’‘’’ 가 이 패턴.
2.5. ’‘‘*언제 쓰는가’‘’‘’’*
- ’‘‘알고리즘 흐름이 ’‘‘고정’‘’‘’‘’‘’’ + ’‘‘일부 단계만 ’‘‘*다양’‘’‘’‘’‘’‘’‘’‘’’
- ’‘‘보안·트랜잭션·로깅 같은 ’‘‘공통 부분이 ’‘‘*누락되면 안 되는 경우’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’
- ’‘‘프레임워크가 ’‘‘호출자의 코드를 ’‘‘*제어하고 싶을 때’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’
3. ’‘‘콜백 패턴 — ’‘‘‘함수를 인자로 ’‘‘전달’’‘’‘’‘’‘’‘’‘’‘’’*
3.1. ’‘‘템플릿과 ’‘‘거의 같은 의도, ’‘‘다른 구현’‘’‘’‘’‘’‘’‘’‘’’*
템플릿 = ’‘‘서브클래싱’‘’‘’’ (상속). 콜백 = ’‘‘객체 / 함수 전달’‘’‘’’ (위임).
같은 문제, ’‘‘다른 도구’‘’‘’‘’‘*.
// 콜백 인터페이스
interface RowMapper<T> {
T mapRow(ResultSet rs, int rowNum) throws SQLException;
}
// 콜백을 받는 *''*'**라이브러리*''*''*''*
class MyJdbc {
<T> List<T> query(String sql, RowMapper<T> mapper) {
// 1-3, 5-6 은 *''*'**MyJdbc 가 책임*''*''*''*
// 4 만 mapper 에 *''*'**위임*''*''*''*
ResultSet rs = ...;
List<T> out = new ArrayList<>();
int n = 0;
while (rs.next()) {
out.add(mapper.mapRow(rs, n++)); // ★ 콜백
}
return out;
}
}
3.2. ’‘‘람다 시대 — ’‘‘Java 8 이후 ’‘‘*훨씬 자연스러움’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
// Java 7 — 익명 클래스
jdbc.query(sql, new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
return new User(rs.getLong("id"), rs.getString("name"));
}
});
// Java 8+ — 람다 (같은 의도, 짧음)
jdbc.query(sql, (rs, rowNum) ->
new User(rs.getLong("id"), rs.getString("name"))
);
= ’‘‘람다 = 콜백의 ’‘‘아주 작은 형태’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. *’‘‘Java 8 이후 ’‘‘콜백 패턴이 ’‘‘*일상화’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘*.
3.3. ’‘‘Spring 의 ’‘‘TransactionTemplate’‘’‘’‘’‘’‘’’*
new TransactionTemplate(txManager).execute(status -> {
// 트랜잭션 시작/커밋/롤백은 *''*'**Spring 이 처리*''*''*''*
// 여기 람다는 *''*'**비즈니스 로직만***''*''*''*
repository.save(order);
return null;
});
= ’‘‘JdbcTemplate 와 ’‘‘완전히 같은 패턴’‘’‘’‘’‘’‘’‘’‘. ’‘‘프레임워크가 ’‘‘라이프사이클 보장’‘’’* + ’‘‘핵심 한 줄은 ’‘‘호출자가 채움’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘.
3.4. ’‘‘Strategy 패턴과의 ’‘‘차이’‘’‘’‘’‘’‘’‘’’
| 측면 | Strategy | Callback |
|---|---|---|
| 의도 | ’‘‘알고리즘 ’‘‘교체 가능’‘’‘’‘’‘’’ | ’‘‘한 시점’‘’‘’‘에 ’‘‘호출 위임’‘’‘’‘’‘’‘’’* |
| 생명 주기 | 객체에 ’‘‘*저장’‘’‘’’* (필드) | 한 호출에 ’‘‘*전달’‘’‘’’* (인자) |
| 형태 | ’‘‘*여러 메소드’‘’‘’’* 의 인터페이스 | ’‘‘*한 메소드’‘’‘’’* (보통 functional interface) |
Strategy 가 ’‘‘더 무거운 객체 단위 결정’‘’‘’‘’‘’‘’‘, **Callback 은 *’‘‘가벼운 한 시점의 위임**’‘’‘’‘’‘’‘’‘’‘.
3.5. ’‘‘*언제 쓰는가’‘’‘’’*
- ’‘‘한 메소드의 ’‘‘일부분만 ’‘‘바꾸고 싶을 때’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
- ’‘‘상속 계층을 ’‘‘더 만들고 싶지 않을 때’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
- ’‘‘람다로 ’‘‘한 줄에 표현 가능할 때’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
4. ’‘‘데코레이터 패턴 — ’‘‘‘원본을 ’‘‘*감싸서 *’‘‘기능을 ’‘‘덧붙인다’’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
4.1. ’‘‘가장 직관적 예 — ’‘‘Java I/O’‘’‘’‘’‘’‘’’*
InputStream in = new FileInputStream("data.txt"); // 원본
InputStream buffered = new BufferedInputStream(in); // ← 데코레이터
InputStream zipped = new GZIPInputStream(buffered); // ← 데코레이터
DataInputStream data = new DataInputStream(zipped); // ← 데코레이터
= ’‘‘4 단계 ’‘‘러시아 인형’‘’‘’‘’‘’‘’‘. 각 *’‘‘데코레이터’‘’’* 가 ’‘‘같은 인터페이스 (InputStream)’‘’‘’’ 를 ’‘‘구현하면서’‘’’* ’‘‘*래핑 + 추가 기능’‘’‘’‘*.
read() 호출 → DataInputStream → GZIPInputStream → BufferedInputStream → FileInputStream
↑
진짜 디스크 read
4.2. ’‘‘스프링의 ’‘‘HttpServletRequestWrapper’‘’‘’‘’‘’‘’’*
public class CachingRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public CachingRequestWrapper(HttpServletRequest delegate) throws IOException {
super(delegate);
this.body = StreamUtils.copyToByteArray(delegate.getInputStream());
}
@Override
public ServletInputStream getInputStream() {
return new CachedServletInputStream(body); // *''*'**캐싱된 body 반환*''*''*''*
}
}
= ’‘‘원본 request 를 그대로 두고 ’‘‘감싸서’‘’’* ’‘‘body 를 ’‘‘여러 번 읽을 수 있게’‘’‘’‘’‘’‘’‘. *’‘‘로깅·검증·서명 검증’‘’’* 같은 곳에서 ’‘‘필수’‘’‘’‘.
4.3. ’‘‘AOP — ’‘‘데코레이터의 ’‘‘*자동화’‘’‘’‘’‘’‘’‘’‘’‘’’*
@Transactional 이 어떻게 동작하는가?
@Service
class OrderService {
@Transactional
public Order place(...) { ... }
}
→ Spring 이 ’‘‘프록시 데코레이터’‘’‘’’ 를 ’‘‘자동 생성’‘’‘’‘:
class OrderService$$Proxy extends OrderService { // ← 자동 생성된 데코레이터
public Order place(...) {
TransactionStatus tx = txManager.getTransaction(...);
try {
Order result = super.place(...); // ← 원본 호출
txManager.commit(tx);
return result;
} catch (Exception e) {
txManager.rollback(tx);
throw e;
}
}
}
= ’‘‘AOP = 컴파일 타임 + 런타임에 ’‘‘데코레이터가 ’‘‘자동 적용’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. *’‘‘개발자가 ’‘‘원본 코드에 ’‘‘아무것도 안 적어도’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘*.
4.4. ’‘‘*언제 쓰는가’‘’‘’’*
- ’‘‘원본을 ’‘‘수정하지 않고’‘’‘’’ ’‘‘기능을 ’‘‘확장’‘’‘’‘’‘’‘’‘’‘’‘’’
- ’‘‘여러 기능을 ’‘‘조합’‘’‘’’ 가능 (러시아 인형)
- ’‘‘런타임에 ’‘‘선택적으로 ’‘‘*적용’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’
5. ’‘‘세 패턴이 ’‘‘한 코드에서 ’‘‘함께 사는 곳 — ’‘‘*Spring Security FilterChain’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’
HTTP request
↓
[OncePerRequestFilter (템플릿)] ← 1단계: 템플릿
↓ doFilterInternal()
[AuthenticationFilter (데코레이터)] ← 2단계: 데코레이터
↓ chain.doFilter(req, res)
[AuthorizationFilter (데코레이터)] ← 3단계: 데코레이터
↓ chain.doFilter(req, res)
[DispatcherServlet] ← 4단계: 진짜 핸들러
↓
[Controller method]
↓ JdbcTemplate.query(sql, rowMapper) ← 5단계: 콜백
한 흐름 안에서 ’‘‘세 패턴이 모두’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. ’‘‘Spring 에 능숙하다 = ’‘‘이 세 패턴의 ’‘‘조합 패턴 (composition pattern) 을 ’‘‘자유롭게 ’‘‘읽고 쓴다**’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘*.
6. ’‘‘주니어가 ’‘‘자주 헷갈리는 ’‘‘4 가지’‘’‘’‘’‘’‘’‘’’
6.1. ’‘‘‘템플릿이랑 콜백 ’‘‘언제 다른가?’’‘’‘’‘’‘’‘’’*
같은 의도 (Hollywood Principle), ’‘‘다른 ’‘‘메커니즘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘*.
- ’‘‘여러 메소드가 ’‘‘같이 변하면’‘’‘’‘’‘’‘’‘’‘’’* → 템플릿 (서브클래싱)
- ’‘‘한 시점만 변하면’‘’‘’’ → **콜백 (함수 전달)
6.2. ’‘‘‘데코레이터랑 상속 ’‘‘뭐가 다른가?’’‘’‘’‘’‘’‘’’*
상속 = ’‘‘컴파일 타임’‘’‘’’ 에 ’‘‘고정’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. 데코레이터 = *’‘*‘런타임’‘’‘’’* 에 ’‘‘조합 가능’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘*.
// 상속 — 1 종류만
class GzippedBufferedFileInput extends FileInput { ... }
// 데코레이터 — 조합 자유
new GZIPInputStream(new BufferedInputStream(new FileInputStream(...)));
new BufferedInputStream(new GZIPInputStream(new FileInputStream(...)));
6.3. ’‘‘‘AOP 가 ’‘‘데코레이터**’‘’‘’’ 라고? 인터페이스도 안 만들었는데?’’‘’‘’’
→ ’‘‘CGLIB 가 ’‘‘바이트코드 차원에서 ’‘‘서브클래스* 를 ’‘‘런타임 생성’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. *’‘‘인터페이스 없어도 ’‘‘데코레이터 가능’‘’‘’‘’‘’‘’‘’‘’‘. 단 *’‘‘final 클래스/메소드는 ’‘‘*프록시 불가’‘’‘’‘’‘’‘’‘’‘*.
6.4. ’‘‘‘JdbcTemplate 의 query 가 ’‘‘템플릿* 이라고? ’‘‘Java 클래스도 ’‘‘*JdbcTemplate’‘’’ 인데?’’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
→ ’‘‘Spring 의 명명이 ’‘‘‘템플릿 메소드 패턴’’‘’’* 의 ’‘‘Template’‘’‘’’ 에서 옴’‘’‘. *’‘‘JdbcTemplate 자체가 ’‘‘템플릿 메소드 ’‘‘객체화’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. *’‘‘RowMapper 는 ’‘‘그 안의 ’‘‘콜백’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. *’‘‘Spring 의 모든 ~Template 클래스 (JdbcTemplate, RestTemplate, JmsTemplate, RedisTemplate, …) 가 ’‘‘*같은 패턴’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘*.
7. ’‘‘실전 적용 가이드 — ’‘‘‘언제 어떤 걸 쓸까’’‘’‘’‘’‘’‘’’*
"공통 흐름을 만들고 *''*'**일부만 *''*'바꾸게 하고 싶다"
├─ 변하는 부분이 *''*'**여러 메소드*''*''* → 템플릿 (추상 클래스)
├─ 변하는 부분이 *''*'**한 시점*''*''* → 콜백 (함수형 인터페이스 + 람다)
└─ 변하는 부분이 *''*'**선택적 + 조합 가능*''*''* → 데코레이터 (래퍼)
"기존 코드를 *''*'**건드리지 않고*''*''* 기능 추가"
└─ 데코레이터 (또는 AOP)
"프레임워크의 *''*'**확장 지점*''*''* 을 만들고 싶다"
├─ 강제 보호 + 단계 고정 → 템플릿
└─ 한 시점만 위임 → 콜백
8. ’‘‘AI 시대에 패턴이 ’‘‘‘더 중요해지는’ 이유’‘’‘’‘’‘’‘’’*
LLM 이 ’‘‘1 차 코드 생성’‘’‘’’ 을 잘 한다. ’‘‘그러나 ’‘‘구조를 ’‘‘‘어디서 어떻게 자를지’’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’ 는 ’‘‘*여전히 사람의 일’‘’‘’‘’‘’‘*.
- ’‘‘AI 가 생성한 코드’‘’‘’’ 가 ’‘‘템플릿/콜백/데코레이터를 ’‘‘이해하지 못하면’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’ → ’‘‘경계 위반 + 중복 + 강결합’‘’‘’‘’‘’’
- ’‘‘시니어가 ’‘‘‘템플릿 자리’’‘’’* / ’‘‘‘콜백 자리’’‘’’* / ’‘‘‘데코레이터 자리’’‘’’* 를 ’‘‘미리 잡아두면’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’ → ’‘‘AI 가 ’‘‘그 안에서만 ’‘‘작동**’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
= ’‘‘패턴 = AI 시대의 ’‘‘가드레일’‘’‘’‘’‘’‘’‘’‘’‘’‘.
9. ’‘‘실전 사례 — ’‘‘본인 코드에서 ’‘‘적용 가능한 곳 찾기’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
지금 본인 프로젝트에서 ’‘‘*5 분 안에 찾을 수 있는 곳’‘’‘’‘’‘’‘’‘:
- ’‘‘모든 컨트롤러에서 ’‘‘똑같이 반복하는 ’‘‘try-catch + logging’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’ → ’‘‘OncePerRequestFilter (템플릿) 또는 ’‘‘
@RestControllerAdvice’‘’’* (AOP 데코레이터)’‘’’* - ’‘‘여러 곳에서 ’‘‘같은 SQL/REST 호출 후 ’‘‘다른 변환’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’* → ’‘‘메소드에 ’‘‘*Function<T, R>’‘’‘’‘’’ 받기 (콜백)*
- ’‘‘같은 객체에 ’‘‘선택적으로 ’‘‘캐싱 / 로깅 / 검증 추가’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’* → ’‘‘데코레이터 (러시아 인형)’‘’‘’’
- ’‘‘프레임워크의 ’‘‘확장 지점’‘’‘’’ 이 ’‘‘필요할 때’‘’‘’’ → ’‘‘
abstract class + final 템플릿 메소드’‘’‘’‘’’*
10. ’‘‘결론 — ’‘‘‘한 가족, 세 얼굴’’‘’‘’‘’‘’‘’’*
세 패턴을 ’‘‘한 줄로 압축’‘’‘’‘’‘’‘’‘’‘’‘’‘:
- ’‘‘템플릿’‘’‘’’ — ’‘‘‘뼈대를 만들고 ’‘‘빈칸만 채우게 해라’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’
- ’‘‘콜백’‘’‘’’ — ’‘‘‘한 시점만 ’‘‘함수로 받아라’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
- ’‘‘데코레이터’‘’‘’’ — ’‘‘‘기존을 건드리지 말고 ’‘‘감싸라’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
세 패턴 ’‘‘모두 ’‘‘Hollywood Principle’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. *’‘‘‘Don’t call us, we’ll call you’’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘*.
’‘‘‘좋은 시니어는 ’‘‘‘템플릿 메소드 패턴이 뭡니까?’ 라는 질문에 ’‘‘JdbcTemplate 의 query 메소드 한 줄을 보여준다’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. *’‘‘‘좋은 주니어는 ’‘‘그 한 줄에서 ’‘‘‘아, 이게 그거구나’’‘’‘’’ 를 ’‘‘알아챈다’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘.
’‘‘패턴은 ’‘‘암기가 아니라 ’‘‘‘알아챔’’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. ’‘‘암기는 ’‘‘ChatGPT 도 잘 한다’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. ’‘‘알아챔은 ’‘‘‘그 패턴이 살고 있는 코드* 를 ’‘‘한 번 ’‘‘손가락 끝까지 ’‘‘쫓아본 사람’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’* 에게서만 나온다’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘*.
→ ’‘‘오늘 ’‘‘JdbcTemplate / RestTemplate / OncePerRequestFilter / HttpServletRequestWrapper’‘’‘’‘’‘’‘’’* 중 ’‘‘하나만 ’‘‘소스 코드까지 ’‘‘열어 보세요’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘. *’‘‘한 번에 ’‘‘시니어와 같은 풍경’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’ 이 보일 거예요.
더 읽을 거리
- Design Patterns: Elements of Reusable Object-Oriented Software — GoF (1994). ’‘‘여전히 ’‘‘현대 표준**’‘’‘’‘’‘’‘’‘’’
- Head First Design Patterns — Eric Freeman (개정판 2020). ’‘‘주니어용 가장 친절’‘’‘’‘’‘’‘’‘’’
- Effective Java — Joshua Bloch. ’‘‘*Item 21-23 (인터페이스 vs 추상 클래스), Item 42-44 (람다와 메소드 참조)’‘’’
- Spring Framework Documentation — ’‘‘*
JdbcTemplate,RestTemplate,OncePerRequestFilter’‘’‘’‘’‘’‘’‘’’* 소스 코드 직접 읽기 - Spring AOP Reference — ’‘‘데코레이터의 ’‘‘자동화 메커니즘**’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’*
- Refactoring — Martin Fowler. ’‘‘‘Replace Conditional with Polymorphism’, ‘Extract Method’’‘’’* 가 ’‘‘세 패턴의 ’‘‘시작점’‘’’*
다음 글 예고: Strategy / Adapter / Facade — *’‘‘비슷해 보이는 세 패턴’‘’’* 의 ’‘‘미묘한 ’‘‘경계’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’ (또 한 번의 ‘한 가족’ 시리즈)