객체지향언어에서 활용할 수 있는 전략패턴에 대해서 포스팅 하고자 한다. 사실 디자인 패턴이라는 시리즈를 따로 팔까 고민했었다. 고민 끝에 토비에 스프링에서 OCP와 관련해서 소개한 패턴인 만큼, 스프링 시리즈에서 소개하는게 맞다고 생각해서 여기에 기록을 남긴다.
전략 패턴(strategy pattern) 또는 정책 패턴(policy pattern)은 실행 중에 알고리즘을 선택할 수 있게 하는 행위 소프트웨어 디자인 패턴이다. 전략 패턴은 특정한 계열의 알고리즘들을 정의하고 각 알고리즘을 캡슐화하며 이 알고리즘들을 해당 계열 안에서 상호 교체가 가능하게 만든다.
전략은 알고리즘을 사용하는 클라이언트와는 독립적으로 다양하게 만든다. 전략은 유연하고 재사용 가능한 객체 지향 소프트웨어를 어떻게 설계하는지 기술하기 위해 디자인 패턴의 개념을 보급시킨 디자인 패턴(Gamma 등)이라는 영향력 있는 책에 포함된 패턴들 가운데 하나이다. — 위키피디아
결론적으로 특정 컨텍스트에서 알고리즘을 별도로 분리하는 설계 방법을 의미한다. 여기서 컨텍스트
와 알고리즘
이라는 키워드를 기억하는게 출발이다.
특정 상품을 판매한다고 가정해보자. 손님에 따라 (등급이나 선착순) 차등적으로 할인율을 제공해준다고 가정해보자. 서버 개발자는 어떻게 로직을 구현해서 결제 시스템에 적용할 수 있을까??
public class Calculator {
public double calculate(boolean isFirstGuest, boolean isLastGuest, List<Item> items) {
double sum = 0;
for (Item item : items) {
if (isFirstGuest) {
sum += item.getPrice() * 0.9;
} else if (!item.isFresh()) {
sum += item.getPrice() * 0.8;
} else if (isFirstGuest) {
sum += item.getPrice() * 0.8;
} else {
sum += item.getPrice();
}
}
return sum;
}
}
public class Item {
private final String name;
private final int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public int getPrice() {
return price;
}
public boolean isFresh() {
return true;
}
}
위 예제 코드에서 Calculator 클래스에 calculate 메서드를 확인해보자. 간단한 예제임에도 생각에 오래 잠기게 될 것이다. 만일 할인율이 점점 많아지고, 시기에 따라서 변경해줘야 한다면?? 상상해보면 개발자의 노동은 굉장히 늘어나게 될 것이다.
우선적으로 할인율 계산이라는 항목을 추상화해보자. 아래에서 인터페이스를 구현하고 할인율 클래스를 구체화 해보자.
public interface DiscountPolicy {
double calculateWithDisCountRate(Item item);
}
public class FirstCustomerDiscount implements DiscountPolicy{
@Override
public double calculateWithDisCountRate(Item item) {
return item.getPrice() * 0.9;
}
}
public class LastCustomerDiscount implements DiscountPolicy{
@Override
public double calculateWithDisCountRate(Item item) {
return item.getPrice() * 0.8;
}
}
public class UnFreshFruitDiscount implements DiscountPolicy{
@Override
public double calculateWithDisCountRate(Item item) {
return item.getPrice() * 0.8;
}
}
위 예제에서 한눈에 First, Last, UnFreashFruit 이라는 항목들로 할인율 구체화 클래스들을 확인 할 수있고, 각 클래스마다 할인율 정첵이 달라 질 수 있음을 확인 할 수 있다.
public class Calculator {
private final DiscountPolicy discountPolicy;
public Calculator(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
public double calculate(List<Item> items) {
double sum = 0;
for (Item item : items) {
sum += discountPolicy.calculateWithDisCountRate(item);
}
return sum;
}
}
계산을 하면서 개발자가 해줘야 할일은 DiscountPolicy 인터페이스를 활용해서 할인율을 다형성을 갖춰서 의존도를 낮춰주는 것, 단지 그것 뿐이다.
즉 할인율을 고려한 비용계산
이라는 컨텍스트에서 각 인터페이스에 맞게 알고리즘
을 구현하여 Dependency Inversion policy와 OCP를 준수할 수 있게 해주는 것 그것이 바로 전략 패턴이다.
무엇보다도 컨텍스트 코드의 변경 없이 새로운 전략을 추가할 수 있다는 점이다.
단점이라고 하긴 그렇지만, 굳이 사용하지 않아도 되는 부분에서는 사용하지 않는게 나을 수도 있다. 또한 테스트케이스 확인시에 매번 수행 알고리즘을 구현해준다라던지, 아니면 컨텍스트 테스트를 위해 Mock 오브젝트를 생성해줘야 한다라던지..?