![[Spring 핵심원리 - 고급] 전략 패턴(디자인 패턴)](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FERrD8%2FbtsNeQQ7IYW%2Fx7Nhzp65euU2PcfFMy1ZF0%2Fimg.png)
[Spring 핵심원리 - 고급] 전략 패턴(디자인 패턴)Back-End/Spring2025. 4. 10. 14:43
Table of Contents
이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.
전략 패턴
선 조립 후 실행
GOF 디자인 패턴에서 정의한 전략 패턴의 의도는 다음과 같다.
고리즘 제품군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만들자.
전략을 사용하면 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변경할 수 있다.
- 전략 패턴(Strategy Pattern)이란 행동(알고리즘)을 객체로 분리하여 동적으로 교체할 수 있도록 해주는 디자인 패턴이다.
- 쉽게 말해서, 상황에 따라 알고리즘이나 기능을 바꾸고 싶을 때 유용한 방법이다.
- 행동(전략)을 인터페이스로 정의하고, 그 행동을 구현한 여러 클래스를 만든 다음, 컨텍스트(Context)에서 그 전략 객체를 주입받아 사용하는 방식이다.
public interface Strategy {
void call();
}
@Slf4j
public class StrategyLogic1 implements Strategy {
@Override
public void call() {
log.info("비즈니스 로직1 실행");
}
}
@Slf4j
public class StrategyLogic2 implements Strategy {
@Override
public void call() {
log.info("비즈니스 로직2 실행");
}
}
@Slf4j
public class ContextV1 {
private Strategy strategy;
public ContextV1(Strategy strategy) {
this.strategy = strategy;
}
public void execute() {
long startTime = System.currentTimeMillis();
//비즈니스 로직 실행
strategy.call(); //위임
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
- ContextV1 은 변하지 않는 로직을 가지고 있는 템플릿 역할을 하는 코드이다. 전략 패턴에서는 이것을 컨텍스트(문맥)이라 한다.
- 쉽게 이야기해서 컨텍스트(문맥)는 크게 변하지 않지만, 그 문맥 속에서 strategy 를 통해 일부 전략이 변경된다 생각하면 된다.
- Context 는 내부에 Strategy타입의 strategy 필드를 가지고 있다. 이 필드에 변하는 부분인 Strategy 의 구현체를 주입하면 된다.
- 전략 패턴의 핵심은 Context 는 Strategy 인터페이스에만 의존한다는 점이다. 덕분에 Strategy 의 구현체를 변경하거나 새로 만들어도 Context 코드에는 영향을 주지 않는다.
- 스프링에서 의존관계 주입에서 사용하는 방식이 바로 전략 패턴이다.
@Slf4j
public class ContextV2Test {
// 전략 패턴 적용
@Test
void strategyV1() {
ContextV2 context = new ContextV2();
context.execute(new StrategyLogic1());
context.execute(new StrategyLogic2());
}
}
- 코드를 보면 의존관계 주입을 통해 ContextV1 에 Strategy 의 구현체인 strategyLogic1 를 주입하는 것을 확인할 수 있다.
- 이렇게해서 Context 안에 원하는 전략을 주입한다. 이렇게 원하는 모양으로 조립을 완료하고 난 다음에 context1.execute() 를 호출해서 context 를 실행한다.
- Context 에 원하는 Strategy 구현체를 주입한다.
- 클라이언트는 context 를 실행한다.
- context 는 context 로직을 시작한다.
- context 로직 중간에 strategy.call() 을 호출해서 주입 받은 strategy 로직을 실행한다.
- context 는 나머지 로직을 실행한다.
당연히 익명 구현 클래스 및 람다도 사용할 수 있다.
@Slf4j
public class ContextV2Test {
// 전략 패턴 익명 내부 클래스
@Test
void strategyV2() {
ContextV2 context = new ContextV2();
context.execute(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직1 실행");
}
});
context.execute(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직2 실행");
}
});
}
// 전략 패턴 익명 내부 클래스2, 람다
@Test
void strategyV3() {
ContextV2 context = new ContextV2();
context.execute(() -> log.info("비즈니스 로직1 실행"));
context.execute(() -> log.info("비즈니스 로직2 실행"));
}
}
선 조립, 후 실행
Context의 내부 필드에 Strategy를 두고 사용하는 이 방식은 Context와 Strategy를 실행 전에 원하는 모양으로 조립해두고, 그다음에 Context를 실행하는 선 조립, 후 실행 방식에서 매우 유용하다.
Context와 Strategy를 한 번 조립하고 나면, 이후로는 Context를 실행하기만 하면 된다.
우리가 스프링으로 애플리케이션을 개발할 때, 애플리케이션 로딩 시점에 의존관계 주입을 통해 필요한 의존관계를 모두 맺어두고 난 다음에 실제 요청을 처리하는 것과 같은 원리이다.
이 방식의 단점은 Context와 Strategy를 조립한 이후에는 전략을 변경하기가 번거롭다는 점이다.
물론 Context에 setter를 제공해서 Strategy를 넘겨 받아 변경하면 되지만, Context를 싱글톤으로 사용할 때는 동시성 이슈 등 고려할 점이 많다.
그래서 전략을 실시간으로 변경해야 하면, 차라리 이전에 개발한 테스트 코드처럼 Context를 하나 더 생성하고 그곳에 다른 Strategy를 주입하는 것이 더 나은 선택일 수 있다.
선 실행 후 조립(템플릿 콜백 패턴)
- 이 방식은 전략이 자주 바뀌거나, 다양한 전략을 상황에 따라 적용해야 할 때 매우 유용하다.
- 예를 들어, 사용자 요청마다 다른 할인 정책, 다른 정렬 기준, 또는 다른 필터링 조건을 적용해야 하는 경우 등에서 이 구조가 잘 맞는다.
- 전략을 외부에서 주입받아 실행할 때마다 바꿔 쓸 수 있다는 점에서, 전략 객체를 일급 객체처럼 사용하는 구조라고도 볼 수 있다.
스프링에서는 JdbcTemplate , RestTemplate , TransactionTemplate , RedisTemplate 처럼 다양한 템플릿 콜백 패턴이 사용된다.
스프링에서 이름에 XxxTemplate 가 있다면 템플릿 콜백 패턴으로 만들어져 있다 생각하면 된다.
@Slf4j
public class ContextV2 {
public void execute(Strategy strategy) {
long startTime = System.currentTimeMillis();
//비즈니스 로직 실행
strategy.call(); //위임
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
ContextV2 는 전략을 필드로 가지지 않는다. 대신에 전략을 execute(..) 가 호출될 때 마다 항상 파라미터로 전달받는다.
@Slf4j
public class ContextV2Test {
// 전략 패턴 적용
@Test
void strategyV1() {
ContextV2 context = new ContextV2();
context.execute(new StrategyLogic1());
context.execute(new StrategyLogic2());
}
}
- Context와 Strategy를 '선 조립 후 실행'하는 방식이 아니라, Context를 실행할 때마다 전략을 파라미터로 전달한다.
- 클라이언트는 Context를 실행하는 시점에 원하는 Strategy를 전달할 수 있다.
- 따라서 이전 방식과 비교해서 원하는 전략을 더욱 유연하게 변경할 수 있다.
- 코드를 보면 하나의 Context만 생성한다. 그리고 하나의 Context에 실행 시점에 여러 전략을 인수로 전달해서 유연하게 실행하는 것을 확인할 수 있다.
- 클라이언트는 Context 를 실행하면서 파라미터로 Strategy 를 전달한다.
- Context 는 execute() 로직을 실행한다.
- Context 는 파라미터로 넘어온 strategy.call() 로직을 실행한다.
- Context 의 execute() 로직이 종료된다.
당연히 익명 구현 클래스 및 람다도 사용할 수 있다.
@Slf4j
public class ContextV2Test {
// 전략 패턴 익명 구현 클래스
@Test
void strategyV2() {
ContextV2 context = new ContextV2();
context.execute(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직1 실행");
}
});
context.execute(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직2 실행");
}
});
}
// 전략 패턴 익명 구현 클래스, 람다
@Test
void strategyV3() {
ContextV2 context = new ContextV2();
context.execute(() -> log.info("비즈니스 로직1 실행"));
context.execute(() -> log.info("비즈니스 로직2 실행"));
}
}
'Back-End > Spring' 카테고리의 다른 글
[Spring 핵심원리 - 고급] 동적 프록시(Dynamic Proxy) (0) | 2025.04.15 |
---|---|
[Spring 핵심원리 - 고급] 프록시 및 데코레이터 패턴(디자인 패턴) (0) | 2025.04.11 |
[Spring 핵심원리 - 고급] 템플릿 메서드 패턴(디자인 패턴) (0) | 2025.04.10 |
[Spring 핵심원리 - 고급] 쓰레드 로컬(Thread Local) (1) | 2025.04.09 |
[Spring] 스프링 컨테이너(IoC, DI 컨테이너) (0) | 2025.04.02 |