본문 바로가기
까망 동네/디자인 패턴

💻 [디자인패턴] 전략 패턴 (Strategy Pattern)

by 까망 하르방 2023. 10. 19.
반응형

전략 패턴 (Strategy Pattern) 이란?

전략(Strategy)은 코드 내부에서 로직(Logic)을 처리하는 알고리즘

어떤 목적 달성을 위한 수행 방식이라고 생각하면 좋다.

영화관에서 이벤트 영화 예매 방식을 새롭게 정하고자 한다.
선입선출 / 예약제 / 랜덤 배정 등
다양한 "전략(방식)"으로 관람객을 선정할 수 있다.
 
이러한 전략은 기존에는 없던 새로운 전략이 생길 수도 있고
프로그램 목적에 따라서는 런타임 시간에도 계속 업데이트시 활용될 수 있다.

 

 

[예제 코드]

#include <iostream>
#include <vector>

// 전략 인터페이스 (추상 클래스)
class Strategy {
public:
    virtual void execute() = 0;
};

// 구체적인 전략 클래스
class ConcreteStrategyA : public Strategy {
public:
    void execute() override {
        std::cout << "전략 A" << std::endl;
    }
};

class ConcreteStrategyB : public Strategy {
public:
    void execute() override {
        std::cout << "전략 B" << std::endl;
    }
};

// 컨텍스트 클래스
class Context {
private:
    Strategy* strategy;

public:
    Context(Strategy* strategy) : strategy(strategy) {}
    void setStrategy(Strategy* newStrategy) {
        strategy = newStrategy;
    }
    void executeStrategy() {
        strategy->execute();
    }
};

void main() {
    ConcreteStrategyA strategyA;
    Context context(&strategyA);
    context.executeStrategy();

    ConcreteStrategyB strategyB;
    context.setStrategy(&strategyB);
    context.executeStrategy();
}

 

• Strategy 추상 클래스로 인터페이스 구현

• 각 전략 클래스(ConcreteStrategyA, ConcreteStrategyB) 구현

• Context 클래스는 전략 실행하는 역할

• setStrategy 함수로 런타임에 전략 교체 가능

 

→ 필요한 "전략"을 캡슐화 하여 교체 가능하다.

    이는 코드 확장성 측면에서도 유리하다!

 

[예제 코드]

정렬 방식을 퀵(Quick), 힙(Heap), 합병(Merge) 등

목적에 맞는 알고리즘 선택이 가능하다.

#include <iostream>
#include <vector>
#include <algorithm>

class SortingStrategy {
public:
    virtual void sort(std::vector<int>& data) = 0;
};

// Quick Sort
class QuickSort : public SortingStrategy {
public:
    void sort(std::vector<int>& data) override {
        std::cout << "Quick Sort" << std::endl;
        std::sort(data.begin(), data.end());
    }
};

// Heap Sort
class HeapSort : public SortingStrategy {
public:
    void sort(std::vector<int>& data) override {
        std::cout << "Heap Sort" << std::endl;
        std::make_heap(data.begin(), data.end());
        std::sort_heap(data.begin(), data.end());
    }
};

class SortContext {
private:
    SortingStrategy* strategy;

public:
    SortContext(SortingStrategy* strategy) : strategy(strategy) {}
    void setSortingStrategy(SortingStrategy* newStrategy) {
        strategy = newStrategy;
    }
    void performSort(std::vector<int>& data) {
        strategy->sort(data);
    }
};

void main() {
    std::vector<int> data = { 9, 3, 7, 1, 8, 5, 4 };

    QuickSort quickSort;
    SortContext context(&quickSort);
    context.performSort(data);

    HeapSort heapSort;
    context.setSortingStrategy(&heapSort);
    context.performSort(data);
}

 

전략 패턴 장단점

정렬 알고리즘 교체와 같이 새로운 전략 선택시

코드 변경을 최소화 할 수 있으며

각 "전략"을 개별 테스트할 때도 유용한다.

 

