프로그래밍 일반/디자인 패턴

전략 패턴(Strategy Pattern)

지노윈 2020. 3. 4. 22:42
반응형

구성(Composition)을 이용하여 알고리즘 상세 구현을 분리하여 구현하는 것입니다.

 

런타임에 변경 가능한 동적형태와 템플릿으로 컴파일 타임에 결정되는 정적 형태가 있습니다.

전략 패턴은 알고리즘의 골격만을 정의하고 세부 구현은 구성(Composition)으로서 특정 전략을 선택적으로 채워 넣을 수 있게 하는 것입니다.

 

앞으로 단순한 텍스트 목록을 두 가지 포맷(HtmlList, MarkdownList)으로 전략적으로 선별하여 렌더링 하는 것을 구현합니다.

동적 전략

구성(Composition)을 이용하며 런타임에 전략을 동적으로 결정합니다.

전략 패턴의 골격을 위한 ListStrategy 클래스를 구현합니다.

 

struct ListStrategy
{
  virtual void add_list_item(ostringstream& oss, const string& item) {};
  virtual void start(ostringstream& oss) {};
  virtual void end(ostringstream& oss) {};
};

텍스트 처리 컴포넌트는 목록 처리를 위한 전용 멤버 함수 append_list()를 가집니다. start, add_list_item, end를 호출합니다.

동적 전략 패턴의 핵심 구현은 set_output_format입니다. set_output_format 호출을 통해 동적으로 어떤 전략의 구현을 사용할지 결정합니다.

struct TextProcessor
{
  void clear()
  {
    oss.str("");
    oss.clear();
  }
 
  void append_list(const vector<string> items) // 알고리즘 골격
  {
    list_strategy->start(oss);
    for (auto& item : items)
      list_strategy->add_list_item(oss, item);
    list_strategy->end(oss);
  }
 
  void set_output_format(const OutputFormat format) // 동적으로 전략 선택
  {
    switch(format)
    {
    case OutputFormat::Markdown:
      list_strategy = make_unique<MarkdownListStrategy>();
      break;
    case OutputFormat::Html:
      list_strategy = make_unique<HtmlListStrategy>();
      break;
    default:
      throw runtime_error("Unsupported strategy.");
    }
  }
  string str() const { return oss.str(); }
private:
  ostringstream oss;
  unique_ptr<ListStrategy> list_strategy;
};

목록 렌더링 전략의 구현을 하는 HtmlListStrategy 클래스를 구현합니다.

struct HtmlListStrategy : ListStrategy      // HtmlList의 세부 구현
{
  void start(ostringstream& oss) override
  {
    oss << "<ul>" << endl;
  }
 
  void end(ostringstream& oss) override
  {
    oss << "</ul>" << endl;
  }
 
  void add_list_item(ostringstream& oss, const string& item) override
  {
    oss << "<li>" << item << "</li>" << endl;
  }
};

마찬가지로 또 다른 전략의 구현인 MarkdownListStrategy 클래스를 구현합니다. start, end는 아무런 동작을 하지 않으므로 add_list_item만 구현합니다.

struct MarkdownListStrategy : ListStrategy      // set_output_format대신 MarkdownList의 세부 구현
{
  void add_list_item(ostringstream& oss, const string& item) override
  {
    oss << " * " << item << endl;
  }
};

이제 TextProcessor를 이용하여 서로 다른 전략에 목록을 입력하여 서로 다른 렌더링 결과를 얻을 수 있습니다.

// markdown
TextProcessor tp;
tp.set_output_format(OutputFormat::Markdown);   // 동적으로 Markdown 전략 구현 사용
tp.append_list({"foo", "bar", "baz"});
cout << tp.str() << endl;
 
// html
tp.clear();
tp.set_output_format(OutputFormat::Html);   // 동적으로 Html 전략 구현 사용
tp.append_list({"foo", "bar", "baz"});
cout << tp.str() << endl;

정적 전략

탬플릿을 이용하며 컴파일 타임에 사용할 전략을 결정합니다.

전략 패턴의 골격을 위한 ListStrategy 인터페이스를 정의합니다.

struct ListStrategy
{
  virtual ~ListStrategy() = default;
  virtual void add_list_item(ostringstream& oss, const string& item) = 0;
  virtual void start(ostringstream& oss) = 0;
  virtual void end(ostringstream& oss) = 0;
};

텍스트 처리 컴포넌트는 목록 처리를 위한 전용 멤버 함수 append_list()를 가집니다. start, add_list_item, end를 호출합니다.

정적 전략 패턴의 핵심은 set_ouput_format 함수 호출 대신 template으로 구현 한다는 것입니다.

template <typename LS>        // 탬플릿으로 구현
struct TextProcessor
{
  void clear()
  {
    oss.str("");
    oss.clear();
  }
  void append_list(const vector<string> items)
  {
    list_strategy.start(oss);
    for (auto& item : items)
      list_strategy.add_list_item(oss, item);
    list_strategy.end(oss);
  }
  string str() const { return oss.str(); }
private:
  ostringstream oss;
  LS list_strategy;

목록 렌더링 전략의 구현을 하는 HtmlListStrategy 구상 클래스를 구현합니다.

struct HtmlListStrategy : ListStrategy
{
  void start(ostringstream& oss) override
  {
    oss << "<ul>" << endl;
  }
 
  void end(ostringstream& oss) override
  {
    oss << "</ul>" << endl;
  }
 
  void add_list_item(ostringstream& oss, const string& item) override
  {
    oss << "<li>" << item << "</li>" << endl;
  }
};

ListStrategy 추상 클래스의 오버라이딩해야 할 멤버 함수들을 구현하였습니다.

마찬가지로 또 다른 전략의 구현인 MarkdownListStrategy 구상 클래스를 구현합니다.

struct MarkdownListStrategy : ListStrategy
{
  void start(ostringstream& oss) override
  {
  }
 
  void end(ostringstream& oss) override
  {
  }
 
  void add_list_item(ostringstream& oss, const string& item) override
  {
    oss << " * " << item << endl;
  }
};

이제 TextProcessor를 이용하여 서로 다른 전략에 목록을 입력하여 서로 다른 렌더링 결과를 얻을 수 있습니다.

// markdown
TextProcessor<MarkdownListStrategy> tpm;
tpm.append_list({"foo", "bar", "baz"});
cout << tpm.str() << endl;
 
// html
TextProcessor<HtmlListStrategy> tph;
tph.append_list({"foo", "bar", "baz"});
cout << tph.str() << endl;

 

 

[독서 리뷰] - 모던 C++ 디자인 패턴

 

모던 C++ 디자인 패턴

[객체 지향 프로그래밍/디자인 패턴] - 빌더 패턴(Builder Pattern) [분류 전체보기] - 디자인 패턴 [객체 지향 프로그래밍/디자인 패턴] - 팩토리 패턴(Factory pattern) [객체 지향 프로그래밍/디자인 패턴] -..

devjino.tistory.com

코드 참고 : https://cpp-design-patterns.readthedocs.io/en/latest/
 

Welcome to C++ Design Patterns’s documentation! — C++ Design Patterns 0.0.1 documentation

© Copyright 2018, Hans-J. Schmid Revision 786c83f8.

cpp-design-patterns.readthedocs.io