5주차 Unit 5.1 — 템플릿 메소드 패턴

Psj·2026년 5월 27일

F-lab

목록 보기
169/240

Unit 5.1 — 템플릿 메소드 패턴

F-LAB JAVA · 5주차 · Phase 5 · 디자인 패턴의 적용
🌱 Phase 5 시작 — Phase 4 리팩토링의 정체 밝히기


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • 템플릿 메소드 패턴 의 정의는?
  • 템플릿 메서드 + 추상 메서드 의 구조는?
  • "변하지 않는 흐름 + 변하는 부분" 과의 연결은?
  • Phase 4.3 의 코드가 이 패턴인 이유는?
  • 훅 메서드 (hook) 란?
  • Spring JdbcTemplate 의 "Template" 유래는?
  • 템플릿 메소드 패턴의 장점 은?
  • 람다/함수형으로 대체 가능한가?
  • 템플릿 메소드 패턴의 한계 는?

🎯 핵심 한 문장

템플릿 메소드 패턴은 슈퍼클래스에 변하지 않는 기본 흐름 (템플릿 메서드) 을 정의하고, 변하는 부분만 추상 메서드로 서브클래스가 구현하게 하는 패턴으로, Phase 4.3 의 추상 ShipmentDao 가 정확히 이 패턴이다.
구조는 템플릿 메서드 (변하지 않는 전체 흐름을 담은 부모의 구현 메서드) 와 추상 메서드 (변하는 부분을 서브클래스에 위임) 로 이루어진다.
Phase 4.3 에서 부모의 add() 가 "연결 → SQL → 해제" 흐름을 고정하고 getConnection() 만 추상으로 비워둔 것이, 바로 템플릿 메서드 (add) + 추상 메서드 (getConnection) 의 템플릿 메소드 패턴이다.
Spring 의 JdbcTemplate 이름에 "Template" 이 붙은 이유가 바로 이것 — 연결·실행·자원 해제의 반복 흐름을 템플릿으로 제공하고, 변하는 SQL·매핑만 콜백으로 받기 때문이다.
다만 상속 기반이라 단일 상속 제약과 강한 결합의 한계가 있으며, 변하는 부분이 하나뿐이면 람다 (함수형 인터페이스) 로 더 유연하게 대체할 수 있다.

비유 — 입사 지원서 양식

템플릿 메소드 패턴 = 지원서 양식:

양식 (템플릿 메서드 — 고정):
  1. 이름: ___
  2. 학력: ___
  3. 자기소개: ___ (빈칸)
  4. 제출

  - 양식 구조는 고정 (흐름)
  - 빈칸만 변함 (추상 메서드)

지원자 (서브클래스):
  - 김OO: 자기소개 = "성실한..."
  - 이OO: 자기소개 = "도전적인..."
  - 양식은 같고, 빈칸만 다름

JdbcTemplate:
  - 양식 = 연결/실행/해제 흐름
  - 빈칸 = SQL/매핑 (콜백)

람다 대체:
  - 빈칸이 하나면
  - 굳이 새 서브클래스 X
  - 빈칸만 함수로 전달

→ 템플릿 메소드 패턴 = 고정 흐름(템플릿) + 빈칸(추상), Phase 4.3 이 바로 이것.


🧭 9개 섹션 로드맵

1. 템플릿 메소드 패턴의 정의
2. 템플릿 메서드 + 추상 메서드
3. 변하지 않는 흐름과의 연결
4. Phase 4.3이 이 패턴
5. 훅 메서드
6. JdbcTemplate의 Template
7. 장점
8. 람다/함수형 대체
9. 면접 + 자기 점검

1️⃣ 템플릿 메소드 패턴의 정의

1.1 정의

템플릿 메소드 패턴:

  슈퍼클래스에 기본 흐름을 정의하고,
  변하는 부분만 서브클래스에서 구현.

  - 흐름: 부모 (템플릿)
  - 변동: 자식 (추상)

1.2 GoF 패턴

GoF 디자인 패턴:

  행위 패턴 (Behavioral):
    - 알고리즘 골격 정의
    - 일부 단계 서브클래스

  "알고리즘의 구조는 유지,
   일부 단계만 재정의"

