본문 바로가기

우아한테크코스/테코톡

전략패턴

2021-05-24 글

[10분 테코톡] 📣 완태의 전략패턴을 들으며 정리한 글입니다.

if - else의 문제점

example

public double calculate(boolean isFirstGuest, boolean isLastGuest, List<Item> items) {
  double sum = 0;
  for (Item item : items) {
    if (isFirstGuest) {
      sum += item.getPrice() * 0.9;
    } else if (!item.isFresh()) {
      sum += item.getPrice() * 0.8;
    } else if (isFirstGuest) {
      sum += item.getPrice() * 0.8;
    } else {
      sum += item.getPrice();
    }
  }
  return sum;
}

기능이 추가될 경우

  • 새로운 조건문을 직접 추가해주어야 한다.
  • 때문에 추가되는 기능들이 많아질 수롣 코드 블럭도 점점 증가한다.
  • 이럴 경우 유지보수성이 떨어진다.

누락될 위험

  • (안 좋은 예시이지만) 같은 기능을 하는 로직을 서로 다른 클래스에서 사용하기 위해 복붙을 했을 경우, 한 쪽의 기능이 변경되면 이를 복붙한 모든 클래스를 전부 수정해주어야 한다.

때문에 if-else를 사용하면 유지보수가 어렵다.


OCP - Open Close Principle (개방 폐쇄의 원칙)

소프트웨어 구성요소(컴포넌트, 클래스, 모듈, 함수)는 확장에 대해서는 개방되어야 하지만 변경에 대해서는 폐쇄되어야 한다.
기존 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계되어야 한다.
기존 코드의 변경이 작으며, 확장이 쉽다는 장점이 있다.

적용 방법

  • 상속(is-a)
  • 컴포지션(has-a)

컴포지션

변경(확장)될 것과 변하지 않을 것을 구분한다.

이 두 모듈이 만나는 지점에 인터페이스를 정의한다.

구현에 의존하기보다 정의한 인터페이스에 의존하도록 코드를 작성한다.

예시

  • List와 ArrayList의 관계
    • 타입 선언은 List이지만 인스턴스를 생성할 때는 그 구현체인 ArrayList로 생성한다.

전략 패턴

  • 디자인 패턴 중 가장 많이 쓰임
    • 디자인 패턴이란? (소프트웨어) 디자인 + (공통적으로 마주치는 문제를 해결하는 방법의) 패턴
  • 동적으로 전략 수정 가능
  • 행위를 클래스로 캡슐화해 동적으로 행위를 자유롭게 바꿀 수 있게 함
  • 새로운 기능의 추가가 기존 코드에 영향을 미치지 못하게 하므로 OCP를 만족
  • 기존 코드 변경없이 행위를 자유롭게 바꿀 수 있게 해주는 OCP를 준수한 디자인 패턴

➕ GoF의 디자인 패턴에서의 전략패턴

  • 동일 계열의 알고리즘군을 정의하고 (walk, run, fly, rocket)
  • 각 알고리즘을 캡슐화하며(MoveStrategy)
  • 이들을 상호교환이 가능하도록 만든다.

전략이란?

어떤 목적을 달성하기 위해 일을 수행하는 방식.
비즈니스 규칙, 문제를 해결하는 알고리즘 등 (Random, Reverse, Nothing)

구성

Context

  • 전략 패턴을 이용하는 역할을 수행
  • 필요에 따라 동적으로 구체적인 전략을 바꿀 수 있도록 함 (setter 또는 DI)

Strategy

  • 인터페이스나 추상 클래스로 외부에서 동일한 방식으로 알고리즘을 호출하는 방법 명시

ConcreateStrategy

  • 전략패턴에서 명시한 알고리즘을 실제로 구현한 클래스

Example

배민 로봇 이동 전략 예시

  • Walk
  • Run
  • Fly
  • Rocket

초기 배달 로봇

public class Robot {
    public void display() {
        System.out.println("배달 로봇");
    }

    public void move() {
        System.out.println("걸어서 배달합니당.");
    }
}

그런데 이제 달리는 로봇도 추가하게 되었다면,

public class RunningRobot {
    public void display() {
        System.out.println("배달 로봇");
    }

    public void move() {
        System.out.println("뛰어서 배달합니당.");
    }
}

이후에 날으는 로봇, 로켓으로 배달하는 로봇이 생기는 경우를 생각해보자.
이를 먼저 상속으로 해결하는 경우, 아래와 같이 구현할 수 있다.

그런데 여기서 로봇에 온도 조절 기능을 추가할 경우, 아래와 같이 구현할 수 있다.

🤔 문제점?

  • 메서드 수정이 어렵다.
    • "걸어서 배달하는 로봇"이 "빠르게 걸어서 배달합니다"로 변할 경우 모든 클래스를 순회하면서 메서드들을 수정해주어야 한다.
  • 새로운 기능 추가가 어렵다.
    • 한국어, 영어, 중국어를 말하는 기능이 추가된다면 추상 클래스에 말하기 기능이 추가될 것이고, 이를 상속받는 클래스에 기능에 따라 다른 각각의 메서드를 구현해주어야 한다.

이를 해결하는 것이 바로 전략패턴이다!

  • 이동전략 : 걷기, 달리기, 날기 등의 로봇의 행위를 정의한다.
  • 온도 전략 : cold, warm, hot등을 정의한다.

로봇을 생성할 때 각각의 전략을 주입해주어 로봇은 전략의 행위를 실행하게 된다.

이렇게 되면 문제점이 어떻게 해결될까?

  • 메서드 수정이 용이하다.
    • "걸어서 배달하는 로봇"이 "빠르게 걸어서 배달합니다"로 변할 경우 walk 부분만 변경해주면 된다.
  • 새로운 기능 추가가 용이하다.
    • 한국어, 영어, 중국어를 말하는 기능이 추가된다면 이에 따른 새로운 전략의 행위를 정의해주고, 로봇을 생성할 때 전략을 함께 주입해주면 된다.
  • 만약 이동방식이 도중에 변경된다면?
    • setter등을 통해 객체 생성 후 전략을 변경할 수 있다.

JDK에서의 전략패턴

Comparator

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

Comparator가 전략이고 개발자마다 custom하게 정의한 Comparator구현을 sort()라는 컨텍스트에서 사용하고 있다.
개발자는 상황에 맞게 비교 전략을 구현할 수 있게 하여 기존 코드의 수정없이 확장을 이룰 수 있다.


참고 자료

'우아한테크코스 > 테코톡' 카테고리의 다른 글

CORS  (0) 2021.08.06
Cache  (0) 2021.08.06
MVC 패턴  (0) 2021.08.06
DTO와 VO  (0) 2021.08.06
Forward Proxy, Reverse Proxy, Load Balancer  (0) 2021.08.06