GitHub

https://github.com/Choidongjun0830

공부

템플릿 메서드 패턴 vs 전략 패턴

gogi masidda 2025. 3. 16. 23:56

이번에 shouldDrawMore() 메서드를 효율적으로 구현하는 방법을 고민하면서 템플릿 메서드 패턴과 전략 패턴을 학습했다. 두 패턴 모두 공통된 알고리즘을 효과적으로 관리하고 확장성을 높이는 방법이지만, 사용하는 방식이 다르다.

 

템플릿 메서드 패턴 (Template Method Pattern)

 

개념

 

템플릿 메서드 패턴은 상속을 활용하여 알고리즘의 공통 구조를 상위 클래스에서 정의하고, 하위 클래스에서 세부 동작을 구현하는 방식이다.

 

구조

추상 클래스 (부모)

알고리즘의 기본 흐름을 정의하는 템플릿 메서드를 제공

각 단계에서 호출될 추상 메서드를 선언

구현 클래스 (자식)

부모가 정의한 추상 메서드를 구현하여 세부 동작을 정의

Hook 메서드

템플릿 메서드의 특정 조건을 변경할 수 있도록 하는 선택적 메서드

if(훅 메서드), while(훅 메서드) 형태로 사용 가능

 

장점

코드 중복 제거

알고리즘 구조를 수정하지 않고 기능 확장 가능

핵심 로직이 한 곳에서 관리되므로 유지보수가 용이

 

단점

부모 클래스의 수정이 하위 클래스에 큰 영향을 줌

하위 클래스의 코드만으로 전체 동작을 이해하기 어려움

 

적용 예시: Dealer와 Player의 shouldDrawMore() 구현

 

Dealer와 Player가 shouldDrawMore()을 다르게 구현해야 한다면,

템플릿 메서드 패턴을 활용하여 공통 로직을 GameParticipant 클래스에 두고, 차이점만 하위 클래스에서 구현할 수 있다.

abstract class Participant {
    // 템플릿 메서드: 기본 흐름을 정의
    public final boolean shouldDrawMore() {
        return hasLessThanLimit() && additionalCondition();
    }

    protected abstract boolean hasLessThanLimit(); // 기본 조건
    protected boolean additionalCondition() { return true; } // Hook 메서드 (필요 시 재정의)
}

class Dealer extends Participant {
    @Override
    protected boolean hasLessThanLimit() {
        return getTotalScore() < 17; // 딜러는 17점 미만이면 카드를 뽑음
    }
}

class Player extends Participant {
    @Override
    protected boolean hasLessThanLimit() {
        return getTotalScore() < 21; // 플레이어는 21점 미만이면 뽑을 수 있음
    }

    @Override
    protected boolean additionalCondition() {
        return wantsToDraw(); // 플레이어는 추가적인 의사 결정이 가능
    }
}

 

요점

shouldDrawMore()기본 흐름을 GameParticipant에서 정의

Dealer와 Player는 자신의 규칙에 맞게 hasLessThanLimit()과 additionalCondition()을 구현

Hook 메서드(additionalCondition())를 활용하여 Player만 추가 조건을 적용 가능

 


전략 패턴 (Strategy Pattern)

 

개념

 

전략 패턴은 행동(알고리즘)을 독립적인 객체(Strategy)로 분리하여, 필요할 때 교체할 수 있도록 하는 방식이다.

 

구조

Strategy 인터페이스: 여러 알고리즘을 동일한 방식으로 호출할 수 있도록 정의

Concrete Strategy (구체적인 전략): Strategy 인터페이스를 구현하여 서로 다른 알고리즘 제공

Context (컨텍스트, 실행 주체): 어떤 전략을 사용할지 결정하지 않으며, 외부에서 주입받아 실행

 

장점

코드 확장성과 유연성 증가

실행 중에 전략을 변경할 수 있어 동적인 동작이 가능

코드 중복 제거

 

단점

클라이언트가 직접 전략을 선택해야 함

전략이 많아지면 관리가 어려워질 수 있음

 

적용 예시: shouldDrawMore()를 전략 패턴으로 구현

 

Dealer와 Player의 shouldDrawMore() 조건이 다르므로, 전략 패턴을 활용하여 각각의 규칙을 별도의 전략 객체로 분리할 수 있다.

// 1️⃣ 전략 인터페이스
interface DrawingStrategy {
    boolean shouldDrawMore(int score);
}

// 2️⃣ 구체적인 전략 (Dealer와 Player 각각의 규칙)
class DealerDrawingStrategy implements DrawingStrategy {
    @Override
    public boolean shouldDrawMore(int score) {
        return score < 17; // 딜러는 17점 미만이면 카드를 뽑음
    }
}

class PlayerDrawingStrategy implements DrawingStrategy {
    @Override
    public boolean shouldDrawMore(int score) {
        return score < 21 && wantsToDraw(); // 플레이어는 21점 미만 + 추가 선택 가능
    }

    private boolean wantsToDraw() {
        // 플레이어의 추가 카드 선택 로직 (예: 사용자 입력)
        return true; 
    }
}

// 3️⃣ GameParticipant가 전략을 사용
class GameParticipant {
    private final DrawingStrategy drawingStrategy;

    public GameParticipant(DrawingStrategy drawingStrategy) {
        this.drawingStrategy = drawingStrategy;
    }

    public boolean shouldDrawMore(int score) {
        return drawingStrategy.shouldDrawMore(score);
    }
}

 

요점

shouldDrawMore() 로직을 DrawingStrategy 인터페이스로 분리

Dealer와 Player는 각자의 전략을 구현한 클래스를 사용

GameParticipant전략이 무엇인지 신경 쓰지 않고 주어진 전략을 실행


shouldDrawMore()에 어떤 패턴을 적용할까?

 

이번 고민을 통해 “상속을 활용한 템플릿 메서드 패턴”과 “합성을 활용한 전략 패턴”의 차이점을 명확하게 이해할 수 있었다.

템플릿 메서드 패턴: shouldDrawMore()의 기본 흐름이 일정하고, 하위 클래스가 작은 차이를 구현해야 할 때 적합.

전략 패턴: shouldDrawMore()의 로직이 자주 변경되거나 실행 중에 교체해야 한다면 더 유리.

 

내 경우, shouldDrawMore()의 로직은 Dealer와 Player가 고정된 규칙을 따르는 상황이므로, 템플릿 메서드 패턴이 더 적절해 보인다. 하지만 만약 규칙이 동적으로 바뀌어야 한다면 전략 패턴을 고려할 수도 있다.

728x90

'공부' 카테고리의 다른 글

SOLID 원칙  (3) 2025.03.26
상속과 합성: 코드 재사용과 확장의 방법  (0) 2025.03.09
equals()와 hashCode()의 개념과 관계  (0) 2025.03.01
단위 테스트  (0) 2025.03.01
객체와 자료 구조의 차이  (0) 2025.03.01