본문 바로가기
Design Pattern/행동 패턴(Behavioral Patterns)

전략(Strategy) - 행동 패턴(Behavioral Patterns)

by 김 민 준 2024. 5. 25.

 

전략 패턴은 알고리즘을 정의하고 각각 캡슐화하여 상호 교체가 가능하도록 만드는 패턴이다. 특정 작업을 수행하는 여러 알고리

즘이 있을때 런타임에 사용할 알고리즘을 성택할 수 있다. 핵심은 알고리즘을 사용하는 클라이언트와 알고리즘 자체를 분리하여 알고리즘을 독립적으로 변경할 수 있게 하는것이다.

 

주요 개념 부터 살펴보자. 

 

1. Strategy (전략) : 알고리즘을 정의하는 공통 인터페이스이다.

2. ConcreteStrategy (구체적인 전략) : 전략 인터페이스를 구현하며, 실제 알고리즘을 정의한다.

3. Context (문맥) : Strategy 개체를 사용하는 클래스이다. Context는 전략 개체를 가지고 있으며, 이를 통해 알고리즘을 실행한다. 

 

정렬 알고리즘을 전략 패턴으로 구현해본 소스코드를 살펴보자. 

 

// Strategy 인터페이스: 정렬 알고리즘을 정의
interface SortStrategy {
    void sort(int[] numbers);
}

 

// BubbleSortStrategy 클래스: 버블 정렬 알고리즘 구현
class BubbleSortStrategy implements SortStrategy {
    @Override
    public void sort(int[] numbers) {
        int n = numbers.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (numbers[j] > numbers[j + 1]) {
                    // Swap numbers[j] and numbers[j+1]
                    int temp = numbers[j];
                    numbers[j] = numbers[j + 1];
                    numbers[j + 1] = temp;
                }
            }
        }
    }
}

// SelectionSortStrategy 클래스: 선택 정렬 알고리즘 구현
class SelectionSortStrategy implements SortStrategy {
    @Override
    public void sort(int[] numbers) {
        int n = numbers.length;
        for (int i = 0; i < n - 1; i++) {
            int minIdx = i;
            for (int j = i + 1; j < n; j++) {
                if (numbers[j] < numbers[minIdx]) {
                    minIdx = j;
                }
            }
            // Swap numbers[i] and numbers[minIdx]
            int temp = numbers[minIdx];
            numbers[minIdx] = numbers[i];
            numbers[i] = temp;
        }
    }
}

 

// Context 클래스: 정렬 알고리즘을 사용하는 클래스
class SortContext {
    private SortStrategy strategy;

    public void setStrategy(SortStrategy strategy) {
        this.strategy = strategy;
    }

    public void sortArray(int[] numbers) {
        if (strategy != null) {
            strategy.sort(numbers);
        } else {
            System.out.println("Sort strategy not set.");
        }
    }
}

 

// Client 클래스: SortContext를 사용하여 정렬 알고리즘을 실행
public class StrategyPatternDemo {
    public static void main(String[] args) {
        SortContext context = new SortContext();

        int[] numbers = {5, 2, 9, 1, 5, 6};

        context.setStrategy(new BubbleSortStrategy());
        context.sortArray(numbers);
        System.out.println("Bubble Sorted: " + java.util.Arrays.toString(numbers));

        numbers = new int[]{5, 2, 9, 1, 5, 6}; // 배열을 초기 상태로 재설정
        context.setStrategy(new SelectionSortStrategy());
        context.sortArray(numbers);
        System.out.println("Selection Sorted: " + java.util.Arrays.toString(numbers));
    }
}

 

//출력

//Bubble Sorted: [1, 2, 5, 5, 6, 9]
//Selection Sorted: [1, 2, 5, 5, 6, 9]

 

버블 정렬과 선택 정렬 두가지에 대해서 구현하고 SortContext를 사용해서 알고리즘을 실행하여 결과를 받는 소스코드를 확인했다. 

 

패턴에 대한 설명에서도 알고리즘으로 설명했으나 꼭 알고리즘만 해당하는것은 아니긴 하다. 

일반적인 책에서 소개하는 방법일뿐 어떠한 상황에서 전략적으로 갖고가야할때 해당 패턴을 이용한다. 

 

