백엔드/Spring

[Spring] Strategy & Template Callback

kwang2134 2024. 11. 1. 16:16
728x90
728x90
반응형

Strategy- 전략 패턴

  • 로직을 정의하고 이를 캡슐화하여 서로 교환 가능하게 만드는 패턴
  • 상속이 아닌 인터페이스를 위임받아 구현하는 방식으로 상속으로 인해 생기는 문제를 해결

구성 요소

  • Context: 전략 객체를 사용하는 클래스로 템플릿과 같은 변하지 않는 코드를 다루는 클래스
  • Strategy Interface: 다양한 로직을 정의하는 인터페이스로 전략 클래스는 해당 인터페이스를 상속받아 구현
  • Concrete Strategies: 인터페이스를 구현하여 특정 로직을 제공하는 클래스 

사용 방법

  • 변하는 로직을 정의하는 인터페이스를 정의
  • 인터페이스를 통해 특정 로직을 구현한 클래스 생성
  • 공통된 로직을 정의하는 Context 클래스를 생성
//strategy interface - 변경되는 로직을 정의하는 인터페이스 
public interface Strategy {
    void call();
}

//interface를 상속받아 구현한 클래스
public class StrategyLogic1 implements Strategy{
    @Override
    public void call() {
        log.info("로직1 실행");
    }
}

필드에 전략을 보관하는 방식

  • Context 클래스 내에 전략을 필드로 두고 생성 시 주입받아 사용하는 방식
  • 스프링에서 의존관계를 주입받아 사용하는 것과 동일한 방식 - 선 조립 후 실행
  • 전략 변경 시 새로운 전략으로 객체를 생성하거나 변경이 어려움
//공통된 로직이 정의된 Context 클래스 - 전략을 필드에 보관하고 주입 받는 방식
public class Context {

    private final Strategy strategy;

    public Context(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);		//공통 로직
    }
}

//사용
StrategyLogic1 strategyLogic1 = new StrategyLogic1();
Context context = new Context(strategyLogic1);
context.execute();

//익명 클래스 
Strategy strategy = new Strategy() {
    @Override
    public void call() {
    log.info("로직1 실행");
    }
};
Context context = new Context(strategy);
context.execute();

//람다 - interface에 정의된 메서드가 1개인 경우
Context context = new Context(() -> log.info("로직1 실행"));
context.execute();

파라미터로 전략을 넘기는 방식 -  Template Callback

  • 변경할 전략을 실행 시 파라미터로 넘기는 방식
  • 실행하는 메서드에서 직접 파라미터로 넘겨받아 변경 유연
  • 스프링에서 템플릿-콜백 패턴으로 불림
  • 변하지 않는 부분 template(Context)과 변하는 부분 strategy로 코드가 호출은 되는데 코드를 넘겨준 곳 뒤에서 실행되어 콜백이라 불림
  • 클라이언트는 strategy의 함수를 직접 호출하지 않고 template에서 함수를 실행할 때 strategy를 넘겨주고 뒤(template)에서 실행
  • 스프링의 ...template 는 모두 이러한 방식으로 구현되어 있음
  • 매번 전략을 파라미터로 넘겨야 하는 번거로움이 있음
//실행 시 파라미터로 넘겨 받는 방식
public class Context {
    public void execute(Strategy strategy) {
        long startTime = System.currentTimeMillis();	//공통 로직
        
        strategy.call();				//변경되는 로직
        
        long endTime = System.currentTimeMillis();	//공통 로직
        long resultTime = endTime - startTime;		//공통 로직
        log.info("resultTime={}", resultTime);		//공통 로직
    }
}

//사용
Context context = new Context();
context.execute(new StrategyLogic1());

//익명 클래스 
Strategy strategy = new Strategy() {
    @Override
    public void call() {
    log.info("로직1 실행");
    }
};
Context context = new Context();
context.execute(strategy);

//람다 - interface에 정의된 메서드가 1개인 경우
Context context = new Context();
context.execute(() -> log.info("로직1 실행"));

장점

  • 새로운 로직 추가나 기존 로직 변경이 쉬움
  • 로직이 컨텍스트와 분리되어 가독성과 유지보수가 편함
  • 템플릿 메서드와 다르게 상속이 아닌 위임을 통해 구현되므로 단순한 인터페이스에만 의존

단점

  • 전략을 선택하는 로직이 컨텍스트에 포함되어 복잡성이 증가할 수 있음
  • 전략 객체가 상태를 가지게 되면 상태 관리가 복잡해짐
  • 상속은 아니지만 인터페이스 위임받아 특정 인터페이스에 의존하게 되므로 새로운 전략 도입 시 수정이 생길 수 있음

스프링 핵심 원리 - 고급편 강의 | 김영한 - 인프런 강의 내용 참고

728x90

'백엔드 > Spring' 카테고리의 다른 글

[Spring] 동적 프록시  (0) 2024.11.05
[Spring] Proxy & Decorator Pattern  (0) 2024.11.02
[Spring] Template Method  (0) 2024.10.31
[Spring] Thread Pool & ThreadLocal  (0) 2024.10.30
[Spring] 모니터링 환경 구성  (0) 2024.10.12