프로그래밍 언어/디자인패턴

1. Strategy 패턴

코딩금융치료 2025. 1. 6. 16:15

Strategy 패턴이란?

Strategy 패턴은 동일한 문제를 해결하는 여러 알고리즘이 있을 때, 이들을 독립된 클래스로 캡슐화하고 런타임에 알고리즘을 교체할 수 있도록 하는 설계 패턴입니다.

이 패턴은 코드 중복을 줄이고 확장성을 높이는 데 매우 유용합니다.

 

예를 들어, 결제 시스템을 설계한다고 가정해봅시다. 결제 방식은 신용카드, PayPal, 혹은 기타 방식으로 다양할 수 있습니다. 각 결제 방식을 동적으로 변경할 수 있다면 사용자의 요구에 더 잘 대응할 수 있습니다. 하지만 잘못된 설계로 인해 유지보수와 확장성이 떨어질 위험이 있습니다.

 

다음은 잘못된 설계와 그 문제점을 살펴보고, 이를 Strategy 패턴으로 해결하는 과정을 소개합니다.

 

이 글의 소스코드 및 설명은 헤드퍼스트 디자인패턴 책을 참조하였습니다.

 

단순 확장 방식의 문제점

아래는 Payment 클래스를 단순 확장하여 다양한 결제 방식을 구현한 코드입니다. 이 방식은 확장성이 부족하고, 새로운 결제 방식이 추가될 때마다 기존 코드를 수정해야 하는 문제를 야기합니다.

 

// Base class
class Payment {
    public void pay(String type, int amount) {
        if (type.equals("CREDIT")) {
            System.out.println("Credit Card로 " + amount + "원을 결제합니다.");
        } else if (type.equals("PAYPAL")) {
            System.out.println("PayPal로 " + amount + "원을 결제합니다.");
        } else {
            System.out.println("결제 유형이 잘못되었습니다.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Payment payment = new Payment();
        payment.pay("CREDIT", 10000); // 출력: Credit Card로 10000원을 결제합니다.
        payment.pay("PAYPAL", 20000); // 출력: PayPal로 20000원을 결제합니다.
    }
}

 

문제점

  1. 결제 방식 추가 시 수정 필요:
    • 새로운 결제 방식을 추가하려면 pay 메서드에 조건문을 추가해야 합니다.
    • 이는 기존 코드를 수정하는 일이므로, **개방-폐쇄 원칙(OCP)**에 위배됩니다.
  2. 유지보수 어려움:
    • 결제 방식이 많아질수록 pay 메서드의 조건문이 길어지고 복잡해집니다.
    • 이는 코드의 가독성을 떨어뜨리고, 수정 시 오류가 발생할 가능성을 높입니다.
  3. 재사용성 부족:
    • 특정 결제 방식에 대한 로직이 Payment 클래스에 고정되어 있어, 다른 곳에서 재사용하기 어렵습니다.

Strategy 패턴으로 문제 해결

위의 문제를 해결하기 위해 Strategy 패턴을 적용하면 결제 방식을 독립적인 클래스로 분리하여 유연성과 확장성을 높일 수 있습니다.

// Strategy Interface
interface PaymentStrategy {
    void pay(int amount);
}

// Concrete Strategies
class CreditCardPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("Credit Card로 " + amount + "원을 결제합니다.");
    }
}

class PayPalPayment implements PaymentStrategy {
    @Override
    public void pay(int amount) {
        System.out.println("PayPal로 " + amount + "원을 결제합니다.");
    }
}

// Context Class
class PaymentContext {
    private PaymentStrategy strategy;

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void pay(int amount) {
        if (strategy == null) {
            throw new IllegalStateException("Payment strategy is not set.");
        }
        strategy.pay(amount);
    }
}

// Main Class
public class Main {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext();

        // Credit Card 결제
        context.setPaymentStrategy(new CreditCardPayment());
        context.pay(10000); // 출력: Credit Card로 10000원을 결제합니다.

        // PayPal 결제
        context.setPaymentStrategy(new PayPalPayment());
        context.pay(20000); // 출력: PayPal로 20000원을 결제합니다.
    }
}

Strategy 패턴 적용 후 장점

  1. 확장성 증가:
    • 새로운 결제 방식을 추가할 때 기존 코드를 수정하지 않고, 새로운 PaymentStrategy 구현체만 작성하면 됩니다.
  2. 유지보수 용이:
    • 조건문 없이 각 결제 방식이 독립적인 클래스로 분리되어 있어 수정 및 테스트가 간단합니다.
  3. 유연한 런타임 동작:
    • 런타임에 동적으로 결제 방식을 변경할 수 있습니다.

Strategy 패턴의 핵심 원리

  1. 행동 캡슐화:
    • PaymentStrategy 인터페이스는 모든 결제 방식이 따라야 할 공통 계약을 정의합니다.
    • 이를 통해 구체적인 구현은 전략 클래스 내부로 캡슐화되고, 외부에서는 인터페이스를 통해 동작을 호출합니다.
  2. 구성(Composition)을 통한 유연성:
    • PaymentContext 클래스는 PaymentStrategy 객체를 구성 요소로 포함합니다.
    • 이 구성 요소는 런타임에 동적으로 교체될 수 있으므로, 다양한 전략을 유연하게 적용할 수 있습니다.
  3. 개방-폐쇄 원칙(OCP) 준수:
    • 기존 코드는 변경 없이 새로운 전략(결제 방식)을 쉽게 추가할 수 있습니다.

런타임에서의 변경이란?

"런타임 동적 변경"이란 코드 실행 중 사용자의 선택이나 특정 조건에 따라 전략(결제 방식)을 설정하고 변경할 수 있음을 의미합니다. 이를 통해, 컴파일 시점에 결제 방식을 고정하지 않고 실행 중에 유연하게 결제 방식을 교체할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext();

        // 사용자 입력이나 조건에 따라 전략 선택
        boolean userSelectedCreditCard = true;

        if (userSelectedCreditCard) {
            context.setPaymentStrategy(new CreditCardPayment());
        } else {
            context.setPaymentStrategy(new PayPalPayment());
        }

        context.pay(15000); // 선택된 전략에 따라 결제 수행
    }
}

이처럼 런타임에 전략을 설정하거나 변경함으로써, 프로그램의 유연성과 확장성이 극대화됩니다.

결론

Strategy 패턴은 다양한 알고리즘을 유연하게 적용하고, 코드의 유지보수성과 확장성을 크게 향상시키는 데 유용합니다. 이를 통해 객체지향 설계의 중요한 원칙을 준수하면서 효율적인 소프트웨어 개발이 가능합니다.

'프로그래밍 언어 > 디자인패턴' 카테고리의 다른 글

디자인 패턴  (1) 2025.01.01