예제 코드로 이해해보는 디자인 패턴 - 전략 패턴(Strategy Pattern)

kindof

·

2023. 10. 29. 20:57

전략 패턴(Strategy Pattern)은 특정 작업에 대해 여러 가지 알고리즘을 적용해야 할 때, 사용자가 런타임에 실제로 사용할 구현체를 결정할 수 있도록 하는 패턴입니다.

 

전략 패턴은 여러 알고리즘을 각각의 클래스로 캡슐화하고 사용자는 특정 클래스를 런타임에 선택하여 해당 클래스가 가진 알고리즘을 동작하도록 합니다.

 

이번 글에서는 예제 코드를 작성해보면서 전략 패턴에 대해 이해해보고자 합니다.

 

1. 예제 코드

물건을 구입할 때 총 결제 금액을 계산해야 하는 상황에서 결제 수단에 따라 다른 방식의 계산법이 적용될 수 있습니다.

 

예를 들어, 카드로 계산할 때는 물건들의 총 금액을 합한 금액을 모두 지불해야 하지만 현금으로 결제하면 5% 할인 혜택을 받을 수 있다고 해보겠습니다.

 

이를 위해 아래와 같이 간단한 코드를 작성해봅니다.

 

[Item.java]

@Getter
public class Item {
    
    private String code;
    private int price;
    
}

 

[PaymentType.java]

public enum PaymentType {
    CASH,
    CREDIT,
    ;
}

 

[ShoppingService.java]

@Service
public class ShoppingService {

    public void pay(List<Item> itemList, PaymentType paymentType) {

        int totalPrice = itemList.stream().mapToInt(Item::getPrice).sum();

        switch (paymentType) {
            case CASH -> System.out.println("totalPrice = " + totalPrice * 0.95);
            case CREDIT -> System.out.println("totalPrice = " + totalPrice)
        }
    }
}

 

간단하게 작성한 코드이지만 이 코드에는 몇 가지 문제가 있습니다.

 

[1] 결제 수단(PaymentType)이 추가되면 case문을 추가하고 pay() 메서드의 코드를 수정해야 합니다.

[2] 결제 수단(PaymentType)이 할인 로직을 적용하는 역할을 해야하지만 실제 코드에서는 ShoppingService가 할인 로직까지 담당해야 합니다.

 

 

2. 전략 패턴 적용하기

위에서 살펴본 코드를 전략 패턴을 활용해 리팩토링해보겠습니다. 처음 설명한 것처럼 전략 패턴은 아래와 같은 구현을 모티브로 합니다.

 

여러 알고리즘을 각각의 클래스로 캡슐화하고 사용자는 특정 클래스를 런타임에 선택하여 해당 클래스가 가진 알고리즘을 동작하도록 한다.

 

따라서, 결제 수단에 따라 어떻게 할인 로직이 적용될지 각각의 클래스로 캡슐화합니다.

 

[PaymentStrategy.java]

public interface PaymentStrategy {

    void pay(List<Item> itemList);

}

 

[CashPayment.java]

public class CashPayment implements PaymentStrategy {

    private static final double discountRate = 0.05;

    @Override
    public void pay(List<Item> itemList) {

        int totalPrice = itemList.stream().mapToInt(Item::getPrice).sum();
        final double finalPrice = totalPrice - (totalPrice * discountRate);

        System.out.println("finalPrice = " + finalPrice);
    }
}

 

[CreditPayment.java]

public class CreditPayment implements PaymentStrategy {

    @Override
    public void pay(List<Item> itemList) {

        int totalPrice = itemList.stream().mapToInt(Item::getPrice).sum();

        System.out.println("finalPrice = " + totalPrice);
    }
}

 

[ShoppingService.java]

@Service
public class ShoppingService {

    public void pay(List<Item> itemList, PaymentStrategy paymentStrategy) {
        paymentStrategy.pay(itemList);
    }
}

 

전략 패턴을 사용하여 코드를 개선했습니다.

 

이제 새로운 결제 방식이 추가되어도 PaymentStrategy를 구현하는 구현체만 추가하면 됩니다. 결제 방식의 추가나 결제 로직의 유지 보수가 간단해졌습니다.

 

또한, 결제 방식의 변경이 ShoppingService에 영향을 미치지 않게 되었습니다.