[Design pattern] 3-1. 전략 패턴(Strategy pattern)


Study/Design Pattern  2021. 11. 3. 18:37

안녕하세요. 명월입니다.


이 글은 디자인 패턴의 전략 패턴(Strategy pattern)에 대한 글입니다.


이번 글부터는 행위 패턴에 대한 설명입니다.

생성 패턴은 프로그램에서 인스턴스를 어떻게 생성하는지에 대한 형태이고 구조 패턴은 인터페이스와 추상 클래스, 그리고 일반 클래스 간의 구조적 정의에 대한 형태입니다.

행위 패턴은 클래스와 알고리즘을 실제로 프로그램에서 어떻게 사용할지에 대한 방법을 설명하는 패턴입니다.


전략 패턴은 사용되는 클래스에서 주입되는 클래스의 형태에 따라 나오는 결과를 달리하는 패턴입니다.

출처 - https://en.wikipedia.org/wiki/Strategy_pattern
#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// 전략 패턴의 인터페이스
class IStrategy {
public:
  // 함수 추상화
  virtual int calc(int data) = 0;
  virtual ~IStrategy() { }
};
// NormalStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class NormalStrategy : public IStrategy {
public:
  // 함수 재정의
  virtual int calc(int data) {
    // 입력값에 10을 곱해서 리턴
    return data * 10;
  }
};
// SpecialStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class SpecialStrategy : public IStrategy {
public:
  // 함수 재정의
  virtual int calc(int data) {
    // 입력값에 100을 곱해서 리턴
    return data * 100;
  }
};
// Process 클래스
class Process {
private:
  // 전략 패턴의 맴버 변수
  IStrategy* strategy = nullptr;
public:
  // 전략 패턴 인스턴스 입력
  void setStrategy(IStrategy* strategy) {
    // 맴버 변수에 인스턴스를 설정
    this->strategy = strategy;
  }
  // 출력 함수
  void print(int data) {
    // 전략 패턴의 맴버 변수가 null이 아니면
    if (this->strategy != nullptr) {
      // calc 함수를 이용해 data를 재설정
      data = this->strategy->calc(data);
    }
    // 콘솔 출력
    cout << "data - " << data << endl;
  }
};

// 실행 함수
int main() {
  // 인스턴스 생성
  Process process;
  // 전략 패턴의 인스턴스 입력 없이 10의 데이터를 출력
  process.print(10);
  // NormalStrategy 인스턴스 생성
  NormalStrategy normal;
  // process 인스턴스에 전략 패턴 설정
  process.setStrategy(&normal);
  // 데이터를 출력
  process.print(10);
  // SpecialStrategy 인스턴스 생성
  SpecialStrategy special;
  // process 인스턴스에 전략 패턴 설정
  process.setStrategy(&special);
  // 데이터를 출력
  process.print(10);

  return 0;
}

아주 단순한 구조입니다. Process 클래스에 전략 패턴을 설정하지 않으면 그대로 10이 출력이 되고 NormalStrategy 인스턴스를 입력하면 100, SpecialStrategy 인스턴스를 입력하면 1000이 됩니다.

사양에 따라 다르겠지만 전략 패턴의 클래스를 플라이웨이트 패턴과 같이 사용하게 되면 클래스의 재사용율이 매우 높아지고, 성능 개선에도 큰 장점이 있습니다.

import java.util.HashMap;
import java.util.Map;
// 전략 패턴의 인터페이스
interface IStrategy {
  // 함수 추상화
  int calc(int data);
}
// NormalStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class NormalStrategy implements IStrategy {
  // 함수 재정의
  public int calc(int data) {
    // 입력값에 10을 곱해서 리턴
    return data * 10;
  }
}
// NormalStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class SpecialStrategy implements IStrategy {
  // 함수 재정의
  public int calc(int data) {
    // 입력값에 10을 곱해서 리턴
    return data * 100;
  }
}
// Process 클래스
class Process {
  // flyweight 패턴의 맵
  private Map<Class<? extends IStrategy>, IStrategy> flyweight = new HashMap<>();
  // 전략 패턴의 맴버 변수
  private IStrategy strategy = null;
  // 맴버 변수
  private int data = 0;
  // flyweight 패턴의 전략 패턴 인스턴스 취득 함수
  private IStrategy getStrategy(Class<? extends IStrategy> clz) {
    // flyweight 맵에 클래스 타입이 있는지 확인
    if (!flyweight.containsKey(clz)) {
      try {
        // 없으면 인스턴스를 생성해서 입력한다.
        flyweight.put(clz, clz.getDeclaredConstructor().newInstance());
      } catch (Exception e) {
        // Exception 처리를 RuntimeException 처리로 변환
        throw new RuntimeException(e);
      }
    }
    // 전략 패턴 인스턴스 취득
    return flyweight.get(clz);
  }
  // 전략 패턴 타입 입력
  public void setStrategy(Class<? extends IStrategy> clz) {
    // flyweight 맵에서 인스턴스 취득
    this.strategy = getStrategy(clz);
  }
  // 데이터 입력
  public void setData(int data) {
    // 맴버 변수 설정
    this.data = data;
  }
  // 출력 함수
  public void print() {
    // 출력 값의 임시 변수
    int val = data;
    // 전략 패턴이 설정되어 있으면
    if (this.strategy != null) {
      // 출력 값을 패턴의 계산값으로 변경
      val = this.strategy.calc(val);
    }
    // 콘솔 출력
    System.out.println("Data - " + val);
  }
}
// 실행 클래스
class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Process 인스턴스 생성
    Process process = new Process();
    // 데이터를 10 입력
    process.setData(10);
    // 콘솔 출력
    process.print();
    // 전략 패턴 타입 설정
    process.setStrategy(NormalStrategy.class);
    // 콘솔 출력
    process.print();
    // 전략 패턴 타입 설정
    process.setStrategy(SpecialStrategy.class);
    // 콘솔 출력
    process.print();
  }
}

