[Design Pattern] 1-3. 팩토리 메서드 패턴 (Factory method pattern)


Study/Design Pattern  2021. 6. 23. 18:53

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


이 글은 디자인 패턴의 팩토리 메서드 패턴(Factory method pattern)에 대한 글입니다.


이전 글에서 빌드 패턴에 대해서 설명했었습니다.

링크 - [Design Pattern] 1-2. 빌더 패턴(Builder pattern)

빌드 패턴이란 간단히 Builder 클래스와 Director 클래스의 조합으로 하나의 인스턴스가 생성되는 생성 패턴입니다. 이 팩토리 메서드 패턴도 빌드 패턴과 마찬가지로 생성 패턴 중 하나이므로 인스턴스를 생성하는 패턴입니다.

이 팩토리 패턴은 메서드에서 파라미터에 의해 생성되는 인스턴스가 바뀌는 형태를 말합니다.

출처 - https://en.wikipedia.org/wiki/Factory_method_pattern
#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// 추상 클래스
class INode {
public:
  // 추상 메서드
  virtual void print() = 0;
};
// Node1 클래스, INode 클래스를 상속 받음
class Node1 : public INode {
public:
  // 출력 함수
  virtual void print() {
    // 콘솔 출력
    cout << "Node1 Class " << endl;
  }
};
// Node2 클래스, INode 클래스를 상속 받음
class Node2 : public INode {
public:
  // 출력 함수
  virtual void print() {
    // 콘솔 출력
    cout << "Node2 Class " << endl;
  }
};
// 팩토리 메서드 패턴의 함수
INode* factory(int type) {
  // 파라미터 type의 값이 0일 경우
  if (type == 0) {
    // Node1 인스턴스를 생성
    return new Node1();
  }
  // 파라미터 type의 값이 1일 경우
  else if (type == 1) {
    // Node2 인스턴스를 생성
    return new Node2();
  }
  // null 리턴
  return nullptr;
}
// 실행 함수
int main() {
  // 팩토리 함수에서 파라미터로 0을 넣으면
  INode* node = factory(0);
  // Node1 클래스의 print 함수가 실행
  node->print();
  // 메모리 해제
  delete node;
  // 팩토리 함수에서 파라미터로 1을 넣으면
  node = factory(1);
  // Node2 클래스의 print 함수가 실행
  node->print();
  // 메모리 해제
  delete node;
  return 0;
}

위 예제를 보면 factory 함수에서 파라미터의 값에 따라 Node1 클래스 인스턴스가 생성되거나 Node2 클래스 인스턴스가 생성되는 것이 결정됩니다.


빌드 패턴에 비하면 매우 단순한 구조입니다.

// 실행 함수가 있는 클래스
public class Program {
  // 팩토리 메서드 패턴의 함수
  public static INode factory(String type) {
    // 파라미터 type이 대소문자 관계없이 NODE1인 경우
    if ("NODE1".equalsIgnoreCase(type)) {
      // Node1 클래스의 인스턴스를 생성
      return new Node1();
    // 파라미터 type이 대소문자 관계없이 NODE2인 경우
    } else if ("NODE2".equalsIgnoreCase(type)) {
      // Node2 클래스의 인스턴스를 생성
      return new Node2();
    }
    // 위 조건이 없으면 null를 리턴
    return null;
  }
  // 실행 함수
  public static void main(String... args) {
    // 팩토리 메서드에 node1의 값을 넣으면
    INode node = factory("node1");
    // Node1 인스턴스가 생성되어 콘솔에 출력된다.
    node.print();
    // 팩토리 메서드에 node2의 값을 넣으면
    node = factory("node2");
    // Node2 인스턴스가 생성되어 콘솔에 출력된다.
    node.print();
  }
}
// 인터페이스
interface INode {
  // 콘솔 출력 추상 메소드
  void print();
}
// Node1 클래스, INode 인스턴스를 상속 받음
class Node1 implements INode {
  // 재정의
  @Override
  public void print() {
    // 콘솔 출력
    System.out.println("Node 1 class");
  }
}
// Node2 클래스, INode 인스턴스를 상속 받음
class Node2 implements INode {
  // 재정의
  @Override
  public void print() {
    // 콘솔 출력
    System.out.println("Node 2 class");
  }
}

Java 예제에서는 파라미터 값을 String으로 받았습니다. 즉 String 데이터에 따라 인스턴스를 생성할 수 있습니다.

이렇게 되면 실제 프로젝트에서는 데이터베이스나 유저로부터 받는 값에 따라 생성되는 인스턴스를 바꾸어 실행되는 로직을 바꿀 수가 있습니다.


그리고 팩토리 메서드에서는 리턴되는 타입을 한 종류의 타입으로 통일을 해야 하기 때문에 interface를 사용했습니다.

using System;

