Design Pattern

전략 패턴 (Strategy Pattern)

테리는당근을좋아해 2022. 9. 6. 02:01

전략(Strategy) 패턴

예제

public class Calculator {
    public int calculator(boolean firstGuest, List<Item> items) {
        int sum = 0;
        for (Item item : items) {
            if (firstGuest)
                sum += (int) (item.getPrice() * 0.9)
            else if (! item.isFresh())
                sum += (int) (item.getPrice() * 0.8)
            else
                sum += item.getPrice()
        }
        return sum;
    }
}
  • 위 코드는 첫번째 손님인지 또는 상품의 신선도 여부에 따라 다른 가격 정책을 사용하고 있다.

 

문제점

  • 서로 다른 계산 정책들이 한 코드에 섞여, 정책이 추가될수록 코드 분석을 어렵게 만든다.
  • 정책이 추가될 때마다 메서드를 수정하는 것이 어려워지고 if-else 블록이 추가된다.

 

 

1) Strategy Pattern 적용하기

전략 패턴 적용

  • 인터페이스(Strategy)로 계산 정책을 추상화
  • 콘크리트 클래스(Strategy Concrete)에서 상황에 맞는 계산 정책을 제공
  • 기능 자체에 대한 책임을 갖고 있는 클래스를 Context라 함
  • 컨텍스트는 사용할 전략을 직접 선택하는 대신 클라이언트가 컨테스트에 사용할 전략을 전달(DI)

 

Context Class

// Context
public class calculator {
    private DiscountStrategy discountStrategy;

    // 생성자를 통한 의존 주입
    public Calculator(DiscocuntStrategy discountStrategy) {
        this.discountStrategy = discountStrategy;
    }

    public int calculator(List<Item> items) {
        int sum = 0;
        for (Item item : items) {
            sum += discountStrategy.getDiscountPrice(item);
        }
        return sum;
    }
}
  • Context 클래스는 Strategy Interface 타입의 멤버 변수를 갖는다.
  • Context 클래스는 생성자를 통해서 사용할 전략 객체를 주입받는다.
  • Context 클래스는 주입 받은 전략 객체를 메서드를 통해 사용한다.

 

Strategy Interface

// Strategy Interface
public interface DiscountStrategy {
    int getDiscountPrice(Item itme);
}
  • Strategy Interface 에서는 Context에서 사용할 메서드의 정의를 담당한다.
  • 구현은 Strategy Interface를 상속받는 하위 클래스에서 한다.

 

Strategy Concrete Class

// Strategy Concrete Class
public class FisrtGuestDiscountStrategy implements DiscountStrategy {
    @Override
    public int getDiscountPrice(Item itme) {
        return (int) (item.getPrice() * 0.9);
    }
}
  • Strategy Interface를 상속받는 하위 클래스에서 메서드 정책을 구현한다.

 

Client Code

// client code 
public class testStrategy {
    private DiscountStrategy strategy;

    public void onFirstGuestButtonClick() {
        strategy = new FisrtGuestDiscountStrategy();
    }

    public void onCalculationButtonClick() {
        Calculator cal = new Calculator(strategy);
        int price = cal.calcaulate(items);
    }
}
  • 전략 객체는 Context를 사용하는 클라이언트에서 직접 생성
  • 컨텍스트를 사용하는 클라이언트가 전략의 상세 구현에 대한 의존이 발생
  • 컨텍스트의 클라이언트가 전략의 인터페이스가 아닌 상세 구현을 안다는 것이 문제처럼 보일 수 있으나, 전략의 콘크리트 클래스와 클라이언트의 코드가 쌍을 이루기 때문에 유지 보수 문제가 발생할 가능성이 줄어듦
  • 클라이언트 코드에서 전략 객체를 직접 생성하는 것은 오히려 코드 이해를 높이고 코드 응집을 높여주는 효과

 

 

2) 전략 패턴의 이점

  • 컨텍스트 코드의 변경 없이 새로운 전략을 추가( 클라이언트 코드에 객체를 생성해 주기만 하면 된다.)
  • 전략 패턴을 적용함으로써 확장에는 열려 있고 변경에는 닫혀 있음 → 개방 폐쇄 원칙을 따르는 구조

 

3) 전략 패턴을 적용할 수 있는 경우

  • if-else로 구성된 코드 블록이 비슷한 알고리즘을 수행하는 경우 전략 패턴을 적용함으로써 코드를 확장 가능하도록 변경할 수 있음
  • 완전히 동일한 기능을 제공하지만 성능의 장단점에 따라 알고리즘을 선택해야하는 경우에 사용할 수 있음