2. 전략 패턴 - midoBanDev/design-pattern-java GitHub Wiki
전략패턴
- 알고리즘 제품군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만들자.
전략을 사용하면 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변경할 수 있다. - 변하지 않는 부분을 Context라는 곳에 두고, 변하는 부분은 Strategy라는 인터페이스를 만들어 해당 인터페이스를 구현하도록 해서 문제를 해결한다.
상속이 아니라 위임으로 문제를 해결하는 패턴이다. - 전략패턴에서는 Context는 변하지 않는 템플릿 역할을 하고, Strategy는 변하는 알고리즘 역할을 한다.
1. 전략패턴 : 전략을 필드에 보관하는 방식
선 조립 후 실행
- Context의 내부 필드에 Strategy를 두고 사용하는 부분을 보자. 이 방식은 Context와 Strategy를 실행 전에 원하는 모양으로 조립해두고, 그 다음에 Context를 실행하는 선 조립, 후 실행 방식에서 유용하다.
- Context와 Strategy를 한번 조립하고 나면 이후로는 Context를 실행하기만 하면 된다.
- 우리가 스프링으로 애플리케이션을 개발할 때 애플리케이션 로딩 시점에 의존관계 주입을 통해 필요한 의존관계를 맺어두고 난 다음에 실제 요청을 처리하는것과 같은 원리이다.
단점
- Context와 Strategy를 조립하고 난 후에는 전략을 변경하기 번거롭다는 점이다. 물론 Context에 Setter를 제공해서 Strategy를 넘겨받아 변경하면 되지만, Context를 싱글톤으로 사용할 때는 동시성 이슈 등 고려할 점이 많다. 그래서 전략을 실시간으로 변경해야 하면 차라리 Context를 하나더 생성하고 그곳에 다른 Strategy를 주입하는것이 더 나은 선택일 수 있다.
2. 전략패턴 : 전략을 파라미터로 받는 방식
실행 시점에 조립
- Context와 Strategy를 선 조립 후 실행하는 방식이 아니라 Context를 실행할 때 마다 전략을 인수로 전달한다. 클라이언트는 Context를 실행하는 시점에 원하는 Strategy를 전달할 수 있다. 따라서 이전 방식(선 조립 후 실행)과 비교하여 원하는 전략을 더욱 유연하게 변경할 수 있다.
- 또한 Context를 하나만 생성한다. 하나의 Context에 실행 시점에 여러 전략을 인수로 전달하여 유연하게 실행할 수 있다.
실제 우리가 주로 원하는 것은 변하지 않는 부분은 템플릿에 넣고, 변하는 부분을 우리가 실행하는 시점에 살짝 다른 코드로 실행하고 싶은 경우가 많다. 따러서 전략패턴 방식 중 파라미터로 전달하는 방식이 더 적합하다.
Code
1. Context 클래스 : 전략을 필드에 보관하는 방식
/**
* 전략패턴 : 전략을 필드에 보관하는 방식
*/
@Slf4j
class Context1 {
private Strategy strategy;
public Context1(Strategy strategy) {
this.strategy = strategy;
}
public void excute() {
try {
long startTime = System.currentTimeMillis();
// 비즈니스 로직
strategy.call();
long endTime = System.currentTimeMillis();
log.info("실행 시간 = {}", (endTime - startTime));
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
2. Context 클래스 : 전략을 파라미터로 받는 방식
/**
* 전략패턴 : 전략을 파라미터로 받는 방식
*/
@Slf4j
class Context2 {
public void excute(Strategy strategy) {
try {
long startTime = System.currentTimeMillis();
// 비즈니스 로직
strategy.call();
long endTime = System.currentTimeMillis();
log.info("실행 시간 = {}", (endTime - startTime));
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
Strategy 인터페이스
interface Strategy {
void call();
}
Strategy 구현클래스 : 알고리즘1
@Slf4j
class StrategyLogic1 implements Strategy{
@Override
public void call() {
log.info("비즈니스 로직 1 실행");
}
}
Strategy 구현클래스 : 알고리즘2
@Slf4j
class StrategyLogic2 implements Strategy{
@Override
public void call() {
log.info("비즈니스 로직 2 실행");
}
}
전략 패턴 실행 코드
@Slf4j
public class ContextTest {
// @Test
void excute() {
logic1();
logic2();
}
void logic1() {
try {
long startTime = System.currentTimeMillis();
// 비즈니스 로직
log.info("비즈니스 로직 1 실행");
long endTime = System.currentTimeMillis();
log.info("실행 시간 = {}", (endTime - startTime));
} catch (Exception e) {
log.error(e.getMessage());
}
}
void logic2() {
try {
long startTime = System.currentTimeMillis();
// 비즈니스 로직
log.info("비즈니스 로직 2 실행");
long endTime = System.currentTimeMillis();
log.info("실행 시간 = {}", (endTime - startTime));
} catch (Exception e) {
log.error(e.getMessage());
}
}
/**
* 전략패턴(필드 보관) : 기본
*/
// @Test
void strategy1() {
Strategy strategy1 = new StrategyLogic1();
Context1 context1 = new Context1(strategy1);
context1.excute();
Strategy strategy2 = new StrategyLogic2();
Context1 context2 = new Context1(strategy2);
context2.excute();
}
/**
* 전략패턴(필드 보관) : 익명 내부 클래스 사용.
* - 익명 내부 클래스를 사용하면 인터페이스의 구현클래스를 만들지 않아도 된다.
*/
// @Test
void strategy2() {
Strategy strategy1 = new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 1 실행");
}
};
Context1 context1 = new Context1(strategy1);
context1.excute();
Strategy strategy2 = new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 2 실행");
}
};
Context1 context2 = new Context1(strategy2);
context2.excute();
}
/**
* 전략패턴(필드 보관) : 익명 내부 클래스 코드 줄이기
* 인터페이스를 구현한 후 Context에 주입해주는걸 한번에 해결할 수 있다.
*/
// @Test
void strategy3() {
Context1 context1 = new Context1(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 1 실행");
}
});
context1.excute();
Context1 context2 = new Context1(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 2 실행");
}
});
context2.excute();
}
/**
* 전략패턴(필드 보관) : 람다 사용
* - 람다를 사용하려면 인터페이스에 메서드가 1개만 존재해야 한다.
*/
// @Test
void strategy4() {
Context1 context1 = new Context1(() -> log.info("비즈니스 로직 1 실행"));
context1.excute();
Context1 context2 = new Context1(() -> log.info("비즈니스 로직 2 실행"));
context2.excute();
}
/**
* 전략 패턴(파라미터 전달) : 기본
*/
// @Test
void strategy5() {
Context2 context1 = new Context2();
context1.excute(new StrategyLogic1());
context1.excute(new StrategyLogic2());
}
/**
* 전략 패턴(파라미터 전달) : 익명 내부 클래스 사용
*/
// @Test
void strategy6() {
Context2 context1 = new Context2();
context1.excute(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 1 실행");
}
});
context1.excute(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 2 실행");
}
});
}
/**
* 전략 패턴(파라미터 전달) : 람다
*/
@Test
void strategy7() {
Context2 context1 = new Context2();
context1.excute(() -> log.info("비즈니스 로직 1 실행"));
context1.excute(() -> log.info("비즈니스 로직 2 실행"));
}
}