home > back-end > design-pattern > [design-pattern] strategy 패턴

[design-pattern] strategy 패턴
back-end design-pattern strategy

intro : strategy 패턴에 대해서 알아보자

Strategy 패턴이란 ?

Strategy 패턴은 특정 작업을 수행하는 여러 방식(즉, 전략)을 정의하고, 필요에 따라 이를 교체하며 사용할 수 있도록 설계된 행동 패턴입니다. 쉽게 말해, 어떤 작업을 처리하는 방식(모드)을 각각의 클래스로 분리하고, 실행 시점에 적절한 전략을 선택해 사용하는 것입니다. 예를 들어, 다이소에서 물건을 결제할 때 다양한 결제 방식을 선택할 수 있는 화면이 나온다고 가정해 보겠습니다. 사용할 수 있는 결제 방식으로는 카드 결제, 현금 결제, 카카오페이 결제, 애플페이 결제, 네이버페이 결제 등이 있을 수 있습니다. 만약 이러한 결제 프로그램을 우리가 작성할 때, 결제라는 행위는 동일하지만, 선택된 결제 방식에 따라 수행되어야 하는 로직이 달라지게 됩니다. 이처럼 다양한 결제 방식을 유연하게 처리하기 위해 Strategy 패턴을 적용할 수 있습니다. Strategy 패턴을 통해 각 결제 방식을 별도 클래스로 분리하고, 실행 시점에 적절한 전략을 선택하여 사용함으로써 코드의 확장성유연성을 높일 수 있습니다.

Strategy 패턴은 언제 사용하는가 ?

Strategy 패턴은 행동(Behavior)을 캡슐화하고, 다양한 알고리즘(전략)을 동적으로 교체할 수 있도록 설계할 때 사용한다. 이 패턴은 특정 작업을 수행하는 여러 알고리즘(전략)들이 존재하며, 이들 사이를 유연하게 전환하거나 교체해야 할 때 유용하다.

Strategy 패턴 적용 코드 (1)

결제 전략에 사용할 인터페이스

// 결제 방식에 대한 인터페이스 
interface PaymentStrategy {
    void pay(int amount);
}

결제 전략 인터페이스를 구현한 실제 전략 클래스 (구현체)

// PaymentStrategy 인터페이스를 구현한 신용카드 결제 방식 클래스 구성
class CreditCardPayment implements PaymentStrategy {
    private String name;
    private String cardNumber;

    public CreditCardPayment(String name, String cardNumber) {
        this.name = name;
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid with credit card");
    }
}
// PaymentStrategy 인터페이스를 구현한 페이팔 결제 방식 클래스 구성
class PayPalPayment implements PaymentStrategy {
    private String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using PayPal");
    }
}

각 전략(신용카드결제, 페이팔결제) 에 맞게 결제 메소드를 호출할수 있는 클래스

// CreditCardPayment, PayPalPayment 클래스를 통해 객체를 생성할 수 있는 ShoppingCart 클래스 
// setPaymentStrategy 메소드를 통해 어떤 전략이던지 동적으로 선택할 수 있다.
// 해당 클래스의 checkout 메소드를 통해 각 전략에 맞는 결제 메소드를 실행 할 수 있다.
class ShoppingCart {
    private PaymentStrategy paymentStrategy;

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

    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

클라이언트 코드 에서 ShoppingCart 클래스 setPaymentStrategy 메소드를 통해 신용카드 결제 방식으로 결제, 두번째로는 setPaymentStrategy 메소드를 통해 페이팔 결제방식 으로 전환. 다음과 같은 코드를 통해 각 상황에 맞게 전략을 동적으로 선택 할 수 있음을 알 수 있다.

// Client code
public class Main {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        cart.setPaymentStrategy(
                new CreditCardPayment(
                        "John Doe",
                        "1234567890123456"
                )
        );
        //100 paid with credit card
        cart.checkout(100);

        cart.setPaymentStrategy(
                new PayPalPayment(
                        "johndoe@example.com"
                )
        );
        // 200 paid using PayPal
        cart.checkout(200);
    }
}

Strategy 패턴 적용 코드 (2)

문자열을 압축하는 프로그램을 구성할건데, 압축하는 방식을 두가지 전략으로 구성하고자 한다. 1번은 각 문자가 연속되는 횟수를 기록하여 문자열을 압축하는 방식, 2번은 문자열의 각 모음을 정해진 숫자로 변환하는 방식이 있다. 해당 상황을 가정하고 아래와 같은 코드를 따라가보자.

압축 전략에 사용할 인터페이스

interface CompressionStrategy {
    String compress(String data);
}

압축 전략 인터페이스를 구현한 실제 전략 클래스 (구현체)

class RunLengthEncoding implements CompressionStrategy {
    @Override
    public String compress(String data) {
        StringBuilder compressed = new StringBuilder();
        int count = 1;
        for (int i = 1; i <= data.length(); i++) {
            if (i < data.length() && data.charAt(i) == data.charAt(i - 1)) {
                count++;
            } else {
                compressed.append(data.charAt(i - 1));
                compressed.append(count);
                count = 1;
            }
        }
        return compressed.toString();
    }
}

class SimpleReplacementCompression implements CompressionStrategy {
    @Override
    public String compress(String data) {
        return data.replace("a", "1")
                   .replace("e", "2")
                   .replace("i", "3")
                   .replace("o", "4")
                   .replace("u", "5");
    }
}

각 전략(RunLengthEncoding, SimpleReplacementCompression) 에 맞게 압축 메소드를 호출할 수 있는 클래스

// Context class
class Compressor {
    private CompressionStrategy strategy;

    public void setCompressionStrategy(CompressionStrategy strategy) {
        this.strategy = strategy;
    }

    public String compress(String data) {
        return strategy.compress(data);
    }
}

클라이언트 코드 에서 Compressor 클래스 setCompressionStrategy 메소드를 통해 RunLengthEncoding방식으로 압축, 두번째로는 setCompressionStrategy메소드를 통해 SimpleReplacementCompression 방식으로 전환. 결국 1번에서 확인했던 전략 패턴의 큰 틀의 구조와 같다고 볼 수 있다. 전략패턴을 적용할 상황만 살짝 다를 뿐이었다.

// Client code
public class Main {
    public static void main(String[] args) {
        Compressor compressor = new Compressor();
        String data = "aabcccccaaa";

        compressor.setCompressionStrategy(new RunLengthEncoding());
        System.out.println(
                "RLE Compression: " + compressor.compress(data)
        );
        // RLE Compression: a2b1c5a3

        compressor.setCompressionStrategy(new SimpleReplacementCompression());
        System.out.println(
                "Simple Replacement: " + compressor.compress(data)
        );
        // Simple Replacement: 11bccccc111
    }
}

Strategy 패턴 요약

Strategy 패턴은 동일한 작업을 수행하는 여러 방법을 정의하고, 실행 시 적합한 방법을 동적으로 선택하여 사용하는 행동 패턴이다.