위 예제는 전략 패턴에 flyweight 패턴을 추가하여 전략 패턴의 인스턴스를 취득할 때, 메모리 할당을 최소화하고 클래스의 재사용을 높였습니다.

using System;
using System.Collections.Generic;
// 전략 패턴의 인터페이스
interface IStrategy
{
  // 함수 추상화
  int Calc(int data);
}
// NormalStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class NormalStrategy : IStrategy
{
  // 함수 재정의
  public int Calc(int data)
  {
    // 입력값에 10을 곱해서 리턴
    return data * 10;
  }
}
// NormalStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class SpecialStrategy : IStrategy
{
  // 함수 재정의
  public int Calc(int data)
  {
    // 입력값에 10을 곱해서 리턴
    return data * 100;
  }
}
// Process 클래스
class Process
{
  // flyweight 패턴의 맵
  private Dictionary<Type, IStrategy> flyweight = new Dictionary<Type, IStrategy>();
  // 전략 패턴의 맴버 변수
  private IStrategy strategy = null;
  // 맴버 변수
  private int data = 0;
  // flyweight 패턴의 전략 패턴 인스턴스 취득 함수
  private IStrategy GetStrategy(Type clz)
  {
    // flyweight 맵에 클래스 타입이 있는지 확인
    if (!flyweight.ContainsKey(clz))
    {
      // 없으면 인스턴스를 생성해서 입력한다.
      flyweight.Add(clz, Activator.CreateInstance(clz) as IStrategy);
    }
    // 전략 패턴 인스턴스 취득
    return flyweight[clz];
  }
  // 전략 패턴 타입 입력
  public void SetStrategy(Type clz)
  {
    // flyweight 맵에서 인스턴스 취득
    this.strategy = GetStrategy(clz);
  }
  // 데이터 입력
  public void SetData(int data)
  {
    // 맴버 변수 설정
    this.data = data;
  }
  // 출력 함수
  public void Print()
  {
    // 출력 값의 임시 변수
    int val = data;
    // 전략 패턴이 설정되어 있으면
    if (this.strategy != null)
    {
      // 출력 값을 패턴의 계산값으로 변경
      val = this.strategy.Calc(val);
    }
    // 콘솔 출력
    Console.WriteLine("Data - " + val);
  }
}
// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // Process 인스턴스 생성
    Process process = new Process();
    // 데이터를 10 입력
    process.SetData(10);
    // 콘솔 출력
    process.Print();
    // 전략 패턴 타입 설정
    process.SetStrategy(typeof(NormalStrategy));
    // 콘솔 출력
    process.Print();
    // 전략 패턴 타입 설정
    process.SetStrategy(typeof(SpecialStrategy));
    // 콘솔 출력
    process.Print();
    // 아무 키나 누르면 종료
    Console.WriteLine("Press Any key...");
    Console.ReadLine();
  }
}

전략 패턴는 최대한 클래스의 결합도를 낮추고 재사용성을 높여서 성능을 개선하는 것이 주요 목표입니다. 그리고 클래스는 최대한 나누는 작업으로 인해 프로그램의 UT 테스트와 개별 테스트가 용이하다는 장점이 있습니다.

그런데 이 전략 패턴의 단점은 너무 지나치게 클래스를 쪼개여서 전략패턴으로 분산화시키면 가독성이 떨어진다는 단점이 있습니다.

그리고 클래스 작성이 지나치게 늘어나서 프로젝트 관리에도 어렵고 프로젝트 난이도가 상승하는 부분이 생기게 됩니다.


여기까지 디자인 패턴의 전략 패턴(Strategy pattern)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.