본문 바로가기
카테고리 없음

전략 패턴(Strategy Pattern)으로 유연한 객체지향 설계 만들기

by silvertogold100 2025. 8. 11.
반응형

객체지향 프로그래밍에서 상속만으로는 해결할 수 없는 복잡한 요구사항들이 있습니다. 오늘은 이런 문제를 해결해주는 **전략 패턴(Strategy Pattern)**에 대해 알아보겠습니다.

🦆 문제 상황: 오리 시뮬레이터의 딜레마

SimUDuck이라는 오리 시뮬레이터를 개발한다고 가정해봅시다. 처음에는 단순히 상속을 통해 다양한 오리 종류를 구현했지만, 새로운 요구사항이 추가되면서 문제가 발생했습니다.

상속의 한계점

// 기존 설계의 문제점
public class Duck {
    public void fly() { /* 모든 오리가 날 수 있다고 가정 */ }
    public void quack() { /* 모든 오리가 꽥꽥거린다고 가정 */ }
}

public class RubberDuck extends Duck {
    // 문제: 고무오리는 날 수 없고, 삑삑거린다!
    @Override
    public void fly() { /* 비워두거나 예외처리? */ }
    @Override
    public void quack() { /* 삑삑 소리로 변경 */ }
}

문제점들:

  • 규격이 바뀔 때마다 모든 서브클래스의 메소드를 확인하고 수정해야 함
  • 모든 오리가 같은 행동을 하지 않는데도 강제로 상속받아야 함
  • 코드 재사용성이 떨어짐

💡 해결책: 전략 패턴 적용하기

전략 패턴을 적용하기 위해 세 가지 핵심 디자인 원칙을 활용합니다.

디자인 원칙 1: 변하는 부분과 변하지 않는 부분 분리하기

// 변하는 부분을 인터페이스로 분리
public interface FlyBehavior {
    void fly();
}

public interface QuackBehavior {
    void quack();
}

디자인 원칙 2: 구현보다는 인터페이스에 맞춰 프로그래밍하기

// 구체적인 행동 구현체들
public class FlyWithWings implements FlyBehavior {
    public void fly() {
        System.out.println("날개로 날고 있어요!");
    }
}

public class FlyNoWay implements FlyBehavior {
    public void fly() {
        System.out.println("날지 못합니다.");
    }
}

public class FlyRocketPowered implements FlyBehavior {
    public void fly() {
        System.out.println("로켓 추진으로 날아갑니다!");
    }
}

public class Quack implements QuackBehavior {
    public void quack() {
        System.out.println("꽥");
    }
}

public class Squeak implements QuackBehavior {
    public void quack() {
        System.out.println("삑");
    }
}

public class MuteQuack implements QuackBehavior {
    public void quack() {
        System.out.println("조용");
    }
}

디자인 원칙 3: 상속보다는 구성(Composition) 활용하기

public abstract class Duck {
    // 행동을 위임할 객체들
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
    
    public void performFly() {
        flyBehavior.fly(); // 행동을 위임
    }
    
    public void performQuack() {
        quackBehavior.quack(); // 행동을 위임
    }
    
    // 런타임에 행동을 변경할 수 있는 세터 메소드
    public void setFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }
    
    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }
    
    public abstract void display();
}

🔧 구체적인 구현

이제 다양한 오리들을 구현해봅시다.

public class MallardDuck extends Duck {
    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }
    
    public void display() {
        System.out.println("저는 물오리입니다.");
    }
}

public class ModelDuck extends Duck {
    public ModelDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new MuteQuack();
    }
    
    public void display() {
        System.out.println("저는 모형 오리입니다.");
    }
}

🎯 실제 사용 예제

public class MiniDuckSimulator {
    public static void main(String[] args) {
        // 물오리 생성 및 테스트
        Duck mallard = new MallardDuck();
        mallard.performFly();    // "날개로 날고 있어요!"
        mallard.performQuack();  // "꽥"
        
        // 모형 오리 생성 및 테스트
        Duck model = new ModelDuck();
        model.performFly();      // "날지 못합니다."
        model.performQuack();    // "조용"
        
        // 런타임에 행동 변경!
        model.setFlyBehavior(new FlyRocketPowered());
        model.performFly();      // "로켓 추진으로 날아갑니다!"
    }
}

🚀 확장성 예제: 오리 호출기

Duck 클래스를 상속받지 않고도 꽥꽥거리는 기능을 사용할 수 있습니다.

public class DuckCaller {
    QuackBehavior quackBehavior;
    
    public void call(QuackBehavior qb) {
        quackBehavior = qb;
        qb.quack();
    }
}

// 사용 예제
DuckCaller duckCaller = new DuckCaller();
duckCaller.call(new Quack());  // "꽥"
duckCaller.call(new Squeak()); // "삑"

📚 전략 패턴의 정의

**전략 패턴(Strategy Pattern)**은 알고리즘군을 정의하고 캡슐화해서 각각의 알고리즘군을 수정해서 쓸 수 있게 해줍니다. 전략 패턴을 사용하면 클라이언트로부터 알고리즘을 분리해서 독립적으로 변경할 수 있습니다.

✨ 전략 패턴의 장점

1. 유연성 향상

  • 런타임에 객체의 행동을 변경할 수 있습니다
  • 새로운 알고리즘을 쉽게 추가할 수 있습니다

2. 코드 재사용성

  • 같은 행동을 여러 클래스에서 재사용할 수 있습니다
  • 중복 코드를 제거할 수 있습니다

3. 관리의 용이성

  • 각 알고리즘이 독립적으로 존재하여 수정이 쉽습니다
  • 새로운 요구사항에 대한 대응이 빠릅니다

4. 개방-폐쇄 원칙 준수

  • 기존 코드 수정 없이 새로운 전략을 추가할 수 있습니다

🎯 핵심 포인트

  1. 변화하는 부분을 캡슐화하라: 자주 변경되는 알고리즘들을 별도의 클래스로 분리
  2. 인터페이스를 활용하라: 구체적인 구현보다는 추상화에 의존
  3. 구성을 활용하라: 상속보다는 객체 합성을 통해 유연성 확보
  4. 런타임 변경 가능: 필요에 따라 실행 중에 전략을 바꿀 수 있음

💭 마무리

전략 패턴은 단순해 보이지만 매우 강력한 디자인 패턴입니다. 특히 다양한 알고리즘이나 정책이 필요한 상황에서 코드의 유연성과 확장성을 크게 향상시킬 수 있습니다.

객체지향 설계에서 "변화"를 다루는 것은 항상 중요한 과제입니다. 전략 패턴을 통해 변화에 유연하게 대응할 수 있는 설계를 만들어보세요!


다음 포스팅에서는 옵저버 패턴(Observer Pattern)에 대해 알아보겠습니다. 많은 관심 부탁드립니다! 🙌

반응형