Menu
Coddy logo textTech

전략 패턴

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 95번째.

전략 패턴(Strategy pattern)은 일련의 알고리즘을 정의하고, 각 알고리즘을 캡슐화하며, 이들을 상호 교체 가능하게 만듭니다. 이를 통해 코드를 수정하지 않고도 런타임에 객체의 동작을 변경할 수 있으며, 알고리즘은 이를 사용하는 클라이언트와 독립적으로 변화합니다.

이 패턴은 세 부분으로 구성됩니다: 알고리즘 메서드를 선언하는 Strategy 인터페이스, 다양한 변형을 구현하는 Concrete Strategies, 그리고 전략을 사용하는 Context입니다:

#include <iostream>
#include <memory>

// 전략 인터페이스
class PaymentStrategy {
public:
    virtual void pay(int amount) = 0;
    virtual ~PaymentStrategy() = default;
};

// 구체적인 전략들
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via Credit Card\n";
    }
};

class PayPalPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via PayPal\n";
    }
};

// 컨텍스트
class ShoppingCart {
    std::unique_ptr<PaymentStrategy> strategy;
public:
    void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
        strategy = std::move(s);
    }
    
    void checkout(int total) {
        if (strategy) strategy->pay(total);
    }
};

int main() {
    ShoppingCart cart;
    
    cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
    cart.checkout(100);
    
    cart.setPaymentMethod(std::make_unique<PayPalPayment>());
    cart.checkout(50);
}

ShoppingCart는 어떤 결제 수단을 사용하고 있는지 알지 못합니다. 설정된 전략이 무엇이든 그저 pay()를 호출할 뿐입니다. setPaymentMethod()를 통해 런타임에 전략을 교체할 수 있어, 시스템을 유연하게 만들고 새로운 결제 옵션으로 쉽게 확장할 수 있습니다.

특정 작업을 위한 여러 알고리즘이 있고 이들 사이를 동적으로 전환하고 싶을 때, 또는 동작을 선택하기 위해 조건문을 사용하는 것을 피하고 싶을 때 전략(Strategy) 패턴을 사용하세요.

challenge icon

챌린지

쉬움

다양한 배송 방법에 따라 배송비를 계산하기 위해 전략(Strategy) 패턴을 사용하는 배송 계산기(Shipping Calculator)를 만들어 보겠습니다. 이는 런타임에 알고리즘을 교체해야 하는 실제적인 시나리오입니다. 동일한 패키지라도 지상(ground), 항공(air) 또는 특송(express)을 통해 배송될 수 있으며, 각 방법은 고유한 가격 책정 로직을 가집니다.

코드는 다음 세 개의 파일로 구성됩니다:

  • ShippingStrategy.h: 전략 인터페이스와 구체적인 배송 전략을 정의합니다.

    배송비를 double로 반환하는 순수 가상 메서드 calculateCost(double weight)와 가상 소멸자를 포함하는 추상 클래스 ShippingStrategy를 생성하세요.

    그런 다음 세 가지 구체적인 전략을 구현하세요:

    • GroundShipping — 무게 단위당 1.5의 비용 (weight * 1.5)
    • AirShipping — 무게 단위당 4.0의 비용 (weight * 4.0)
    • ExpressShipping — 무게 단위당 6.5의 비용에 고정 수수료 10.0 추가 (weight * 6.5 + 10.0)
  • ShippingService.h: 배송 전략을 사용하는 컨텍스트(context) 클래스를 생성합니다.

    ShippingService 클래스는 std::unique_ptr<ShippingStrategy>를 프라이빗 멤버로 가져야 합니다. 다음을 구현하세요:

    • 배송 방법을 변경하기 위한 setStrategy(std::unique_ptr<ShippingStrategy> strategy) 메서드
    • 현재 전략을 사용하여 비용을 계산하고 반환하는 calculateShipping(double weight) 메서드

    calculateShipping이 호출될 때 설정된 전략이 없다면 0.0을 반환하세요.

  • main.cpp: 런타임에 전략을 전환하는 것을 보여줍니다.

    두 개의 입력을 읽습니다:

    1. 패키지 무게 (double)
    2. 배송 방법: ground, air, 또는 express

    ShippingService를 생성하고 입력된 방법에 따라 적절한 전략을 설정하세요. 배송비를 계산하고 출력합니다.

    그런 다음 다른 전략으로 전환하고(입력이 air가 아니면 air를 사용하고, 그렇지 않으면 ground를 사용) 동일한 무게에 대해 비용을 다시 계산합니다. 이는 런타임에 전략을 교체하는 위력을 보여줍니다.

    각 비용은 배송 방법 이름과 함께 소수점 첫째 자리까지 한 줄에 하나씩 출력하세요:

    [Method]: $[cost]