namespace Example
{
  // 인터페이스
  interface INode
  {
    // 콘솔 출력 추상 메소드
    void Print();
  }
  // Node1 클래스, INode 인스턴스를 상속 받음
  class Node1 : INode
  {
    // 재정의 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Node1 Class");
    }
  }
  // Node2 클래스, INode 인스턴스를 상속 받음
  class Node2 : INode
  {
    // 재정의 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Node2 Class");
    }
  }
  // 열러형 타입
  enum NodeType
  {
    Node1,
    Node2
  }
  // 실행 함수가 있는 클래스
  class Program
  {
    // 팩토리 메서드 패턴의 함수
    static INode Factory(NodeType type)
    {
      // type의 값이 NodeType.Node1라면
      if (type == NodeType.Node1)
      {
        // Node1 클래스의 인스턴스를 생성
        return new Node1();
      }
      // type의 값이 NodeType.Node2라면
      else if (type == NodeType.Node2)
      {
        // Node2 클래스의 인스턴스를 생성
        return new Node2();
      }
      // 위 조건이 없으면 null를 리턴
      return null;
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // 팩토리 메서드에 NodeType.Node1의 값을 넣으면
      INode node = Factory(NodeType.Node1);
      // Node1 인스턴스가 생성되어 콘솔에 출력된다.
      node.Print();
      // 팩토리 메서드에 NodeType.Node2의 값을 넣으면
      node = Factory(NodeType.Node2);
      // Node2 인스턴스가 생성되어 콘솔에 출력된다.
      node.Print();

      Console.WriteLine("Press any key...");
      Console.ReadKey();
    }
  }
}

위 에제는 String 타입이 아닌 열거형 타입 값에 의해 인스턴스를 생성했습니다.

String 타입을 사용하는 것은 만약 String 값에 오타가 있을 경우, 디버그 단계에서 에러를 잡아주지 않기 때문에 버그가 발생할 가능성이 있는데 열거형으로 하면 버그가 발생할 가능성은 조금은 줄어 들겠네요.


팩토리 메서드 패턴은 보통 싱글톤 패턴과 많이 엮어서 사용합니다. 그러니 Entity 타입의 데이터 클래스보다는 Controller같은 처리와 관련된 클래스에 사용하게 되는 것입니다.

using System;

namespace Example
{
  // 인터페이스
  interface INode
  {
    // 콘솔 출력 추상 메소드
    void Print();
  }
  // Node1 클래스, INode 인스턴스를 상속 받음
  class Node1 : INode
  {
    // 싱글톤 패턴을 위한 변수
    private static Node1 singleton = null;
    // 생성자 private
    private Node1() { }
    // 인스턴스 취득 함수
    public static Node1 GetInstance()
    {
      // 인스턴스가 없을 경우
      if(singleton == null)
      {
        // 생성
        singleton = new Node1();
      }
      // 인스턴스 리턴
      return singleton;
    }
    // 재정의 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Node1 Class");
    }
  }
  // Node2 클래스, INode 인스턴스를 상속 받음
  class Node2 : INode
  {
    // 싱글톤 패턴을 위한 변수
    private static Node2 singleton = null;
    // 생성자 private
    private Node2() { }
    // 인스턴스 취득 함수
    public static Node2 GetInstance()
    {
      // 인스턴스가 없을 경우
      if (singleton == null)
      {
        // 생성
        singleton = new Node2();
      }
      // 인스턴스 리턴
      return singleton;
    }
    // 재정의 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("Node2 Class");
    }
  }
  // 열거형 타입
  enum NodeType
  {
    Node1,
    Node2
  }
  // 실행 함수가 있는 클래스
  class Program
  {
    // 팩토리 메서드 패턴의 함수
    static INode Factory(NodeType type)
    {
      // type의 값이 NodeType.Node1라면
      if (type == NodeType.Node1)
      {
        // Node1 클래스의 인스턴스를 생성
        return Node1.GetInstance();
      }
      // type의 값이 NodeType.Node2라면
      else if (type == NodeType.Node2)
      {
        // Node2 클래스의 인스턴스를 생성
        return Node2.GetInstance();
      }
      // 위 조건이 없으면 null를 리턴
      return null;
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // 팩토리 메서드에 NodeType.Node1의 값을 넣으면
      INode node = Factory(NodeType.Node1);
      // Node1 인스턴스가 생성되어 콘솔에 출력된다.
      node.Print();
      // 팩토리 메서드에 NodeType.Node1의 값을 넣으면
      node = Factory(NodeType.Node2);
      // Node2 인스턴스가 생성되어 콘솔에 출력된다.
      node.Print();

      Console.WriteLine("Press any key...");
      Console.ReadKey();
    }
  }
}

위 예제에서는 Node1 클래스와 Node2 클래스에 생성자를 private 해서 GetInstance로 취득하게 하는 싱글톤 패턴을 만들었습니다.

팩토리 메서드에서는 파라미터에 의해 각 클래스의 GetInstance 함수를 호출하여 인스턴스를 리턴하게 됩니다.


이런 구조를 어디서 많이 보는가 하니 웹에서 MVC 형태의 Controller 클래스에서 웹의 URL 요청에 의해 호출되는 클래스 인스턴스가 바뀌는 것과 같은 구조라고 할 수 있습니다. (Route 클래스)


여기까지 디자인 패턴의 팩토리 메서드 패턴(Factory method pattern)에 대한 글이었습니다.


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