1.3 의도

의도:

  - 공통 흐름 재사용
  - 변동 부분만 확장
  - 코드 중복 제거
  - 흐름 제어 (부모)

1.4 ILIC 의 맥락

// 템플릿 메소드 패턴
public abstract class ShipmentDao {
    
    // 템플릿 메서드 (변하지 않는 흐름)
    public final void add(Shipment shipment) throws Exception {
        Connection c = getConnection();   // 변하는 부분 (추상)
        PreparedStatement ps = c.prepareStatement(
            "insert into shipments(id, bl_no, weight) values(?, ?, ?)");
        ps.setLong(1, shipment.getId());
        ps.executeUpdate();
        ps.close();
        c.close();
        // 흐름은 고정
    }
    
    // 추상 메서드 (변하는 부분)
    protected abstract Connection getConnection() throws Exception;
}

1.5 자기 점검 답변

템플릿 메소드 패턴의 정의는?

:
1. 정의:

  • 부모: 흐름
  • 자식: 변동
  1. GoF:

    • 행위 패턴
    • 알고리즘 골격
  2. 의도:

    • 흐름 재사용
    • 변동 확장
  3. 구조:

    • 템플릿 + 추상

2️⃣ 템플릿 메서드 + 추상 메서드

2.1 두 요소

두 요소:

템플릿 메서드:
  - 변하지 않는 흐름
  - 부모에 구현
  - 추상 메서드 호출

추상 메서드:
  - 변하는 부분
  - 자식이 구현

2.2 템플릿 메서드

// 템플릿 메서드 — 흐름 정의
public final void process() {
    step1();           // 고정
    stepVariable();    // 변동 (추상)
    step3();           // 고정
}
// 흐름은 부모가 제어
// final 로 흐름 변경 방지 (선택)

2.3 추상 메서드

// 추상 메서드 — 변동 부분
protected abstract void stepVariable();

// 자식이 구현
class ConcreteA extends Parent {
    protected void stepVariable() {
        // A 방식
    }
}

2.4 구조 시각화

구조:

[부모]
  process() {          ← 템플릿 메서드
    step1();
    stepVariable();    ← 추상 호출
    step3();
  }
  abstract stepVariable();  ← 추상
        ↑
[자식] stepVariable() 구현

2.5 ILIC 의 맥락

public abstract class ShipmentDao {
    
    // 템플릿 메서드 (흐름)
    public final Shipment get(Long id) throws Exception {
        Connection c = getConnection();           // 추상 호출
        PreparedStatement ps = c.prepareStatement(
            "select * from shipments where id = ?");
        ps.setLong(1, id);
        ResultSet rs = ps.executeQuery();
        Shipment shipment = rs.next() ? mapRow(rs) : null;
        rs.close(); ps.close(); c.close();
        return shipment;
        // 흐름 고정
    }
    
    // 추상 메서드 (변동)
    protected abstract Connection getConnection() throws Exception;
    
    // 공통 헬퍼
    private Shipment mapRow(ResultSet rs) throws SQLException {
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        return s;
    }
}

// 자식 (변동 구현)
class MySqlShipmentDao extends ShipmentDao {
    protected Connection getConnection() throws Exception {
        return DriverManager.getConnection(
            "jdbc:mysql://localhost/ilic", "root", "password");
    }
}

2.6 자기 점검 답변

템플릿 메서드 + 추상 메서드의 구조는?

:
1. 템플릿 메서드:

  • 흐름 (부모)
  • 추상 호출
  1. 추상 메서드:

    • 변동 (자식)
  2. 구조:

    • 부모 흐름 + 추상
    • 자식 구현
  3. final:

    • 흐름 보호 (선택)

3️⃣ 변하지 않는 흐름과의 연결

3.1 연결

Phase 4 연결:

  "변하지 않는 흐름 + 변하는 부분"
    = 템플릿 메소드 패턴

  흐름 = 템플릿 메서드
  변동 = 추상 메서드

3.2 같은 개념

같은 개념, 다른 이름:

Phase 4.3:
  - 변하지 않는 흐름 (add)
  - 변하는 부분 (getConnection)

Phase 5.1:
  - 템플릿 메서드 (add)
  - 추상 메서드 (getConnection)

