’‘‘주니어가 처음 ’‘‘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 분 안에 찾을 수 있는 곳’‘’‘’‘’‘’‘’‘:

  1. ’‘‘모든 컨트롤러에서 ’‘‘똑같이 반복하는 ’‘‘try-catch + logging’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’ → ’‘‘OncePerRequestFilter (템플릿) 또는 ’‘‘@RestControllerAdvice’‘’’* (AOP 데코레이터)’‘’’*
  2. ’‘‘여러 곳에서 ’‘‘같은 SQL/REST 호출 후 ’‘‘다른 변환’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’* → ’‘‘메소드에 ’‘‘*Function<T, R>’‘’‘’‘’’ 받기 (콜백)*
  3. ’‘‘같은 객체에 ’‘‘선택적으로 ’‘‘캐싱 / 로깅 / 검증 추가’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’* → ’‘‘데코레이터 (러시아 인형)’‘’‘’’
  4. ’‘‘프레임워크의 ’‘‘확장 지점’‘’‘’’ 이 ’‘‘필요할 때’‘’‘’’ → ’‘‘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 — *’‘‘비슷해 보이는 세 패턴’‘’’* 의 ’‘‘미묘한 ’‘‘경계’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’‘’’ (또 한 번의 ‘한 가족’ 시리즈)