전략 패턴(Strategy Pattern)

전홍영·2023년 12월 10일
0

디자인 패턴

목록 보기
2/12

템플릿 메서드 패턴의 한계인 상속의 단점을 제거한 디자인 패턴이 전략이 전략 패턴이라고 했다. 전략 패턴이란 무엇일까?

전략 패턴

템플릿 메서드 패턴은 템플릿을 만들고 해당 템플릿을 자식이 상속받아 사용하는 패턴이었다. 전략 패턴은 변하지 않는 부분을 Context라는 곳에 두고, 변하는 부분을 Strategy라는 인터페이스를 만들어 해당 인터페이스를 구현하여 문제를 해결한다. 상속이 아닌 위임으로 문제를 해결하여 Context는 템플릿 역할을 하고 Strategy는 변하는 알고리즘을 의미한다.

GOF에서는 전략 패턴을 다음과 같이 정의한다.

알고리즘 제품군을 정의하고 각각을 캡슐화하여 상호 교환 가능하게 만들자. 전략을 사용하면 알고리즘을 사용하는 클라이언트와 독립적으로 알고리즘을 변경할 수 있다.

전략 패턴 적용해보기

전략 패턴은 interface에 변하는 부분을 만들고 해당 인터페이스를 구현하여 문제를 해결하도록 한다. 그리고 Context에는 변하지 않는 부분을 둔다.

먼저 인터페이스를 구현해보자.

public interface TeamStrategy<T> {
    T call();
}

call() 메서드를 구현해보자.

@Service
@RequiredArgsConstructor
public class TeamStrategyLogic implements TeamStrategy<List<Team>> {
    private final TeamRepository teamRepository;

    @Override
    public List<Team> call() {
        //비즈니스 로직 실행
        return teamRepository.findAll();
    }
}

이렇게 call메서드를 구현한 후 Context에서 전략을 조립하여 사용해보자.

public class TeamContext {
    private TeamStrategy<List<Team>> teamStrategy;

    public TeamContext(TeamStrategy<List<Team>> teamStrategy) {
        this.teamStrategy = teamStrategy;
    }

    public TeamContext() {
    }

    public List<Team> execute() {
        long startTime = System.currentTimeMillis();
        // 비즈니스 로직
        List<Team> teams = teamStrategy.call();

        long endTime = System.currentTimeMillis();
        System.out.println("수행 시간 : " + (endTime - startTime) + "ms");

        return teams;
    }
}
@ExtendWith(MockitoExtension.class)
class TeamContextTest {
    @MockBean
    private TeamRepository teamRepository;

    @Test
    void 클래스를_이용한_전략_패턴() {
        TeamStrategyLogic teamStrategyLogic = new TeamStrategyLogic(teamRepository);
        TeamContext teamContext = new TeamContext(teamStrategyLogic);
        List<Team> teams = teamContext.execute();
    }

    @Test
    void 익명_클래스를_이용한_전략_패턴() {
        TeamContext teamContext = new TeamContext(new TeamStrategy<List<Team>>() {
            @Override
            public List<Team> call() {
                return teamRepository.findAll();
            }
        });
        List<Team> teams = teamContext.execute();
    }

    @Test
    void 람다_식을_이용한_전략_패턴() {
        TeamContext teamContext = new TeamContext(() -> teamRepository.findAll());
        List<Team> teams = teamContext.execute();
    }
}

Context를 구현후 Strategy를 주입하여 사용하면 된다. 간단하게 익명 클래스나 람다로 구현이 가능하다.

위의 테스트 코드를 보면 먼저 조립을 한 후 실행하게 되면 Context를 실행하는 시점에는 이미 조립이 끝났기 때문에 전략을 신경쓰지 않고 단순히 실행만 하면 된다.

이번에는 전략을 파라미터로 받아 실행할 때 마다 전략을 우연하게 변경할 수 있도록 구현해보자.

public class TeamContext {
    public List<Team> executeWithParameterStrategy(TeamStrategy<List<Team>> teamStrategy) {
        long startTime = System.currentTimeMillis();
        // 비즈니스 로직
        List<Team> teams = teamStrategy.call();

        long endTime = System.currentTimeMillis();
        System.out.println("수행 시간 : " + (endTime - startTime) + "ms");

        return teams;
    }
}
@Test
void 전략을_파라미터로_받아_사용하는_전략_패턴() {
    TeamContext teamContext = new TeamContext();
    List<Team> teams = teamContext.executeWithParameterStrategy(new TeamStrategy<List<Team>>() {
        @Override
        public List<Team> call() {
            return teamRepository.findAll();
        }
    });

    List<Team> teams2 = teamContext.executeWithParameterStrategy(() -> teamRepository.findAll());
}

전략을 파라미터로 받아 익명 클래스나 람다를 이용하여 구현한다면 유연하게 코드를 조작할 수 있어 공통 관심사와 핵심 비즈니스 로직을 분리할 수 있다.

따라서 선조립 후 실행을 선택한다면 전략을 신경쓰지 않고 단순히 실행만 하면 된다. 반면, 전략을 파라미터로 받아 실행할 때마다 전략을 지정해준다면 유연한 코드 조작이 가능해져 변하는 부분과 변하지 않는 부분을 분리할 수 있다.

DIP(의존성 역전의 원칙)

템플릿 메서드의 문제는 상속으로 인한 의존성 문제가 있었다. 그러나 전략 패턴은 특정 컴포넌트에 의존하지 않고 역으로 컴포넌트가 인터페이스에 의존하는 형태가 된다. 이는 DIP를 따르고 있는 것을 알 수 있다.

템플릿 메서드 패턴 VS 전략 패턴

템플릿 메서드 패턴은 상속을 통해 알고리즘의 일부를 다르게 구현하는 반면, 전략 패턴은 구성을 통해 알고리즘의 일부를 동적으로 교체한다. 따라서 템플릿 메서드 패턴은 알고리즘 구조가 변경되지 않고 일정한 경우에 적합하며, 전략 패턴은 알고리즘의 전략이 자주 바뀌는 경우에 적합하다.

즉, 템플릿 메서드 패턴은 '알고리즘의 흐름'을 고정하고 그 '흐름 안의 단계'를 다르게 구현하는 데 초점을 두는 반면, 전략 패턴은 '알고리즘이나 행동' 자체를 객체로 만들어 동적으로 교체하는 데 초점을 둔다.

profile
Don't watch the clock; do what it does. Keep going.

0개의 댓글