본문 바로가기
우아한테크코스/Level1

저는 전략 패턴이 처음이라니까요?

by 조엘 2021. 3. 13.

안녕하세요! 조엘입니다!

 

"처음이라니까요" 시리즈 두 번째 주자 바로 전략 패턴입니다. 

 

자랑은 아니지만, 사실 디자인 패턴이라는 걸 우테코 들어와서 처음 들어봤어요. 😂

그래서 전략 패턴 하나를 배우고 익히는데 시간이 오래 걸렸던 것 같아요. 

 

그러면 전략 패턴을 제가 어떻게 이해하고, 정리하고, 적용했는지 알려드릴게요! 💪

피드백 환영입니다! 댓글 달아주세요 :) 

 

 

*** 디자인 패턴 ***

위키피디아에서 정의하는 디자인 패턴의 정의는 다음과 같아요. 

 

디자인 패턴은 소프트웨어 설계에서 특정 문맥에서 공통적으로 발생하는 문제에 대해 재사용 가능한 솔루션이다.

디자인 패턴은 프로그래머가 어플리케이션이나 시스템을 디자인할 때 공통된 문제들을 해결하는 데에 쓰이는 형식화된 가장 좋은 관행이다. 

 

가장 유명한 GoF 디자인 패턴은 다음과 같이 디자인 패턴들을 분류해요. 

 

GoF 디자인 패턴

Creational

객체 생성 메커니즘에 관련된 패턴으로써, 상황에 알맞은 객체를 생성하기 위해 사용해요. 

 

Structural

객체 간의 구조와 관계를 쉽게 인지하도록 설계하기 위해 사용해요. 

 

Behavioral

객체 간의 커뮤니케이션 패턴을 정의해 놓음으로써, 커뮤니케이션에 유연성을 증대시켜줘요. 

 

오늘 알아볼 전략 패턴은 Behavioral 패턴 중에 하나네요!

전략 패턴이 어떻게 객체 간의 커뮤니케이션에 유연성을 부여하는지 알아보도록 합시다 :)

 

 

*** 전략 패턴 ***

위키피디아에서 정의하는 전략 패턴의 정의는 다음과 같아요.

 

전략 패턴(strategy pattern) 또는 정책 패턴(policy pattern)은 실행 중에 알고리즘을 선택할 수 있게 하는 행위 소프트웨어 디자인 패턴이다. 전략 패턴은

  • 특정한 계열의 알고리즘들을 정의하고
  • 각 알고리즘을 캡슐화하며
  • 이 알고리즘들을 해당 계열 안에서 상호 교체가 가능하게 만든다.

전략은 알고리즘을 사용하는 클라이언트와는 독립적으로 다양하게 만든다.

 

정의에서 얘기하는 알고리즘들이 곧 어떻게 객체가 행동할지에 대한 전략이 되는 거에요. 

사실 정의만 보고는 와닿지 않을 거에요. 전략 패턴의 구조를 살펴보고, 예시를 살펴보면 조금 더 이해가 수월할 거에요. 

 

 

*** 전략 패턴의 구성 요소 ***

전략 패턴을 이루는 구성 요소는 다음과 같아요. 

 

전략 패턴의 구조

 

  - 전략(strategy) : 구체화된 여러 알고리즘들의 추상화로써, 변하는 부분을 담당

     - 전략 콘크리트(concrete strategy) : 여러 알고리즘의 실제 구현

  - 전략 사용자(context) : 전략을 사용하는 프로그램의 흐름으로, 변하지 않는 것

  - 전략 제공자(client) : 전략 사용자에게 실제 전략으로 사용할 전략 콘크리트 클래스를 주입하는 역할

 

보다시피 상황에 따라 다양한 알고리즘을 필요로 하는 경우 전략 패턴을 사용할 수 있어요. 

그러면 이런 발상을 코드로 옮기기 위해서는 어떻게 전략과 전략 콘크리트를 작성하는 게 좋을까요? 

 

바로 인터페이스와 해당 인터페이스를 구현한 클래스를 사용하는 것이에요!

인터페이스를 통해 전략을 추상화 시켜놓은 후, 적재 적소에 필요한 전략을 구현한 Class를 삽입하는 것이죠. 

 

 

*** 전략 패턴 예시 ***

자, 이제 코드로 실제 구성 요소들이 어떻게 구현되는 지 살펴보아요. 

 

public interface ShuffleStrategy {
    List<Card> shuffle(final List<Card> cards);
}
    
public class RandomShuffleStrategy implements ShuffleStrategy {
    @Override
    public List<Card> shuffle(final List<Card> cards) {
        Collections.shuffle(cards);
        return cards;
    }
}

 

전략(strategy) 전략 콘크리트(concrete strategy) 에요. 

 

위에서 언급했던 것 처럼, ShuffleStrategy라는 인터페이스를 만들어 Shuffle 이라는 전략을 추상화 시켰어요. 

이제 해당 인터페이스를 구현한 전략 콘크리트 클래스는, 각자 의도에 맞는 Shuffle 전략을 작성해요. 

 

RandomShuffleStrategy라는 클래스가 전략 콘크리트 클래스의 예시로, 

코드를 보면 매개변수로 넘겨받은 카드를 랜덤으로 섞어 반환하는 것을 볼 수 있어요. 

 

public static CardDeck make(final ShuffleStrategy shuffleStrategy) {
    final List<Card> shuffledCard = shuffleStrategy.shuffle(cards);
    return new CardDeck(shuffledCard);
}

 

전략 사용자(context) 에요. 

