본문 바로가기
Design Pattern

전략 패턴

by 권성호 2022. 4. 18.

사실이 아니라 공부한 내용과 생각을 정리한 글입니다. 언제든 가르침을 주신다면 감사하겠습니다.

 

해당 글은 김영한 님의 인프런 강의 스프링 핵심 원리 - 고급 편 을 학습하며 정리한 내용입니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard

 

스프링 핵심 원리 - 고급편 - 인프런 | 강의

스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

 

지난 글에서 템플릿 메서드 패턴과 한계점에 대해 정리했습니다.

(https://ksh-dev.tistory.com/61)

이번 글에서는 템플릿 메서드 패턴의 한계를 극복하기 위한 또 다른 패턴인 전략 패턴에 대해 알아보겠습니다.

 

1.  해결하고자 하는 문제 

템플릿 메서드 패턴이 해결하고자 하는 문제는 변하지 않는 것과 변하는 것을 분리해 모듈화 하는 것입니다.

객체지향 관점에서 단일 책임 원칙을 달성하기 위한 패턴이라고 생각됩니다.

 

전략 패턴은 템플릿 메소드 패턴과 동일하게 변하지 않는 것과 변하는 것을 분리하기 위한 방법입니다. 하지만 상속이 아니라 인터페이스와 위임을 통해 문제를 해결합니다.

 

 

2.  문제를 푸는 방식

전략 패턴 다이어그램

변하지 않는 부분을 context 로 정의합니다.

변하는 부분에 대한 스펙을 Strategy 인터페이스로 정의하고 이를 상속한 구현채에서 변하는 부분을 구현합니다.

 

3.  예제 코드를 통한 이해

템플릿 메소드 패턴에서 사용한 예제 코드를 발전시켜 보겠습니다.(https://ksh-dev.tistory.com/61)

 

우선 변하지 않는 부분을 담당할 context 클래스를 만들어 줍니다.

@Slf4j
public class TimeLogContext {

    private final Strategy strategy;

    public TimeLogContext(Strategy strategy){
        this.strategy = strategy;
    }

    public void execute_strategy() {
        long startTime = System.currentTimeMillis();
        //비즈니스 로직 실행
        strategy.call(); //위임
        //비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }
}

그 후 Strategy 인터페이스와 구현채를 만듭니다.

public interface Strategy {
    void call();
}
@Slf4j
public class StrategyLogic1 implements Strategy {
    @Override
    public void call() {
        log.info("비즈니스 로직1 실행");
    }
}

그 후에 클라이언트 코드는 아래와 같이 사용하면 됩니다.

@Test
public void strategy(){
    Strategy strategy = new StrategyLogic1();
    TimeLogContext context = new TimeLogContext(strategy);
    context.execute_strategy();
}

 

여기서 눈여겨볼만한 점이 몇 가지 있습니다.

우선, context 클래스의 생성자를 보면 인터페이스 타입의 Strategy를 메게변수로 받고 있고, 클라이언트 코드에선 인터페이스의 구현채를 Context 클래스의 생성자를 통해 주입하고 있습니다. 

이는 Spring이 DI를 통해 빈의 의존관계를 설정해 주는 것과 매우 유사한 형태 입니다. 

전략 페턴의 핵심을 정리하면 아래와 같습니다.

  1. 변하지 않는 Context 가 있고 변하는 Strategy 가 있다.
  2. Strategy는 인터페이스 타입이고 변하는 부분은 이를 상속받은 구현채에서 정의된다.
  3. Context는 Strategy 인터페이스 타입을 주입받는 형태이다.
  4. 클라이언트 코드는 Context에 적절한 Strategy 구현채를 조립하여 사용하면 된다.

(Spring은 많은 부분을 전략 패턴과 유사하게 설계하였고 조립에 대한 책임까지 프래임워크 차원에서 해결해 주었습니다.[IOC 개념])

 

전략 패턴을 쓰면 템플릿 메서드 패턴의 목적이었던 변하는 부분과 변하지 않는 부분을 분리할 수 있고 이 둘 사이의 결합도도 낮출 수 있습니다.

즉, Strategy 인터페이스의 구현채는 Context를 전혀 모릅니다. Context 또한 Strategy 인터페이스만 알 뿐 구현채에 대해서는 전혀 모릅니다.

이는 Strategy를 통해 Context의 동작을 변경하거나 Context를 통해 Strategy의 동작을 변경하는 것이 불가능 함을 의미합니다.

즉, 리스 코프 치환 원칙과 개방 폐쇄 원칙을 만족합니다.

 

4.  추가 자료: [템플릿 콜백 패턴]

전략 패턴에서 Strategy를 Context의 필드가 아니라 메게변수로 받는 형태로 구현할 수도 있는데, 이를 템플릿 콜백 패턴이라고 합니다.

전략 패턴의 경우 Context에 적절한 Strategy를 조립한 후에 동작을 하는 반면, 템플릿 콜백 패턴은 조립 과정을 생략하고 동작하기 직전에 Strategy 를 넘겨주는 방식입니다.

구조가 단순할 경우 템플릿 콜백 패턴을 사용하면 조금 더 유연하게 전략을 바꿔가며 Context를 실행할 수 있다고 생각합니다.

전략이 많고 구조가 복잡해질수록 템플릿 콜백 패턴을 사용하기 어려울 것 같습니다.

 

5. 한계점

최초의 핵심 비즈니스 로직으로 돌아가 보겠습니다.

public void logic1() {
    //비즈니스 로직 실행
    log.info("비즈니스 로직1 실행");
    //비즈니스 로직 종료
}

전략 패턴을 통해 많은 부분을 개선할 수 있었습니다. 

그런데, 기존 logic1 method를 어쨌든 변경해야 합니다.

만약, 기존 클래스와 메서드가 수천 개가 넘는다면 모든 클래스에 메서드를 한 땀 한 땀 다 수정해야 합니다.

조금 더 욕심을 부려서 전략 패턴의 장점을 그대로 가져가면서, 기존 코드를 전혀 수정하지 않고도 부가 기능을 추가할 수 있는 방법은 없을까요??

 

 

다음 글에서는 프락시 패턴(https://ksh-dev.tistory.com/63)에 대해 알아보겠습니다.

'Design Pattern' 카테고리의 다른 글

프록시 패턴 & 데코레이터 패턴  (1) 2022.04.18
템플릿 메소드 패턴  (0) 2022.04.18

댓글