예를 들어, 입력이 5.0ground인 경우:

Ground: $7.5
Air: $20.0

입력이 3.0express인 경우:

Express: $29.5
Air: $12.0

입력이 10.0air인 경우:

Air: $40.0
Ground: $15.0

ShippingService가 각 가격 책정 알고리즘의 세부 사항을 알 필요가 없다는 점에 주목하세요. 단순히 현재 설정된 전략에 위임할 뿐입니다. 서비스 클래스를 전혀 수정하지 않고도 드론 배송이나 당일 배송과 같은 새로운 배송 방법을 쉽게 추가할 수 있습니다.

치트 시트

전략 패턴(Strategy pattern)은 일련의 알고리즘을 정의하고, 각 알고리즘을 캡슐화하며, 이들을 상호 교체 가능하게 만듭니다. 이를 통해 코드 수정 없이 런타임에 객체의 동작을 변경할 수 있습니다.

이 패턴은 세 부분으로 구성됩니다:

  • 전략 인터페이스(Strategy interface): 알고리즘 메서드를 선언하는 추상 클래스
  • 구체적인 전략(Concrete Strategies): 다양한 알고리즘 변형을 구현하는 클래스들
  • 컨텍스트(Context): 전략을 사용하며 전략 간에 전환할 수 있는 클래스
// 전략 인터페이스
class PaymentStrategy {
public:
    virtual void pay(int amount) = 0;
    virtual ~PaymentStrategy() = default;
};

// 구체적인 전략들
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via Credit Card\n";
    }
};

class PayPalPayment : public PaymentStrategy {
public:
    void pay(int amount) override {
        std::cout << "Paid " << amount << " via PayPal\n";
    }
};

// 컨텍스트
class ShoppingCart {
    std::unique_ptr<PaymentStrategy> strategy;
public:
    void setPaymentMethod(std::unique_ptr<PaymentStrategy> s) {
        strategy = std::move(s);
    }
    
    void checkout(int total) {
        if (strategy) strategy->pay(total);
    }
};

컨텍스트는 자신이 어떤 구체적인 전략을 사용하고 있는지 알지 못하며, 단지 인터페이스 메서드를 호출할 뿐입니다. 전략은 세터(setter) 메서드를 사용하여 런타임에 교체될 수 있습니다:

ShoppingCart cart;

cart.setPaymentMethod(std::make_unique<CreditCardPayment>());
cart.checkout(100);

cart.setPaymentMethod(std::make_unique<PayPalPayment>());
cart.checkout(50);

사용 시기: 특정 작업에 대해 여러 알고리즘이 있고 이를 동적으로 전환하고 싶을 때, 또는 동작 선택을 위한 조건문을 피하고 싶을 때 전략 패턴을 사용하세요.

직접 해보기

#include <iostream>
#include <string>
#include <iomanip>
#include <memory>
#include "ShippingStrategy.h"
#include "ShippingService.h"

int main() {
    double weight;
    std::string method;
    
    std::cin >> weight;
    std::cin >> method;
    
    // 출력을 소수점 첫째 자리까지로 설정
    std::cout << std::fixed << std::setprecision(1);
    
    ShippingService service;
    
    // TODO: 입력된 method ("ground", "air", 또는 "express")에 따라:
    // 1. service에 적절한 전략(strategy)을 설정
    // 2. 비용을 계산하고 "[Method]: $[cost]" 형식으로 출력
    
    // TODO: 다른 전략으로 변경:
    // - 입력이 "air"였다면, GroundShipping으로 변경
    // - 그렇지 않다면, AirShipping으로 변경
    // 새로운 비용을 계산하고 출력
    
    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