→ 같은 것

3.3 패턴으로 인식

패턴으로 인식:

  Phase 4.3 에서 한 일:
    - 직관적 리팩토링

  사실은:
    - 잘 알려진 패턴
    - 템플릿 메소드

→ 이름 붙이니 명확

3.4 ILIC 의 맥락

// Phase 4.3 (직관) = Phase 5.1 (패턴)

public abstract class ShipmentDao {
    // Phase 4.3: "변하지 않는 흐름"
    // Phase 5.1: "템플릿 메서드"
    public void add(Shipment s) throws Exception {
        Connection c = getConnection();
        // SQL 흐름
    }
    
    // Phase 4.3: "변하는 부분"
    // Phase 5.1: "추상 메서드"
    protected abstract Connection getConnection() throws Exception;
}
// 같은 코드, 패턴 이름 부여

3.5 자기 점검 답변

"변하지 않는 흐름 + 변하는 부분"과의 연결은?

:
1. 연결:

  • 흐름 = 템플릿 메서드
  • 변동 = 추상 메서드
  1. 같은 개념:

    • Phase 4.3 = Phase 5.1
  2. 패턴 인식:

    • 직관 → 패턴 이름
  3. 명확화:

    • 이름 부여

4️⃣ Phase 4.3이 이 패턴

4.1 정확히 일치

Phase 4.3 = 템플릿 메소드:

  추상 ShipmentDao:
    - add (템플릿 메서드)
    - getConnection (추상)

  서브클래스:
    - getConnection 구현

→ 정확히 템플릿 메소드 패턴

4.2 매핑

Phase 4.3템플릿 메소드 패턴
추상 ShipmentDaoAbstractClass
add (흐름)템플릿 메서드
getConnection (추상)추상 메서드 (primitive)
MySqlShipmentDaoConcreteClass

4.3 우리가 만든 것

우리가 만든 것:

  의도 X (패턴 이름 몰랐음)
  결과 O (템플릿 메소드)

  → 좋은 리팩토링은
  → 자연히 패턴으로

4.4 패턴의 가치

패턴의 가치:

  - 공통 언어 (의사소통)
  - 검증된 구조
  - 빠른 이해

  "템플릿 메소드 썼어요"
  → 즉시 이해

4.5 ILIC 의 맥락

// Phase 4.3 의 코드 = 템플릿 메소드 패턴

// AbstractClass (Phase 4.3 추상 ShipmentDao)
public abstract class ShipmentDao {
    // 템플릿 메서드 (Phase 4.3 의 add 흐름)
    public void add(Shipment s) throws Exception {
        Connection c = getConnection();
        // ... SQL 흐름 (고정)
    }
    // primitive operation (Phase 4.3 의 추상 getConnection)
    protected abstract Connection getConnection() throws Exception;
}

// ConcreteClass (Phase 4.3 의 MySqlShipmentDao)
public class MySqlShipmentDao extends ShipmentDao {
    protected Connection getConnection() throws Exception {
        return null;
    }
}
// → 우리가 만든 게 정확히 GoF 템플릿 메소드 패턴

4.6 자기 점검 답변

Phase 4.3의 코드가 이 패턴인 이유는?

:
1. 정확히 일치:

  • add (템플릿)
  • getConnection (추상)
  1. 매핑:

    • AbstractClass/ConcreteClass
  2. 우리가 만든 것:

    • 의도 X, 결과 O
  3. 가치:

    • 공통 언어

5️⃣ 훅 메서드

5.1 훅 메서드

훅 메서드 (Hook Method):

  서브클래스가 선택적으로 재정의:
    - 기본 구현 있음 (빈 or 기본)
    - 필요 시 오버라이드
    - 추상 X (선택적)

5.2 추상 vs 훅

추상 vs 훅:

추상 메서드:
  - 구현 강제 (필수)

훅 메서드:
  - 기본 구현
  - 선택적 재정의

5.3 예시

public abstract class ShipmentDao {
    
    public final void add(Shipment s) throws Exception {
        beforeAdd(s);      // 훅 (선택)
        Connection c = getConnection();   // 추상 (필수)
        // SQL...
        afterAdd(s);       // 훅 (선택)
    }
    