하지만 전략 교체에 따른 런타임 Overhead가 따를 수 있다.

상태 변화가 자주 발생하거나

Runtime에서 전략 변경이 필요한 경우 적합한 패턴

📌 정렬 알고리즘 비교

 

정렬 알고리즘 비교

시간 복잡도 비교 ▶ O(1) < O(log n) < O(n) < O(n log n) < O(n²) < O(2ⁿ) < O(n!) < O(nⁿ) 정렬 알고리즘 시간 복잡도 특징 안전성은 주어진 배열 원소의 배치와 상관없이 구현 속도에 변함이 없는가를 의미

zoosso.tistory.com


그냥 if 문으로 사용하면 안될까?

[예제] 조건문 사용

#include <iostream>

enum Operation { ADD, SUBTRACT, MULTIPLY, DIVIDE };

double calculate(double a, double b, Operation op) {
    if (op == ADD) {
        return a + b;
    }
    else if (op == SUBTRACT) {
        return a - b;
    }
    else if (op == MULTIPLY) {
        return a * b;
    }
    else if (op == DIVIDE) {
        if (b != 0) {
            return a / b;
        }
        else {
            std::cerr << "Division by zero is not allowed." << std::endl;
            return 0.0;
        }
    }
    else {
        std::cerr << "Invalid operation." << std::endl;
        return 0.0;
    }
}

void main() {
    double num1 = 10.0;
    double num2 = 5.0;

    double result = calculate(num1, num2, ADD);
    std::cout << "덧셈 = " << result << std::endl;

    result = calculate(num1, num2, SUBTRACT);
    std::cout << "뺄셈 = " << result << std::endl;

    result = calculate(num1, num2, MULTIPLY);
    std::cout << "곱셈 = " << result << std::endl;

    result = calculate(num1, num2, DIVIDE);
    std::cout << "나눗셈 = " << result << std::endl;
}

 

 

[예제] 전략 패턴 적용

#include <iostream>

class OperationStrategy {
public:
    virtual double operate(double a, double b) = 0;
};

class AddOperation : public OperationStrategy {
public:
    double operate(double a, double b) override {
        return a + b;
    }
};

class SubtractOperation : public OperationStrategy {
public:
    double operate(double a, double b) override {
        return a - b;
    }
};

class MultiplyOperation : public OperationStrategy {
public:
    double operate(double a, double b) override {
        return a * b;
    }
};

class DivideOperation : public OperationStrategy {
public:
    double operate(double a, double b) override {
        if (b != 0) {
            return a / b;
        }
        else {
            std::cerr << "Division by zero is not allowed." << std::endl;
            return 0.0;
        }
    }
};

class Calculator {
private:
    OperationStrategy* strategy;

public:
    Calculator(OperationStrategy* strategy) : strategy(strategy) {}
    void setStrategy(OperationStrategy* newStrategy) {
        strategy = newStrategy;
    }
    double calculate(double a, double b) {
        return strategy->operate(a, b);
    }
};

void main() {
    double num1 = 10.0;
    double num2 = 5.0;

    Calculator calculator(new AddOperation());
    double result = calculator.calculate(num1, num2);
    std::cout << "덧셈 = " << result << std::endl;

    calculator.setStrategy(new SubtractOperation());
    result = calculator.calculate(num1, num2);
    std::cout << "뺄셈 = " << result << std::endl;

    calculator.setStrategy(new MultiplyOperation());
    result = calculator.calculate(num1, num2);
    std::cout << "곱셈 = " << result << std::endl;

    calculator.setStrategy(new DivideOperation());
    result = calculator.calculate(num1, num2);
    std::cout << "나눗셈 = " << result << std::endl;
}

추상 클래스나 코드 길이만 봐서는 복잡성이 커진 것 같지만

새로운 연산 기능을 추가할 때, 클래스를 새로 만들면 된다.

개별 테스트시에도 다른 연산은 신경쓰지 않아도 된다.

이처럼 전략패턴은 조건문 없이도 알고리즘을 교체할 수 있다.

반응형

댓글