예를 들어 비즈니스적으로 규칙이 필요한데 이를 구현할때 규칙을 정의하고 특정 조건에 따라 규칙을 적용해야하는데 이때 사용할 수 있을것이고, 로깅에 대해서도 필요에 따라서는 파일로 기록하고 혹은 직접 콘솔에 출력해야하는경우도 있을것이다. 때로는 원격서버에 전송하는 경우도 있을것이고 말이다. 

 

로직이나 행동을 캡슐화하여 서로 교체가 가능하게 만든다라는 개념이 가장 중요하다.

 

조금 더 예시 소스코드를 살펴보자.

 

결제 시스템을 개발해야하는데 다양한 결제 방법이 존재할것이다.

 

// PaymentStrategy 인터페이스: 결제 방법을 정의
interface PaymentStrategy {
    void pay(int amount);
}

 

// CreditCardStrategy 클래스: 신용카드 결제 방법 구현
class CreditCardStrategy implements PaymentStrategy {
    private String cardNumber;
    private String cardHolderName;

    public CreditCardStrategy(String cardNumber, String cardHolderName) {
        this.cardNumber = cardNumber;
        this.cardHolderName = cardHolderName;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid with credit card. Card Number: " + cardNumber);
    }
}

// PayPalStrategy 클래스: 페이팔 결제 방법 구현
class PayPalStrategy implements PaymentStrategy {
    private String email;

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

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

// BankTransferStrategy 클래스: 은행 이체 결제 방법 구현
class BankTransferStrategy implements PaymentStrategy {
    private String accountNumber;

    public BankTransferStrategy(String accountNumber) {
        this.accountNumber = accountNumber;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " paid using bank transfer. Account Number: " + accountNumber);
    }
}

 

// ShoppingCart 클래스: 결제 전략을 사용하는 클래스
class ShoppingCart {
    private PaymentStrategy paymentStrategy;

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

    public void checkout(int amount) {
        if (paymentStrategy != null) {
            paymentStrategy.pay(amount);
        } else {
            System.out.println("Payment strategy not set.");
        }
    }
}
// Client 클래스: 결제 방법을 설정하고 결제를 처리
public class StrategyPatternPaymentDemo {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        // 신용카드 결제
        cart.setPaymentStrategy(new CreditCardStrategy("1234-5678-9876-5432", "홍길동"));
        cart.checkout(100);

        // 페이팔 결제
        cart.setPaymentStrategy(new PayPalStrategy("hong@example.com"));
        cart.checkout(200);

        // 은행 이체 결제
        cart.setPaymentStrategy(new BankTransferStrategy("1234567890"));
        cart.checkout(300);
    }
}

 

위 로직처럼 각 결제방법에 대해 전략적으로 나누어 가져가면 동적으로 결제 방법을 변경할 수 있다.

이외에도 파일 저장을 로컬에 저장하거나 클라우드로 저장하는 방법이라던지 여러 인증수단 (Oauth, JWT, SAML)을 전략패턴으로 구현하여 동적으로 변경할 수 있다. 

 

워낙 비슷한 패턴이 많아 핵심만 정리해본다. 

 

1. 상태패턴

- 두 패턴은 행동을 캡슐화하여 교체 할 수 있고 컨텍스트 개체가 행동을 위임한다. 전략 패턴은 런타임에 변경할 수 있도록 하는 반면 상태 패턴은 개체의 상태 변화에 따라 행동을 변경한다.

 

2. 템플릿 메서드 패턴 

- 두 패턴은 세부 구현을 분리하여 유연성을 제공한다. 템플릿 메서드 패턴은 상속을 이용하여 알고리즘 변형을 구현하고 전략 패턴은 개체의 구성을 사용하여 알고리즘의 변형을 구현한다. 

 

3. 디코레이터 패턴 

- 두 패턴은 개체의 행동을 변경할 수 있다. 전략 패턴은 교체를 하고 디코레이터 패턴은 기존 개체에 새로운 기능을 추가한다.

 

4. 의존성 주입 패턴 

- 두 패턴은 개체의 행동을 변경하고 의존성을 외부에서 주입한다. 전략 패턴은 구체적인 알고리즘을 런타임에 주입하는 데 중점을 두고 의전성 주임은 개체의 의존성을 주입하여 결합도를 낮추는데 중점을 둔다. 

 

5. 커맨드 패턴 

- 두 패턴은 개체의 행동을 캡슐화한다. 커맨드 패턴은 작업 자체를 캡슐화 하여 나중에 실행할 수 있게하고 전략 패턴은 교체 가능하게 한다. 

 

 

헛갈리기 쉬우기에 이렇게 정리를 해보았다.