// ❌ 나쁜 예: 중복 코드가 많은 Coffee와 Tea 클래스
public class Coffee {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void brewCoffeeGrinds() {
System.out.println("Dripping Coffee through filter");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
public void addSugarAndMilk() {
System.out.println("Adding Sugar and Milk");
}
}
public class Tea {
void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
// boilWater(), pourInCup() 메서드가 Coffee와 중복!
}
문제점:
Abstract Class를 사용하는 이유:
공통 구현 코드 공유
알고리즘 골격 보호
final로 선언하여 알고리즘 순서 고정부분적 구현 강제
// ✅ Template Method는 Abstract Class 사용
public abstract class CaffeineBeverage {
// final: 알고리즘 골격 보호
final void prepareRecipe() {
boilWater(); // 구현됨 (공통)
brew(); // 추상 메서드 (다름)
pourInCup(); // 구현됨 (공통)
addCondiments(); // 추상 메서드 (다름)
}
// 공통 구현
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
// 서브클래스가 구현해야 할 부분
abstract void brew();
abstract void addCondiments();
}
Interface가 적합하지 않은 이유:
AbstractClass (추상 클래스)
┌─────────────────────────────┐
│ final templateMethod() { │ ← 알고리즘 골격 (final)
│ concreteOperation1() │
│ primitiveOperation1() │
│ concreteOperation2() │
│ primitiveOperation2() │
│ hook() │
│ } │
│ │
│ concreteOperation1() { } │ ← 구현된 메서드
│ concreteOperation2() { } │
│ │
│ abstract primitiveOperation1() │ ← 추상 메서드
│ abstract primitiveOperation2() │
│ │
│ hook() { } │ ← 훅 메서드 (선택)
└─────────────────────────────┘
△
│ 상속
│
┌──────────┴──────────┐
│ │
ConcreteClass1 ConcreteClass2
구현1 구현2
핵심 요소:
// Template Method Pattern 적용
public abstract class CaffeineBeverage {
// 템플릿 메서드: 알고리즘의 골격 정의
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
// 추상 메서드: 서브클래스가 구현
abstract void brew();
abstract void addCondiments();
// 공통 메서드: 모든 서브클래스에서 동일
void boilWater() {
System.out.println("물 끓이는 중");
}
void pourInCup() {
System.out.println("컵에 따르는 중");
}
}
// Coffee 구현
public class Coffee extends CaffeineBeverage {
@Override
void brew() {
System.out.println("필터로 커피 우려내는 중");
}
@Override
void addCondiments() {
System.out.println("설탕과 우유 추가 중");
}
}
// Tea 구현
public class Tea extends CaffeineBeverage {
@Override
void brew() {
System.out.println("차를 우려내는 중");
}
@Override
void addCondiments() {
System.out.println("레몬 추가 중");
}
}
public class BeverageTest {
public static void main(String[] args) {
System.out.println("=== 커피 만들기 ===");
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
System.out.println("\n=== 차 만들기 ===");
CaffeineBeverage tea = new Tea();
tea.prepareRecipe();
}
}
=== 커피 만들기 ===
물 끓이는 중
필터로 커피 우려내는 중
컵에 따르는 중
설탕과 우유 추가 중
=== 차 만들기 ===
물 끓이는 중
차를 우려내는 중
컵에 따르는 중
레몬 추가 중
public abstract class CaffeineBeverage {
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
// Hook 메서드를 이용한 조건부 실행
if (customerWantsCondiments()) {
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("물 끓이는 중");
}
void pourInCup() {
System.out.println("컵에 따르는 중");
}
// Hook 메서드: 기본 구현 제공
boolean customerWantsCondiments() {
return true; // 기본값
}
}
import java.io.*;
public class CoffeeWithHook extends CaffeineBeverage {
@Override
void brew() {
System.out.println("필터로 커피 우려내는 중");
}
@Override
void addCondiments() {
System.out.println("설탕과 우유 추가 중");
}
// Hook 메서드 오버라이드
@Override
boolean customerWantsCondiments() {
String answer = getUserInput();
return answer.toLowerCase().startsWith("y");
}
private String getUserInput() {
String answer = null;
System.out.print("커피에 설탕과 우유를 추가하시겠습니까? (y/n): ");
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch (IOException e) {
System.err.println("입력 오류");
}
if (answer == null) {
return "no";
}
return answer;
}
}
"먼저 연락하지 마세요, 저희가 연락드리겠습니다!"
┌─────────────────────────────┐
│ CaffeineBeverage (고수준) │
│ - prepareRecipe() 제어 │ ← "우리가 호출할게요"
└──────────┬──────────────────┘
│ 호출
│
┌──────▼─────┐ ┌──────▼─────┐
│ Coffee │ │ Tea │
│ (저수준) │ │ (저수준) │
└────────────┘ └────────────┘
"호출 기다림" "호출 기다림"
Coffee와 Tea는:
public class MyFrame extends JFrame {
@Override
public void paint(Graphics g) {
// JFrame이 호출 → Hook처럼 동작
super.paint(g);
g.drawString("Template Method!", 50, 50);
}
}
public class MyApplet extends Applet {
@Override
public void init() {
// Applet이 초기화 시점에 호출
}
@Override
public void start() {
// Applet이 시작 시점에 호출
}
@Override
public void stop() {
// Applet이 중지 시점에 호출
}
@Override
public void destroy() {
// Applet이 종료 시점에 호출
}
}
| 비교 항목 | Template Method | Strategy |
|---|---|---|
| 메커니즘 | 상속 (Inheritance) | 위임 (Delegation) |
| 변경 범위 | 알고리즘의 일부 단계만 변경 | 전체 알고리즘 교체 가능 |
| 유연성 | 컴파일 타임에 결정 (정적) | 런타임에 변경 가능 (동적) |
| 결합도 | 부모-자식 강한 결합 | 느슨한 결합 |
| 사용 시기 | 알고리즘 골격 공유, 일부만 다를 때 | 알고리즘 전체를 교체해야 할 때 |
abstract class Game {
final void play() { // 알고리즘 골격 고정
initialize();
startPlay(); // 다름
endPlay(); // 다름
}
void initialize() {
System.out.println("게임 초기화");
}
abstract void startPlay();
abstract void endPlay();
}
class Game {
private GameStrategy strategy; // 알고리즘 전체를 교체
void setStrategy(GameStrategy strategy) {
this.strategy = strategy;
}
void play() {
strategy.execute(); // 전체 알고리즘 위임
}
}
| 요소 | 역할 | 특징 |
|---|---|---|
| AbstractClass | 알고리즘 골격 정의 | Template Method를 final로 선언 |
| Template Method | 알고리즘 단계 순서 정의 | 서브클래스가 변경 불가 |
| Concrete Methods | 공통 구현 제공 | 모든 서브클래스에서 재사용 |
| Abstract Methods | 서브클래스 구현 강제 | 각 서브클래스마다 다른 구현 |
| Hook Methods | 선택적 확장 지점 | 기본 구현 제공, 오버라이드 가능 |