Spring

Spring Boot에서 전략 패턴 사용하기

웅둘 2024. 3. 29. 16:59

전략패턴

  • 전략 패턴은 런타임 중에 알고리즘 전략을 선택하여 객체 동작을 실시간으로 바뀌도록 할 수 있게 하는 행위 디자인 패턴
  • 어떤 일을 수행하는 알고리즘이 여러가지 일때, 동작들을 미리 전략으로 정의함으로써 손쉽게 전략을 교체할 수 있는, 알고리즘 변형이 빈번하게 필요한 경우 적합한 패턴이다.

 

첫 번째 시도

우선 전략 패턴을 위한 인터페이스와 enum을 작성하였다.

public interface Strategy {
	void execute();
}

@Getter
@RequiredArgsConstructor
public enum StrategyType {
	A("strategyA"), B("strategyB"), C("strategyC")
	
	private final strategyName;
}

전략 패턴을 위한 A,B,C 알고리즘을 작성

 

@Component
public class StrategyA implements Strategy {
	@Override
	public void execute() {
		// A 실행
	}
}

@Component
public class StrategyB implements Strategy {
	@Override
	public void execute() {
		// B 실행
	}
}

@Component
public class StrategyC implements Strategy {
	@Override
	public void execute() {
		// C 실행
	}
}

StrategyFactory을 만들고, 모든 전략들을 Map을 이용해서 저장한다.

 

@Component
@RequiredArgsConstructor 
public class StrategyFactory {
	private final Map<String, Strategy> strategyMap;
	
	public void execute(StrategyType strategyType) {
		Strategy strategy = strategyMap.get(strategyType.getStrategyName());
		strategy.excute();
	}
}

Map<String, Strategy>

  • Key : 스프링 빈의 이름
  • Value : 스프링 빈

Map에 전략들이 저장되어 있기 때문에 StrategyType에 따라 로직을 실행할 시킬 수 있다.

 

문제점

각 전략에 대한 스프링 빈의 이름을 Enum StrategyType의 strategyName 이름을 직접 넣고 있다.

그렇기 때문에 전략을 구현한 클래스의 이름이 변경되어도 컴파일 에러가 나지 않기 때문에 문제를 일으킬 것이라 생각했다.

 

 

두 번째 시도

public interface Strategy {
	void execute();
	boolean isStrategyType(StrategyType strategyType);
}

public enum StrategyType {
	A, B, C
}
  • enum의 strategyName을 제거
  • 전략 패턴 구현 클래스는 동일

 

@Component
public class StrategyA implements Strategy {
	@Override
	public void execute() {
		// A 실행
	}
	
	@Override
	public boolean isStrategyType(StrategyType strategyType) {
		return strategyType == StrategyType.A;
	}
}

@Component
public class StrategyB implements Strategy {
	@Override
	public void execute() {
		// B 실행
	}

	@Override
	public boolean isStrategyType(StrategyType strategyType) {
		return strategyType == StrategyType.B;
	}
}

@Component
public class StrategyC implements Strategy {
	@Override
	public void execute() {
		// C 실행
	}
	
	@Override
	public boolean isStrategyType(StrategyType strategyType) {
		return strategyType == StrategyType.C;
	}	
}
  • boolean isStrategyType 구현

 

@Component
@RequiredArgsConstructor 
public class StrategyFactory {
	private final List<Strategy> strategies;
	
	public void execute(StrategyType strategyType) {
		Strategy strategy = strategyMap.getStrategy(strategyType.getStrategyName());
		strategy.excute();
	}
	
	private Strategy getStrategy(StrategyType strategyType) {
		return strategies.stream()
				.filter(strategy -> strategy.isStrategyType(strategyType))
				.findAny()
				.orElseThrow(() -> new IllegalArgumentException("오류"));
	}
}
  • List로 만들고 반복문을 통해 입력 받은 전략과 일치하는 전략 알고리즘을 사용하였다.

 

문제점

첫 번째 시도에서 Map을 사용하였고 전략을 가져오는 시간이 O(1) 이였다.

전략에 대한 구현 클래스가 많지는 않겠지만, List로 변경됨으로써 전략을 가져오는 시간이 O(n)이 되어버린게 다소 찝찝하다.

 

 

세 번째 시도

public interface Strategy {
	void execute();
	StrategyType getStrategyType();
}

public enum StrategyType {
	A, B, C
}
  • getStrategyType 메소드 추가

 

@Component
public class StrategyA implements Strategy {
	@Override
	public void execute() {
		// A 실행
	}
	
	@Override
	public StrategyType getStrategyType() {
		return StrategyType.A;
	}
}

@Component
public class StrategyB implements Strategy {
	@Override
	public void execute() {
		// B 실행
	}

	@Override
	public StrategyType getStrategyType() {
		return StrategyType.B;
	}
}

@Component
public class StrategyC implements Strategy {
	@Override
	public void execute() {
		// C 실행
	}
	
	@Override
	public StrategyType getStrategyType() {
		return StrategyType.C;
	}
}

 

 

@Component
public class StrategyFactory {
	private final Map<StrategyType, Strategy> strategyMap;

	@Autowired
	public StrategyFactory(Set<Strategy> StrategySet) {
		createStrategy(StrategySet);
	}
	
	private void createStrategy(Set<Strategy> StrategySet) {
		strategyMap= new HashMap<>();
		strategyMap.forEach(
			strategy-> strategyMap.put(StrategySet.getStrategyType(), strategy)
		);
	}

	public Strategy findStrategy(Strategy strategy) {
		return strategyMap.get(strategy);
	}
	
}
@Service
@RequiredArgsConstructor
public class DomainService {
	private StrategyFactory strategyFactory;
	
	public void execute(StrategyType strategyType) {
		 Strategy strategy = strategyFactory.findStrategy(StrategyName.StrategyA);
		 
		 strategy.execute();
	}
}

첫 번째 방식에서 전략을 담은 자료구조 Map과

두 번째 방식에서 전략을 찾기 위해 사용했던 메소드 방식을 합쳐서 나온 결과이다.

마지막 방식은 각각의 방식에서 사용했던 문제점을 해결한 방식인 것 같아 만족스러웠다.