    protected abstract Connection getConnection() throws Exception;  // 필수
    
    // 훅 (기본 빈 구현)
    protected void beforeAdd(Shipment s) { }   // 선택적 재정의
    protected void afterAdd(Shipment s) { }    // 선택적 재정의
}

5.4 훅 활용

// 훅 재정의 (선택)
class AuditShipmentDao extends ShipmentDao {
    protected Connection getConnection() { return null; }   // 필수
    
    @Override
    protected void afterAdd(Shipment s) {   // 훅 재정의
        auditLog.record(s);   // 감사 로그
    }
}
// 흐름 변경 없이 확장점 제공

5.5 ILIC 의 맥락

public abstract class ShipmentDao {
    
    public final void add(Shipment shipment) throws Exception {
        validate(shipment);                    // 훅
        Connection c = getConnection();        // 추상
        // SQL...
        afterPersist(shipment);                // 훅
    }
    
    protected abstract Connection getConnection() throws Exception;
    
    // 훅 (기본 구현, 선택적 재정의)
    protected void validate(Shipment s) {
        // 기본 검증
    }
    protected void afterPersist(Shipment s) {
        // 기본: 아무것도 안 함
    }
}

// 특정 고객사 — 추가 검증 (훅)
class StrictShipmentDao extends ShipmentDao {
    protected Connection getConnection() { return null; }
    
    @Override
    protected void validate(Shipment s) {   // 훅 재정의
        super.validate(s);
        // 엄격한 추가 검증
    }
}

5.6 자기 점검 답변

훅 메서드 (hook) 란?

:
1. :

  • 선택적 재정의
  • 기본 구현
  1. 추상 vs 훅:

    • 추상: 필수
    • 훅: 선택
  2. 활용:

    • 확장점
  3. 효과:

    • 흐름 변경 없이 확장

6️⃣ JdbcTemplate의 Template

6.1 JdbcTemplate

Spring JdbcTemplate:

  JDBC 반복 코드를 템플릿으로:
    - 연결/실행/해제 (흐름)
    - SQL/매핑 (콜백)

  이름의 "Template" = 템플릿 메소드 (변형)

6.2 흐름 제공

JdbcTemplate 흐름:

  반복되는 JDBC 흐름:
    - 연결 획득
    - Statement 준비
    - 실행
    - 결과 처리
    - 자원 해제

  → JdbcTemplate 이 제공

6.3 콜백으로 변동

// JdbcTemplate 사용 (콜백)
jdbcTemplate.query(
    "select * from shipments where id = ?",   // SQL (변동)
    new Object[]{id},
    (rs, rowNum) -> {                          // RowMapper (변동)
        Shipment s = new Shipment();
        s.setId(rs.getLong("id"));
        return s;
    }
);
// 흐름은 JdbcTemplate, 변동(SQL/매핑)은 콜백

6.4 템플릿 메소드 변형

템플릿 메소드 변형:

전통 템플릿 메소드:
  - 상속 (서브클래스)

JdbcTemplate:
  - 콜백 (전략 패턴 가까움)
  - 합성

→ 같은 정신 (흐름 고정, 변동 주입)
→ 상속 대신 콜백

6.5 ILIC 의 맥락

// JdbcTemplate 으로 ShipmentDao (실무)
@Repository
public class ShipmentDao {
    private final JdbcTemplate jdbcTemplate;
    
    public ShipmentDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    public void add(Shipment shipment) {
        // 흐름은 JdbcTemplate, SQL 만 (변동)
        jdbcTemplate.update(
            "insert into shipments(id, bl_no, weight) values(?, ?, ?)",
            shipment.getId(), shipment.getBlNo(), shipment.getWeight());
        // 연결/실행/해제 자동 (템플릿)
    }
    
    public Shipment get(Long id) {
        return jdbcTemplate.queryForObject(
            "select * from shipments where id = ?",
            (rs, rowNum) -> {
                Shipment s = new Shipment();
                s.setId(rs.getLong("id"));
                return s;
            },
            id);
        // 반복 코드 사라짐 (템플릿이 처리)
    }
}

6.6 자기 점검 답변

Spring JdbcTemplate의 "Template" 유래는?

