전략(Strategy) 패턴

김서영·2024년 9월 26일
0

디자인 패턴

목록 보기
4/5

전략 패턴


실행(런타임) 중에 알고리즘 전략을 선택해 객체 동작을 실시간으로 바뀌도록 할 수 있게 하는 행위 디자인 패턴

여기에서 '전략'이란 일종의 알고리즘이 될 수 도 있고, 기능이나 동작이 될 수도 있는 특정한 목표를 수행하기 위한 행동 계획을 말한다.

즉, 어떤 일을 수행하는 알고리즘이 여러가지 일 때, 동작들을 미리 전략으로 정의해 손쉽게 전략을 교체할 수 있는, 알고리즘 변형이 빈번하게 필요한 경우에 적합한 패턴이다.

✨ 전략 패턴 구조


💠 전략 알고리즘 객체들 : 알고리즘, 행위, 동작을 객체로 정의한 구현체
💠 전략 인터페이스 : 모든 전략 구현체에 대한 공용 인터페이스
💠 컨텍스트 : 알고리즘을 실행해야 할 때마다 해당 알고리즘
💠 클라이언트 : 특정 전략 객체를 컨텍스트에 전달 함으로써 전략을 등록하거나 변경하여 전략 알고리즘을 실행한 결과를 누린다.

💡 프로그래밍에서의 컨텍스트란 콘텐츠를 담는 무언가를 뜻하며, 어떤 객체를 핸들링 하기 위한 접근 수단이다.
즉, 물컴에 물이 담겨있으면 물은 콘텐츠가 되고, 물컵은 컨텍스트가 되며, 물을 핸들링하기 위한 접근 수단이 된다.

✨ 전략 패턴 흐름

🔶클래스 구성

// 전략(추상화된 알고리즘)
interface IStrategy {
    void doSomething();
}

// 전략 알고리즘 A
class ConcreteStrateyA implements IStrategy {
    public void doSomething() {}
}

// 전략 알고리즘 B
class ConcreteStrateyB implements IStrategy {
    public void doSomething() {}
}
// 컨텍스트(전략 등록/실행)
class Context {
    IStrategy Strategy; // 전략 인터페이스를 합성(composition)
	
    // 전략 교체 메소드
    void setStrategy(IStrategy Strategy) {
        this.Strategy = Strategy;
    }
	
    // 전략 실행 메소드
    void doSomething() {
        this.Strategy.doSomething();
    }
}

🔶 클래스 흐름

// 클라이언트(전략 교체/전략 실행한 결과를 얻음)
class Client {
    public static void main(String[] args) {
        // 1. 컨텍스트 생성
        Context c = new Context();

        // 2. 전략 설정
        c.setStrategy(new ConcreteStrateyA());

        // 3. 전략 실행
        c.doSomething();

        // 4. 다른 전략 설정
        c.setStrategy(new ConcreteStrateyB());

        // 5. 다른 전략 시행
        c.doSomething();
    }
}

✨ 전략 패턴 특징

🔶 전략 패턴 사용 시기

💠 전략 알고리즘의 여러 버전 또는 변형이 필요할 때 클래스화를 통해 관리
💠 알고리즘 코드가 노출되어서는 안되는 데이터에 액세스 하거나 데이터를 활용할 때 (캡슐화)
💠 알고리즘의 동작이 런타임에 실시간으로 교체 되어야 할 때

🔶 전략 패턴 주의점

💠 알고리즘이 많아질수록 관리해야 할 객체의 수가 늘어난다는 단점이 있다.
💠 만일 어플리케이션 특성이 알고리즘이 많지 않고 자주 변경되지 않는다면, 새로운 클래스와 인터페이스를 만들어 프로그램을 복잡하게 만들 이유가 없다.
💠 개발자는 적절한 전략을 선택하기 위해 전략 간의 차이점을 파악하고 있어야 한다. (복잡도 증가)

✨ 전략 패턴 예제

🔶 문제가 있는 코드

class TakeWeapon {
    public static final int SWORD = 0;
    public static final int SHIELD = 1;
    public static final int CROSSBOW = 2;

    private int state;

    void setWeapon(int state) {
        this.state = state;
    }

    void attack() {
        if (state == SWORD) {
            System.out.println("칼을 휘두르다");
        } else if (state == SHIELD) {
            System.out.println("방패로 밀친다");
        } else if (state == CROSSBOW) {
            System.out.println("석궁을 발사하다");
        }
    }
}
class User {
    public static void main(String[] args) {
        // 플레이어 손에 무기 착용 전략을 설정
        TakeWeapon hand = new TakeWeapon();

        // 플레이어가 검을 들도록 전략 설정
        hand.setWeapon(TakeWeapon.SWORD);
        hand.attack(); // "칼을 휘두르다"

        // 플레이어가 방패를 들도록 전략 설정
        hand.setWeapon(TakeWeapon.SHIELD);
        hand.attack(); // "방패로 밀친다"
    }
}

위 코드를 살펴보면 state 매개변수의 값에 따라 간접적으로 attack() 함수의 동작을 제어하도록 되어 있다.
적이 오면 상수를 메서드에 넘겨 조건문으로 일일히 필터링해 적절한 전략을 실행하는 방식이다.

하지만 이 코드처럼 상태 변수를 통해 행위를 분기문으로 나누는 행위는 좋지 않은 코드이다.
잘못하면 if else 지옥에 빠질 수도 있다.

// 전략 - 추상화된 알고리즘
interface Weapon {
    void offensive();
}

class Sword implements Weapon {
    @Override
    public void offensive() {
        System.out.println("칼을 휘두르다");
    }
}

class Shield implements Weapon {
    @Override
    public void offensive() {
        System.out.println("방패로 밀친다");
    }
}

class CrossBow implements Weapon {
    @Override
    public void offensive() {
        System.out.println("석궁을 발사하다");
    }
}
// 컨텍스트 - 전략을 등록하고 실행
class TakeWeaponStrategy {
    Weapon wp;

    void setWeapon(Weapon wp) {
        this.wp = wp;
    }

    void attack() {
        wp.offensive();
    }
}
// 클라이언트 - 전략 제공/설정
class User {
    public static void main(String[] args) {
        // 플레이어 손에 무기 착용 전략을 설정
        TakeWeaponStrategy hand = new TakeWeaponStrategy();

        // 플레이어가 검을 들도록 전략 설정
        hand.setWeapon(new Sword());
        hand.attack(); // "칼을 휘두르다"

        // 플레이어가 방패를 들도록 전략 변경
        hand.setWeapon(new Shield());
        hand.attack(); // "방패로 밀친다"
        
        // 플레이어가 석궁을 들도록 전략 변경
        hand.setWeapon(new Crossbow());
        hand.attack(); // "석궁을 발사하다"
    }
}

위의 전략패턴에서는 상수 값을 넘겨주는 대신, 인스턴스를 넣어 알고리즘을 수행하도록 했다.

이런식으로 구성하면 나중에 칼이나 방패 외에 도끼나 창과 같은 전략 무기들을 추가로 등록할 때, 코드의 수정없이 빠르게 기능을 확장할 수 있다는 장점이 있다. (class를 추가하고 implements를 해주면 끝)

profile
개발과 지식의 성장을 즐기는 개발자

0개의 댓글