전략 패턴

김주형·2022년 11월 14일
0

자라기

목록 보기
9/22

🙇🏻‍♂️ Reference

전략 패턴

  • 한 과일 매장은 상황에 따라 다른 가격 할인 정책을 적용하고 있다.
  • 매장을 열자마자 들어온 첫 손님을 위한 '첫 손님 할인' 정책과 저녁 시간대에 신선도가 떨어진 과일에 대한 '덜 신선한 과일 할인' 정책이 있다.
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% 할인
    }
    return sum;
}

문제점

  • 서로 다른 계산 정책들이 한 코드에 섞여있다. 정책이 추가될수록 코드 분석을 어렵게 만든다.
  • 가격 정책이 추가될 때마다 calculate 메서드를 수정하는 것이 점점 어려워진다.
    • 새로운 가격 정책이 추가될 경우 파라미터, if 블록이 더 추가되어야 할 것이다.

전략패턴?
특정 콘텍스트에서 전략을 별도로 분리하는 설계 방법

  • 전략 : 구현 로직(할인 방법)을 추상화
    • DiscountStrategy 인터페이스는 상품의 할인 금액 계산을 추상화했다.
  • 콘텍스트 : 가격 계산 자체를 책임지는 Calculator
  • 콘크리트(연산에 대한 구현) 클래스는 상황에 맞는 할인 계산 로직을 제공한다.

전략 패턴에서 콘텍스트는 사용할 전략을 직접 선택하지 않는 대신,
클라이언트가 콘텍스트에 사용할 전략을 전달해준다. (의존성 주입 이용)
그리고 전략이 어떤 메서드를 제공할 지의 여부는 콘텍스트가 전략을 어떤 식으로 사용하느냐에 따라 달라진다.


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;
}
  • 금액 할인 정책을 DiscountStrategy로 분리한 경우로 가정
  • 생성자를 통해 사용할 전략 객체를 전달받는다.
  • calculate() 메서드에서 각 Item의 가격을 계산할 때 전략 객체를 사용
  • Calculator는 각 Item 별로 할인 정책을 적용하고 있으므로 DiscountStrategy 인터페이스는
public interface DiscountStrategy {
	int getDiscountPrice(Item item);
}

만약 각 아이템 별로 할인 정책이 있고 전체 금액에 대한 할인 정책이 별도로 필요하다면?

public interface DiscountStrategy {
	int getDiscountPrice(Item item);
    int getDiscountPrice(int totalPrice);
}

또는, 전체 금액 할인 정책을 위한 전략을 별도 인터페이스로 분리할 수도 있다.

public interface DiscountStrategy {
	int getDiscountPrice(Item item);
}
public interface TotalPriceDiscountStrategy {
	int getDiscountPrice(int totalPrice);
}

전략 객체는 콘텍스트를 사용하는 클라이언트에서 직접 생성한다.
첫 번째 손님에 대해 할인을 해주는 구현체가 있다고 가정해보자.

public class FirstGuestDiscountStrategy implements DiscountStrategy {
	@Override
    public int getDisconutPrice(Item item) {
    	return (int) (item.getPrice() * 0.9);
    }
}
private DiscountStrategy discountStrategy;

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

public void onCalculationButtonClick() {
	// 계산 버튼 누를 때 실행
	Calculator calculator = new Calulator(strategy);
    int price = calculator.calculate(items);
}
  • Calculator 사용하는 코드에서 FirstGuestDiscountStrategy 객체를 생성
  • 콘텍스트를 사용하는 클라이언트가 전략의 상세 구현에 대한 의존 발생

새로운 정책 추가

전략 패턴을 적용할 때 얻을 수 있는 이점은 콘텍스트 코드의 변경없이 새로운 전략을 추가할 수 있다는 점이라고 한다.
마지막 손님 대폭 할인 정책을 추가한다고 가정해보자.

private DiscountStrategy discountStrategy;

public void onFirstGuestButtonClick() {
	// 마지막 손님 대폭 할인 버튼 누를 때 생성
	strategy = new LastGuestDiscountStrategy();
}

public void onCalculationButtonClick() {
	// 계산 버튼 누를 때 실행
	Calculator calculator = new Calulator(strategy);
    int price = calculator.calculate(items);
}
  • 전략 패턴 적용을 통해 Calculator 클래스는 할인 정책 확장에는 열려 있고 변경에는 닫혀 있게 된다. - OCP를 따르는 구조

전략 패턴은 언제 사용할까?

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

0개의 댓글