:
1. JdbcTemplate:

  • JDBC 반복 → 템플릿
  1. 흐름 제공:

    • 연결/실행/해제
  2. 콜백 변동:

    • SQL/매핑
  3. 변형:

    • 상속 대신 콜백

7️⃣ 장점

7.1 장점 정리

템플릿 메소드 장점:

1. 코드 재사용 (흐름)
2. 중복 제거
3. 흐름 제어 (부모)
4. 확장점 (추상/훅)
5. 일관성

7.2 흐름 재사용

흐름 재사용:

  공통 흐름:
    - 부모 한 곳
    - 모든 자식 공유

  → 중복 X

7.3 흐름 제어

흐름 제어 (할리우드 원칙):

  "Don't call us, we'll call you"
    - 부모가 흐름 제어
    - 자식 메서드를 부모가 호출

  → 제어가 부모 (IoC 향)

7.4 IoC 연결

IoC 연결:

  템플릿 메소드:
    - 부모가 자식 메서드 호출
    - 제어가 부모에게

  → 제어의 역전 맛보기
  → Phase 7 IoC 로 발전

7.5 ILIC 의 맥락

public abstract class ShipmentDao {
    
    // 흐름 제어 (부모가 자식 호출)
    public final void add(Shipment s) throws Exception {
        Connection c = getConnection();   // 부모가 자식 메서드 호출
        // 흐름은 부모가 결정
        // 자식은 getConnection 만 제공
    }
    
    protected abstract Connection getConnection() throws Exception;
    // 할리우드 원칙: "너를 부를게" (부모 → 자식)
    // → IoC 의 맛보기
}

7.6 자기 점검 답변

템플릿 메소드 패턴의 장점은?

:
1. 재사용:

  • 흐름 공유
  1. 중복 제거:

    • 부모 한 곳
  2. 흐름 제어:

    • 할리우드 원칙
  3. IoC 연결:

    • 제어 역전 맛보기

8️⃣ 람다/함수형 대체

8.1 람다 대체

람다 대체:

  변하는 부분이 하나면:
    - 새 서브클래스 X
    - 함수형 인터페이스 + 람다

  → 더 유연

8.2 함수형 인터페이스

// 함수형 인터페이스로 변동 표현
@FunctionalInterface
interface ConnectionSupplier {
    Connection get() throws Exception;
}

public class ShipmentDao {
    private final ConnectionSupplier connectionSupplier;
    
    public ShipmentDao(ConnectionSupplier supplier) {
        this.connectionSupplier = supplier;   // 함수 주입
    }
    
    public void add(Shipment s) throws Exception {
        Connection c = connectionSupplier.get();   // 함수 호출
        // 흐름
    }
}

8.3 람다로 주입

// 람다로 변동 주입 (서브클래스 X)
ShipmentDao mysqlDao = new ShipmentDao(() -> 
    DriverManager.getConnection(
        "jdbc:mysql://localhost/ilic", "root", "password"));

ShipmentDao oracleDao = new ShipmentDao(() -> 
    DriverManager.getConnection(
        "jdbc:oracle:thin:@localhost:1521:ilic", "ilic", "pass"));

// 서브클래스 없이 람다로 변동

8.4 상속 vs 람다

상속 vs 람다:

상속 (템플릿 메소드):
  - 변동 여럿
  - 상태 필요
  - 복잡한 변동

람다 (함수형):
  - 변동 하나
  - 무상태
  - 간단한 변동
  - 더 유연 (단일 상속 X)

8.5 ILIC 의 맥락

// 람다 기반 (현대적)
public class ShipmentDao {
    private final ConnectionSupplier connectionSupplier;
    
    public ShipmentDao(ConnectionSupplier supplier) {
        this.connectionSupplier = supplier;
    }
    
    public void add(Shipment s) throws Exception {
        try (Connection c = connectionSupplier.get();
             PreparedStatement ps = c.prepareStatement("insert ...")) {
            ps.executeUpdate();
        }
    }
    
    @FunctionalInterface
    interface ConnectionSupplier {
        Connection get() throws Exception;
    }
}

// 사용 (람다, 서브클래스 불필요)
var dao = new ShipmentDao(() -> 
    DriverManager.getConnection("jdbc:mysql://localhost/ilic", "root", "pass"));
