일반적으로 매우 복잡한 형태의 if-else 분기나 switch-case 분기를 피하기 위해 사용
템플릿 메서드 패턴과 마찬가지로 프레임 워크를 확장하는 데 사용
각 로직을 개별적으로 캡슐화,
이를 서로 교환 가능하게 만드는 것.
--> 사용하는 클라이언트와는 독립적으로 로직을 변경할 수 있음.
## 전략의 생성 생성 논리를 캡슐화하고 클라이언트 코드에서 생성과 관련된 세부 정보를 보호하기 위해 유형에 따라 생성 전략의 논리를 추출하여 팩터리 클래스에 넣음
전략 클래스가 stateless인 경우
public class StrategyFacory {
private static final Map<String, Strategy> strategies = new HashMap<>()
};
static {
strategies.put("A", new ConcreteStrategyA);
strategies.put("B", new ConcreteStrategyB);
}
public static Strategy getStrategy(String type) {
if (type == null || type.isEmpty())
throw new IllegalArgumentException("type should not be empty.");
}
retunr strategies.get(type);
}
전략 클래스가 멤버 변수를 포함하지 않고 순수한 알고리즘 구현만 포함한다면(= 전략 클래스가 stateless라면),
이 전략 객체를 공유하여 사용 가능.
매번 getStrategy()를 호출할 때 새로운 전략 객체를 생성할 필요가 없음.
따라서 이러한 상황에 대응하기 위해
이 코드에서 팩터리 클래스의 구현을 차용하여 미리 전략 객체를 전부 생성하여 팩터리 클래스에 캐시한 다음,
사용시에 이 캐시 객체를 직접 반환할 수 있음
전략 클래스가 stateful인 경우
반대로 전략 클래스가 stateful인 경우 비즈니스 시나리오의 필요에 따라
매번 팩터리 메서드에서 새로 생성된 전략 객체를 얻으려면 전략 팩터리 클래스를 구현해야 함
public class StrategyFactory {
public static Strategy getStrategy(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentExcetption("type should not be empty.");
}
if (type.equals("A")) {
return new ConcreteStrategyA();
} else if (type.equals("B")) {
return new ConcreteStrategyB();
}
return null
}
}
전략의 사용
가장 자주 사용되는 방식은 실행 시간 역학을 통해 사용할 전략을 동적으로 결정하는 것임.
여기서 실행 시간 역학이란
어떤 전략을 사용할지 미리 알지 못하지만 런타임에 설정, 사용자의 입력, 계산 결과와 같은 미확정 요소를 기반으로 사용할 전략을 동적으로 결정한다는 의미임.
// 전략 인터페이스: EvictionStrategy
// 전략 클래스: LruEvictionStrategy, FifoEvctiionStrategy, LfuEvictionStrategy ...
// 전략 팩터리: EvictionStrategyFactory
public class UserCache {
private Map<String, User> cacheData = new HashMap<>();
private Eviction Strategy eviction;
public UserCache(EvictionStrategy eviction) {
this.eviction = eviction;
}
...
}
런타임에 미확정 요소 기반으로 동적 전략 결정
// 실행 시간 동적 결정, 즉 설정 파일을 기반으로 사용할 전략
public Application {
public static void main(String[] args) throws Exception {
EvictionStrategy evictionStrategy = null;
Properties props = new Properties();
props.load(new FileInputStream("./config.properties"));
String type = props.getProperty("eviction_type");
evictionStrategy = EvictionStrategyFactory.getEvictionStrategy(type);
UserCache userCache = new UserCache(evictionStrategy);
...
}
}
비실행 시간 동적 결정이 전략 패턴을 활용하지 않음.
=> 실제로 전략 패턴이 객체지향의 다형성 또는 구현이 아닌 인터페이스 기반으로 퇴화함.
// 비실행 시간 동적 결정, 즉 코드에서 사용할 전략 지정
public class Applicatoin {
public static void main(String[] args) {
...
EvictionStratgy evictionStrategy = new LruEvictionStrategy();
UserCache userCache = new UserCache(evictionStrategy);
...
}
}
전략 패턴으로 분기 결정 대체
전략의 정의, 생성, 사용을 직접 작성하는 경우
public class OrderService {
public double discount(Order order) {
double discount = 0.0;
OrderType type = order.getTpye():
if (type.equals(OrderType.NORMAL)) { // 정상 주문
// 할인 적용 코드
} else if (type.equals(OrderType.GROUPON)) { //단체 주문
// 할인 적용 코드
} else if (type.equals(OrderType.PROMOTION)) { // 프로모션 주문
// 할인 적용 코드
}
return discount;
}
}
이렇게 장황한 분기 판단을 전략 패턴으로 유용하게 제거할 수 있음.
먼저 다양한 주문 유형에 대한 할인 전략을 전략 클래스로 설계하고,
팩터리 클래스는 전략 객체의 생성을 담당하도록 함.
// 전략 정의
public interface DiscountStrategy {
double calDiscount(Order order);
}
// NormalDiscountStrategy, GroupDiscountStrategy, PromotionDiscountStratgy 생략
// 전략 생성
public class DiscountStrategyFactory {
private static final Map<OrderType, DiscountStrategy> strategies = new HashMap<>();
static {
strategies.put(OrderType.NORMAL, new NormalDiscountStrategy());
strategies.put(OrderType.GROUPON, new GrouponDiscountStrategy());
strategies.put(OrderType.PROMOTION, new PromotionDiscountStrategy());
}
public static DiscountStrategy getDiscountStrategy(OrderType type) {
return strategies.get(type);
}
}
// 전략 사용
public class OrderService {
public double discount(Order order) {
OrderType type = order.getType();
DiscountStrategy discountStrategy = DisCountStrategyFactory.getDiscountStrategy(type);
return discountStrategy.calDiscount(order);
}
}
Map 캐시 전략을 통해 type에 따라 해당 전략을 직접 가져오므로 if-else 분기 판단을 제거할 수 있음.
그러나
매번 다른 전략 객체를 생성해야 하는 경우
public calss DiscountStrategyFactory {
public static DiscountSTrategy getDiscountStrategy(OrderType type) {
if (type == null) {
throw new IllegalArgumentException("Type shoud not be null.");
}
if (type.equals(OrderType.NORMAL)) {
return new NormalDiscountStrategy();
} else if (type.equals(OrderType.GROUPON)) {
return new GrouponDiscountStrategy();
} else if (type.equals(OrderType.PROMOTION)) {
return new PromotionDiscountStrategy();
}
return null;
}
}
기존의 if-else 분기 판단 부분을 OrderService 클래스에서 팩터리 클래스로 옮기기는 하지만 제거하지는 않음.
이럴 때의 이점은?
(전략의 생성을 디커플링) + if-else 분기의 복잡한 생성 코드를 팩터리 클래스에 캡슐화하여 코드 작성이 쉬워짐
전략 패턴의 역할은 전략의 정의, 생성, 사용을 분리하여 코드가 복잡해지는 것을 방지하고,
각각의 부분을 최대한 단순화하는 것임.
(굳이 if-else가 복잡하거나 크지 않은데 전략으로 교체하는 것은 과도한 작업임 ;;;)
'기타' 카테고리의 다른 글
| 지피티야 미안해 (0) | 2025.06.29 |
|---|---|
| 카프카 2트 (2) | 2025.04.28 |
| ArrayList와 HashSet 만들어보기.. C++ (1) | 2025.03.03 |
| Vim 마스터의 길... (0) | 2025.03.03 |
| 순서복잡도 (Time Complexity) (0) | 2025.03.02 |