F-LAB JAVA · 5주차 · Phase 5 · 디자인 패턴의 적용
🌱 Phase 5 시작 — Phase 4 리팩토링의 정체 밝히기
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
템플릿 메소드 패턴은 슈퍼클래스에 변하지 않는 기본 흐름 (템플릿 메서드) 을 정의하고, 변하는 부분만 추상 메서드로 서브클래스가 구현하게 하는 패턴으로, 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 이 바로 이것.
1. 템플릿 메소드 패턴의 정의
2. 템플릿 메서드 + 추상 메서드
3. 변하지 않는 흐름과의 연결
4. Phase 4.3이 이 패턴
5. 훅 메서드
6. JdbcTemplate의 Template
7. 장점
8. 람다/함수형 대체
9. 면접 + 자기 점검
템플릿 메소드 패턴:
슈퍼클래스에 기본 흐름을 정의하고,
변하는 부분만 서브클래스에서 구현.
- 흐름: 부모 (템플릿)
- 변동: 자식 (추상)
GoF 디자인 패턴:
행위 패턴 (Behavioral):
- 알고리즘 골격 정의
- 일부 단계 서브클래스
"알고리즘의 구조는 유지,
일부 단계만 재정의"
의도:
- 공통 흐름 재사용
- 변동 부분만 확장
- 코드 중복 제거
- 흐름 제어 (부모)
// 템플릿 메소드 패턴
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. 정의:
GoF:
의도:
구조:
두 요소:
템플릿 메서드:
- 변하지 않는 흐름
- 부모에 구현
- 추상 메서드 호출
추상 메서드:
- 변하는 부분
- 자식이 구현
// 템플릿 메서드 — 흐름 정의
public final void process() {
step1(); // 고정
stepVariable(); // 변동 (추상)
step3(); // 고정
}
// 흐름은 부모가 제어
// final 로 흐름 변경 방지 (선택)
// 추상 메서드 — 변동 부분
protected abstract void stepVariable();
// 자식이 구현
class ConcreteA extends Parent {
protected void stepVariable() {
// A 방식
}
}
구조:
[부모]
process() { ← 템플릿 메서드
step1();
stepVariable(); ← 추상 호출
step3();
}
abstract stepVariable(); ← 추상
↑
[자식] stepVariable() 구현
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");
}
}
템플릿 메서드 + 추상 메서드의 구조는?
답:
1. 템플릿 메서드:
추상 메서드:
구조:
final:
Phase 4 연결:
"변하지 않는 흐름 + 변하는 부분"
= 템플릿 메소드 패턴
흐름 = 템플릿 메서드
변동 = 추상 메서드
같은 개념, 다른 이름:
Phase 4.3:
- 변하지 않는 흐름 (add)
- 변하는 부분 (getConnection)
Phase 5.1:
- 템플릿 메서드 (add)
- 추상 메서드 (getConnection)
→ 같은 것
패턴으로 인식:
Phase 4.3 에서 한 일:
- 직관적 리팩토링
사실은:
- 잘 알려진 패턴
- 템플릿 메소드
→ 이름 붙이니 명확
// 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;
}
// 같은 코드, 패턴 이름 부여
"변하지 않는 흐름 + 변하는 부분"과의 연결은?
답:
1. 연결:
같은 개념:
패턴 인식:
명확화:
Phase 4.3 = 템플릿 메소드:
추상 ShipmentDao:
- add (템플릿 메서드)
- getConnection (추상)
서브클래스:
- getConnection 구현
→ 정확히 템플릿 메소드 패턴
| Phase 4.3 | 템플릿 메소드 패턴 |
|---|---|
| 추상 ShipmentDao | AbstractClass |
| add (흐름) | 템플릿 메서드 |
| getConnection (추상) | 추상 메서드 (primitive) |
| MySqlShipmentDao | ConcreteClass |
우리가 만든 것:
의도 X (패턴 이름 몰랐음)
결과 O (템플릿 메소드)
→ 좋은 리팩토링은
→ 자연히 패턴으로
패턴의 가치:
- 공통 언어 (의사소통)
- 검증된 구조
- 빠른 이해
"템플릿 메소드 썼어요"
→ 즉시 이해
// 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 템플릿 메소드 패턴
Phase 4.3의 코드가 이 패턴인 이유는?
답:
1. 정확히 일치:
매핑:
우리가 만든 것:
가치:
훅 메서드 (Hook Method):
서브클래스가 선택적으로 재정의:
- 기본 구현 있음 (빈 or 기본)
- 필요 시 오버라이드
- 추상 X (선택적)
추상 vs 훅:
추상 메서드:
- 구현 강제 (필수)
훅 메서드:
- 기본 구현
- 선택적 재정의
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) { } // 선택적 재정의
}
// 훅 재정의 (선택)
class AuditShipmentDao extends ShipmentDao {
protected Connection getConnection() { return null; } // 필수
@Override
protected void afterAdd(Shipment s) { // 훅 재정의
auditLog.record(s); // 감사 로그
}
}
// 흐름 변경 없이 확장점 제공
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);
// 엄격한 추가 검증
}
}
훅 메서드 (hook) 란?
답:
1. 훅:
추상 vs 훅:
활용:
효과:
Spring JdbcTemplate:
JDBC 반복 코드를 템플릿으로:
- 연결/실행/해제 (흐름)
- SQL/매핑 (콜백)
이름의 "Template" = 템플릿 메소드 (변형)
JdbcTemplate 흐름:
반복되는 JDBC 흐름:
- 연결 획득
- Statement 준비
- 실행
- 결과 처리
- 자원 해제
→ JdbcTemplate 이 제공
// 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/매핑)은 콜백
템플릿 메소드 변형:
전통 템플릿 메소드:
- 상속 (서브클래스)
JdbcTemplate:
- 콜백 (전략 패턴 가까움)
- 합성
→ 같은 정신 (흐름 고정, 변동 주입)
→ 상속 대신 콜백
// 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);
// 반복 코드 사라짐 (템플릿이 처리)
}
}
Spring JdbcTemplate의 "Template" 유래는?
답:
1. JdbcTemplate:
흐름 제공:
콜백 변동:
변형:
템플릿 메소드 장점:
1. 코드 재사용 (흐름)
2. 중복 제거
3. 흐름 제어 (부모)
4. 확장점 (추상/훅)
5. 일관성
흐름 재사용:
공통 흐름:
- 부모 한 곳
- 모든 자식 공유
→ 중복 X
흐름 제어 (할리우드 원칙):
"Don't call us, we'll call you"
- 부모가 흐름 제어
- 자식 메서드를 부모가 호출
→ 제어가 부모 (IoC 향)
IoC 연결:
템플릿 메소드:
- 부모가 자식 메서드 호출
- 제어가 부모에게
→ 제어의 역전 맛보기
→ Phase 7 IoC 로 발전
public abstract class ShipmentDao {
// 흐름 제어 (부모가 자식 호출)
public final void add(Shipment s) throws Exception {
Connection c = getConnection(); // 부모가 자식 메서드 호출
// 흐름은 부모가 결정
// 자식은 getConnection 만 제공
}
protected abstract Connection getConnection() throws Exception;
// 할리우드 원칙: "너를 부를게" (부모 → 자식)
// → IoC 의 맛보기
}
템플릿 메소드 패턴의 장점은?
답:
1. 재사용:
중복 제거:
흐름 제어:
IoC 연결:
람다 대체:
변하는 부분이 하나면:
- 새 서브클래스 X
- 함수형 인터페이스 + 람다
→ 더 유연
// 함수형 인터페이스로 변동 표현
@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(); // 함수 호출
// 흐름
}
}
// 람다로 변동 주입 (서브클래스 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"));
// 서브클래스 없이 람다로 변동
상속 vs 람다:
상속 (템플릿 메소드):
- 변동 여럿
- 상태 필요
- 복잡한 변동
람다 (함수형):
- 변동 하나
- 무상태
- 간단한 변동
- 더 유연 (단일 상속 X)
// 람다 기반 (현대적)
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 으로 발전)
람다/함수형으로 대체 가능한가?
답:
1. 대체:
함수형 인터페이스:
람다 주입:
상속 vs 람다:
| Q | 핵심 답변 |
|---|---|
| 템플릿 메소드? | 흐름(부모) + 변동(자식) |
| 구조? | 템플릿 메서드 + 추상 |
| Phase 4.3? | 정확히 이 패턴 |
| 훅 메서드? | 선택적 재정의 |
| JdbcTemplate? | JDBC 흐름 템플릿 |
| 장점? | 재사용, 흐름 제어 |
| 할리우드 원칙? | 부모가 자식 호출 |
| 람다 대체? | 변동 하나면 가능 |
| 한계? | 단일 상속, 강결합 |
| IoC 연결? | 제어 역전 맛보기 |
답:
답:
답:
답:
답:
1. 템플릿 메소드 패턴
2. JdbcTemplate
3. 대체와 한계
이번 Unit에서 템플릿 메소드를 봤다면, 다음은 팩토리 메소드 패턴.
🌱 Phase 5 — 디자인 패턴의 적용
✅ Unit 5.1 템플릿 메소드 패턴 ← 여기
⏭ Unit 5.2 팩토리 메소드 패턴
⏭ Unit 5.3 두 패턴의 한계
✅ Part A — 동시성 마무리 (7 Unit)
🌱 Part B — 토비의 스프링
✅ Phase 3 — 전통 DAO (3 Unit)
✅ Phase 4 — 관심사의 분리 (3 Unit)
🌱 Phase 5 — 디자인 패턴 (1/3 진행)
총: 14/26 Unit
🌱 Phase 5 시작 — 디자인 패턴