티스토리 뷰

Strategy Pattern (전략 패턴)

특정 컨텍스트(Context)에서 알고리즘, 전략(Strategy)을 별도로 분리하는 설계 방법

예시 ) 과일 매장의 할인 정책

한 과일 매장은 현재 2가지의 할인 정책을 열고 있다.

  1. 매장에 처음으로 들어온 첫 손님을 위한 '첫 손님 할인' 정책
  2. 저녁시간대 신선도가 떨어진 과일에 대한 '덜 신선한 과일 할인' 정책
    물론 이 2가지 외에 추가적으로 할인 정책이 더 생길 가능성이 있다.
public class Calculator {
    public int calculate(boolean firstGuest. List<Item> items) {
        int sum = 0;

        for(Item item : items) {
            if(firstGuest) {
                sum += (int)(item.getPrice() * 0.9); // 첫 손님 10% 할인
            } else if(!item.isFresh()) {
                sum += (int)(item.getPrice() * 0.8); // 덜 신선한 것 20% 할인
            } else {
                sum += item.getPrice();
            }
        }

        return sum;
    }
}

if-else 로직을 보니 정체모를 찝찝함과 함께 얼굴이 찌뿌려진다. 다행히 근 한달간 우테코에서 뻘공부한건 아닌것 같다.

예상하다시피,
서로다른 가격 정책들이 한곳에 섞여 있어, 코드가 복잡하고 정책이 추가될때마다 코드수정이 어려워진다.
이를 해결하기 위해,
가격 할인 정책을 별도 객체로 분리해보자.

DiscountStrategy 인터페이스는 할인금액 계산을 추상화하였다.
각 콘크리트 클래스는 상황에 맞는 할인계산 알고리즘을 제공한다.
Calculator 클래스는 가격 합산 계산의 책임을 진다.

여기서
가격할인 알고리즘을 추상화하고 있는 DiscountStrategy를 전략(Strategy)라 부르고
가격계산 기능자체의 책임을 갖고있는 Calculator를 콘텍스트(Context)라 부른다.

이렇게, 특정 Context에서 alg, 즉 전략을 분리하는 설계방법이 전략패턴이다.

전략패턴에서 context는 사용할 전략을 직접 선택하지 않는다.
대신, context의 client가 사용할 전략을 전달해준다. 즉 DI를 이용해 전략을 전달받는다.

이렇게 분리하면 다음과 같이 코드를 구성할 수 있다.

public class Calculator {
    private DiscountStrategy discountStrategy;

    public Calculator(DiscountStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }

    public int calculate(List<Item> items) {
        int sum = 0;

        for(Item item : items) {
            sum += discountStrategy.getDiscountPrice(item);
        }

        return sum;
    }
}
public interface DiscountStrategy {
    int getDiscountPrice(Item item);
}
public class FirstGuestDiscountStrategy implements DiscountStrategy {
    @Override
    public int getDiscountPrice(Item item) {
        return (int)(item.getPrice() * 0.9);
    }
}
private DiscountStrategy strategy;

public void onFirstGuestButtonClick() {
    // 첫 손님 할인 버튼 누를 때 생성 됨
    strategy = new FirstGuestDiscountStrategy();
}

public void onCalculationButtonClick() {
    // 계산 버튼 누를 때 실행 됨
    Calculator cal = new Calculator(strategy);
    int price = cal.calculate(items);
    ...
}

가장 마지막 코드를 보면
Caculator를 사용하는 코드에서 FirstGuestDiscountStrategy 객체를 생성하고 있다.
이는 Context를 사용하는 Client가 전략의 상세구현에 대한 의존이 발생한다는 것을 의미한다.

context의 client가 전략의 interface가 아닌 상세구현을 안다는 사실이 문제처럼 보일수있으나,
client의 코드와 전략의 콘크리트 클래스가 쌍을 이루기 때문에 유지보수 문제가 오히려 줄어든다.
즉, 함께 쌍으로 다니며 함께 추가되고 함께 삭제된다는 것.

전략패턴을 이용한 이점은 context 코드의 변경없이 새로운 전략을 추가할 수 있다.
또 Calculator 클래스는 할인정책 확장엔 열려있고 변경엔 닫혀있는 개방-폐쇄 원칙(OCP)도 따르게 된다.

※ 물론 새로운 공통 메소드가 필요해진다면 추가할수도 있고, 새로운 인터페이스를 추가해도 되고 상황에 맞게

참고자료, 출처

OOP - 7 - 주요 디자인 패턴(1)

※ 함께 읽어볼만한 링크
스트래티지 패턴이란 - Heee's Development Blog

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함