전략을 사용하는 프로그램의 흐름으로, 서로 다른 전략에 따른 코드의 변경이 필요 없어요. 

 

해당 context에서는 넘겨 받은 전략 콘크리트의 shuffle 메서드를 통해 cards를 섞어주게 되어요. 

ShuffleStrategy를 구현한 클래스는 shuffle 이라는 전략을 가지고 있다는 것을 보장하기 때문에, 

cards를 섞는 일은 전략 콘크리트 객체에게 위임할 수 있겠네요!

 

만일 전략 패턴이 아닌 방식으로 구현한다면 어땠을까요?

필요한 전략에 맞추어 행동하기 위해 if - else 등의 분기문으로 구구절절 전략들을 작성했을 거에요. 

과도한 if - else 문은 곧 메서드/객체가 하나의 책임을 가진다는 것과 멀어진다는 뜻이에요. 

전략 패턴을 사용해 객체 지향적인 코드 작성이 가능해 졌네요!

 

public void run() {
    final CardDeck cardDeck = CardDeckFactory.make(new RandomShuffleStrategy());
}

 

전략 제공자(client) 에요. 

여기서 전략 사용자에게 구체적인 전략 콘크리트 클래스를 주입해 줬던 것이였군요!

현재 코드에서는 바로 RandomShuffleStrategy의 객체를 생성하여 주입시켜줬지만, 

사용자의 요청에 따라 다른 전략을 사용해야 하는 경우, 

매번 다른 전략을 사용해 객체를 생성해야 하는 경우 등에서 전략 패턴의 유연함은 더 빛을 발할 거에요. 

 

 

*** 전략 패턴의 장단점 ***

앞서 전략 패턴은 Behavioral 패턴 중에 하나로, 객체 간의 커뮤니케이션에 유연성을 부여한다고 했어요.

어떤가요? 객체 간 커뮤니케이션의 유연성이 조금 느껴지시나요? 

 

마지막으로 전략 패턴의 장단점을 정리하며 포스팅 마무리 할게요. 

 

[장점]

  - 전략 사용자(context)의 코드 변경 없이 새로운 전략을 추가 할 수 있다.

      - 이를 통해 if - else 분기를 제거할 수 있다.

      - if - else 분기를 제거하면, 단일 책임 원칙을 준수하기 더 수월해진다. 

 

  - 확장에 유리한 코드를 작성할 수 있다. 

      - 새롭게 필요한 전략 콘크리트 클래스를 쉽게 만들 수 있다. 

      - 개방 폐쇄 원칙을 준수한 코드 작성이 가능하다. 

 

  - 런타임에 전략을 변경시킬 수 있다. 

 

[단점]

  - 어플리케이션에 들어가는 모든 전략을 알고 있어야 한다. 

      - 클래스로 분리한 각 전략들이 어느 상황에 사용되어야 할 지 알고 있어야 한다.

      - 이 같은 특성이 어쩌면 유지보수를 더 힘들게 할 수도 있다. 

 

  - 전략을 추상화한 인터페이스가 효율적이지 못할 수 있다. 

      - 어떤 전략 콘크리트 객체에서는 사용하지 않는 메서드들 역시 전략 인터페이스에 정의해 주어야 한다. 

 

 

참고

- www.geeksforgeeks.org/strategy-pattern-set-1/

- www.youtube.com/watch?v=azrG8bf5EGA

- 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴(최범균 | 인투북스)

 

 

반응형

댓글2

  • nickname 2022.07.20 07:51

    안녕하세요 ㅎㅎ 전략패턴을 공부중인 학생입니다.
    전략 패턴을 보다보면 if - else 를 줄일 수 있는 패턴이라는 말들이 종종 보이는데, 여기서 의문이 조금 생겨서 질문 드립니다~

    Context 가 추상화된 인터페이스를 파라미터로 갖기 때문에 Context 상에서 if - else 문을 줄일 수 있다고 이해했습니다 ㅎㅎ
    그런데 Context 사용자인 Client 에서는 런타임에 적절한 전략을 선정하여 Context 로 넘겨줘야 하기 때문에 어떤식으로든 if - else 나 switch 가 사용될것 같다고 생각했습니다.
    그렇다면 전략 패턴은 Context 가 사용할 분기문을 Client 단에 전가하는 패턴이 아닌지 궁금합니다.
    답글

    • 조엘 2022.07.20 15:38 신고

      안녕하세요! 읽어주셔서 감사합니다 :)

      사실 "특정한 조건에 따라 특정한 로직을 수행한다" 는 부분에서 if-else가 완전히 빠질 수는 없을거에요. 그러다보니 말씀해 주신대로 context에서 쓰던 분기가 client단으로 넘어간다는 말이 저 역시도 동의가 되네요!

      하지만 어디에서 분기를 줘서 알맞은 로직을 처리하도록 역할을 부여할지는 충분히 고민해볼만한 가치가 있는 것 같아요!

      저는 개인적으로 Client단에서 "이 로직으로 수행해주세요!" 라고 부탁하는게 맞다고 생각해요. Context에서는 요청 받은대로의 로직을 수행해주는 것이 각 Client와 Context의 책임과 역할을 더 잘 분리해주는 코드라고 생각이 드네요!

      Client는 역할 요청에 대한 책임을, Context에서는 역할 수행에 대한 책임을 가져가는거죠! 디자인 패턴도 결국 객체지향적으로 유연한 코드를 작성하는데 의미가 있으니까요. 더 궁금한게 있으면 물어봐주세요ㅎㅎ 같이 얘기해보면 좋을 것 같습니다!