// 단일 상속 제약 X, 유연
// → 사실상 전략 패턴 (Phase 6 으로 발전)

8.6 자기 점검 답변

람다/함수형으로 대체 가능한가?

:
1. 대체:

  • 변동 하나면 람다
  1. 함수형 인터페이스:

    • 변동 표현
  2. 람다 주입:

    • 서브클래스 X
  3. 상속 vs 람다:

    • 복잡: 상속
    • 간단: 람다 (유연)

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
템플릿 메소드?흐름(부모) + 변동(자식)
구조?템플릿 메서드 + 추상
Phase 4.3?정확히 이 패턴
훅 메서드?선택적 재정의
JdbcTemplate?JDBC 흐름 템플릿
장점?재사용, 흐름 제어
할리우드 원칙?부모가 자식 호출
람다 대체?변동 하나면 가능
한계?단일 상속, 강결합
IoC 연결?제어 역전 맛보기

9.2 자기 점검 체크리스트

정의

  • 흐름 + 변동

구조

  • 템플릿 + 추상

Phase 4.3

  • 이 패턴

  • 선택적

JdbcTemplate

  • Template 유래

장점

  • 재사용, 제어

람다

  • 대체

9.3 추가 심화 질문

Q1: 템플릿 메소드 vs 전략 패턴?

답:

  • 템플릿: 상속 (서브클래스)
  • 전략: 합성 (주입)
  • 템플릿: 흐름 고정, 일부 재정의
  • 전략: 알고리즘 통째 교체

Q2: final 템플릿 메서드?

답:

  • 흐름 변경 방지
  • 자식이 오버라이드 X
  • 흐름 보호
  • 권장 (흐름 일관성)

Q3: 템플릿 메소드의 단점?

답:

  • 단일 상속 제약
  • 강한 결합 (부모-자식)
  • 흐름 변경 어려움
  • 람다/전략으로 보완

Q4: Spring 의 다른 Template?

답:

  • JdbcTemplate, RestTemplate
  • JmsTemplate, TransactionTemplate
  • 모두 템플릿 메소드 정신
  • 반복 흐름 + 콜백

Q5: 콜백과 템플릿 메소드?

답:

  • 콜백: 함수 전달 (합성)
  • 템플릿: 서브클래스 (상속)
  • JdbcTemplate: 콜백 방식
  • 같은 정신, 다른 메커니즘

🎯 핵심 요약 — 3줄 정리

1. 템플릿 메소드 패턴

  • 슈퍼클래스에 흐름(템플릿 메서드), 변동만 추상 메서드
  • Phase 4.3 의 추상 ShipmentDao 가 정확히 이것

2. JdbcTemplate

  • "Template" = 템플릿 메소드 정신
  • JDBC 흐름 제공 + SQL/매핑 콜백

3. 대체와 한계

  • 변동 하나면 람다로 대체 (더 유연)
  • 상속 기반 → 단일 상속 한계 (Phase 5.3)

📚 다음으로...

Unit 5.2 — 팩토리 메소드 패턴

이번 Unit에서 템플릿 메소드를 봤다면, 다음은 팩토리 메소드 패턴.

  • 객체 생성을 서브클래스에 위임
  • getConnection 도 팩토리 메소드
  • 같은 코드가 두 패턴으로 해석
  • Spring BeanFactory 의 Factory

Phase 5 진행 상황

🌱 Phase 5 — 디자인 패턴의 적용
  ✅ Unit 5.1 템플릿 메소드 패턴 ← 여기
  ⏭ Unit 5.2 팩토리 메소드 패턴
  ⏭ Unit 5.3 두 패턴의 한계

5주차 누적 진행

✅ Part A — 동시성 마무리 (7 Unit)
🌱 Part B — 토비의 스프링
  ✅ Phase 3 — 전통 DAO (3 Unit)
  ✅ Phase 4 — 관심사의 분리 (3 Unit)
  🌱 Phase 5 — 디자인 패턴 (1/3 진행)

총: 14/26 Unit

🌱 Phase 5 시작 — 디자인 패턴

profile
Software Developer

0개의 댓글