[Design Pattern] 1-4. 추상 팩토리 패턴 (Abstract factory pattern)


Study/Design Pattern  2021. 10. 15. 18:50

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


이 글은 디자인 패턴의 추상 팩토리 패턴(Abstract factory pattern)에 대한 글입니다.


디자인 패턴의 생성 패턴 중에서 가장 복잡한 패턴인 추상 팩토리 패턴입니다.

구조는 복잡하지만 자세히 보면 팩토리 메서트 패턴에서 팩토리를 클래스로 만들고 그 위로 추상 인터페이스를 만들어서 사양에 따라 팩토리를 취득하고 그 팩토리 안에서 클래스를 취득하는 구조입니다.

그러니깐 팩토리 메서드 패턴이 중첩되어 있다라고 생각하면 쉽습니다.

출처 - https://en.wikipedia.org/wiki/Abstract_factory_pattern
#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// 추상 클래스
class IDao {
public:
  // 추상 메서드
  virtual string getData() = 0;
};
// 추상 팩토리 클래스
class IFactory {
public:
  // 추상 메서드 (IDao 타입의 클래스를 리턴한다.)
  virtual IDao* getTypeDao() = 0;
};
// ATypeDAO 클래스, IDao 추상 클래스를 상속
class ATypeDAO : public IDao {
public:
  // 함수 재정의
  virtual string getData() {
    // string 값 리턴
    return "ATypeDAO - getData()";
  }
};
// 팩토리 클래스, IFactory 추상 팩토리 클래스를 상속
class AFactory : public IFactory {
public:
  // 함수 재정의해서 ATypeDAO 클래스의 인스턴스를 리턴한다.
  virtual IDao* getTypeDao() {
    return new ATypeDAO();
  }
};
// BTypeDAO 클래스, IDao 추상 클래스를 상속 
class BTypeDAO : public IDao {
public:
  // 함수 재정의
  virtual string getData() {
    // string 값 리턴
    return "BTypeDAO - getData()";
  }
};
// 팩토리 클래스, IFactory 추상 팩토리 클래스를 상속
class BFactory : public IFactory {
public:
  // 함수 재정의해서 BTypeDAO 클래스의 인스턴스를 리턴한다.
  virtual IDao* getTypeDao() {
    return new BTypeDAO();
  }
};
// 팩토리 패턴, 파라미터 값의 의해 팩토리 클래스의 인스턴스를 리턴한다.
IFactory* getFactory(int type) {
  // 0의 값이라면 AFactory 클래스의 인스턴스를 리턴
  if (type == 0) {
    return new AFactory();
  }
  // 0의 값이 아니라면 AFactory 클래스의 인스턴스를 리턴
  else {
    return new BFactory();
  }
}
// 실행 함수
int main() {
  // 팩토리 함수로부터 팩토리 인스턴스를 받는다.
  IFactory* factory = getFactory(0);
  // 함수를 통해서 ATypeDAO 클래스의 인스턴스를 받는다.(여기서는 빌드 패턴(여기서 다시 팩토리 패턴을 넣어도 된다.))
  IDao* dao = factory->getTypeDao();
  // 콘솔에 출력
  cout << dao->getData() << endl;
  // 메모리 해제
  delete dao;
  delete factory;
  // 팩토리 함수로부터 팩토리 인스턴스를 받는다.
  factory = getFactory(1);
  // 함수를 통해서 BTypeDAO 클래스의 인스턴스를 받는다.(여기서는 빌드 패턴(여기서 다시 팩토리 패턴을 넣어도 된다.))
  dao = factory->getTypeDao();
  // 콘솔에 출력
  cout << dao->getData() << endl;
  // 메모리 해제
  delete dao;
  delete factory;
  return 0;
}

위 예제에서 보면 Factory 클래스를 getFactory라는 함수로 인스턴스를 받았습니다.

다시 Factory 클래스에서는 getTypeDao를 통해서 인스턴스를 받습니다. 저는 여기서 빌드 패턴을 통해서 IDao를 받지만 getTypeDao에 파라미터를 넣고 다시 팩토리 메서드 패턴을 사용할 수 있습니다.

// 실행 함수가 있는 클래스
public class Program {
  // 팩토리 메서드 패턴으로 팩토리 클래스를 받는다.
  private static IFactory getFactory(String type) {
    // 입력 값이 A라면 AFactory 클래스의 인스턴스를 리턴
    if ("A".equals(type.toUpperCase())) {
      return new AFactory();
    // 입력 값이 B라면 BFactory 클래스의 인스턴스를 리턴
    } else if ("B".equals(type.toUpperCase())) {
      return new BFactory();
    }
    // 조건에 맞지 않으면 null
    return null;
  }
  // 실행 함수
  public static void main(String[] args) {
    // 팩토리 메서드 패턴으로 팩토리 클래스를 받는다.
    var factory = getFactory("A");
    // 여기서 다시 getType 함수를 통해서 AType1Dao 클래스의 인스턴스를 받고 getData() 함수를 콘솔에 출력
    System.out.println(factory.getTypeDao(1).getData());
    // 여기서 다시 getType 함수를 통해서 AType2Dao 클래스의 인스턴스를 받고 getData() 함수를 콘솔에 출력
    System.out.println(factory.getTypeDao(2).getData());
    
    // 팩토리 메서드 패턴으로 팩토리 클래스를 받는다.
    factory = getFactory("B");
    // 여기서 다시 getType 함수를 통해서 BType1Dao 클래스의 인스턴스를 받고 getData() 함수를 콘솔에 출력
    System.out.println(factory.getTypeDao(1).getData());
    // 여기서 다시 getType 함수를 통해서 BType2Dao 클래스의 인스턴스를 받고 getData() 함수를 콘솔에 출력
    System.out.println(factory.getTypeDao(2).getData());
  }
}
// 인터페이스
interface IDao {
  // 최종 클래스에서 받은 데이터
  String getData();
}
// 추상 팩토리 클래스 인터페이스
interface IFactory {
  // 여기도 역시 팩토리 메서드를 통해서 IDao 클래스의 인스턴스를 받음
  IDao getTypeDao(int type);
}
// AType1Dao 클래스, IDao 인터페이스를 상속
class AType1Dao implements IDao {
  // 함수 재정의
  @Override
  public String getData() {
    // 결과값 리턴
    return "AType1Dao - getData()";
  }
}
// AType2Dao 클래스, IDao 인터페이스를 상속
class AType2Dao implements IDao {
  // 함수 재정의
  @Override
  public String getData() {
    // 결과값 리턴
    return "AType2Dao - getData()";
  }
}
// 팩토리 클래스, IFactory 인터페이스를 상속
class AFactory implements IFactory {
  // 함수 재정의
  @Override
  // 팩토리 메서드 패턴을 통해서 IDao 타입의 클래스를 취득
  public IDao getTypeDao(int type) {
    // 값이 1이라면 AType1Dao 클래스의 인터페이스를 리턴
    if (type == 1) {
      return new AType1Dao();
    // 값이 2이라면 AType1Dao 클래스의 인터페이스를 리턴
    } else if (type == 2) {
      return new AType2Dao();
    }
    // 조건에 맞지 않으면 null
    return null;
  }
}
// BType1Dao 클래스, IDao 인터페이스를 상속
class BType1Dao implements IDao {
  // 함수 재정의
  @Override
  public String getData() {
    // 결과값 리턴
    return "BType1Dao - getData()";
  }
}
// BType2Dao 클래스, IDao 인터페이스를 상속
class BType2Dao implements IDao {
  // 함수 재정의
  @Override
  public String getData() {
    // 결과값 리턴
    return "BType2Dao - getData()";
  }
}
// 팩토리 클래스, IFactory 인터페이스를 상속
class BFactory implements IFactory {
  // 함수 재정의
  @Override
  // 팩토리 메서드 패턴을 통해서 IDao 타입의 클래스를 취득
  public IDao getTypeDao(int type) {
    // 값이 1이라면 BType1Dao 클래스의 인터페이스를 리턴
    if (type == 1) {
      return new BType1Dao();
    // 값이 2이라면 BType1Dao 클래스의 인터페이스를 리턴
    } else if (type == 2) {
      return new BType2Dao();
    }
    // 조건에 맞지 않으면 null
    return null;
  }
}

위 예제는 Java로 작성된 추상 팩토리 패턴 예제입니다.

C/C++과는 다르게 팩토리 클래스 안에서 빌드 패턴 대신에 다시 팩토리 메서드 패턴으로 인스턴스를 취득합니다.

using System;

namespace Example
{
  // 인터페이스
  interface IDao
  {
    // 콘솔 출력
    void Print();
  }
  // 추상 팩토리 클래스 인터페이스
  interface IFactory
  {
    // 추상 메서드 (IDao 타입의 클래스를 리턴한다.)
    IDao GetTypeDao();
  }
  // ATypeDAO 클래스, IDao 추상 클래스를 상속
  class ATypeDao : IDao
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("ATypeDao - GetData()");
    }
  }
  // 팩토리 클래스, IFactory 추상 팩토리 클래스를 상속
  class AFactory : IFactory
  {
    // ATypeDao 클래스의 인스턴스를 리턴
    public IDao GetTypeDao()
    {
      // ATypeDao 클래스 인스턴스 생성
      return new ATypeDao();
    }
  }
  // BTypeDAO 클래스, IDao 추상 클래스를 상속
  class BTypeDao : IDao
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine("BTypeDao - GetData()");
    }
  }
  // 팩토리 클래스, IFactory 추상 팩토리 클래스를 상속
  class BFactory : IFactory
  {
    // BTypeDao 클래스의 인스턴스를 리턴
    public IDao GetTypeDao()
    {
      // BTypeDao 클래스 인스턴스 생성
      return new BTypeDao();
    }
  }
  // 실행 함수가 있는 클래스
  public class Program
  {
    // 팩토리 메서드 패턴으로 팩토리 클래스 인스턴스를 취득하는 함수
    private static IFactory GetFactory(String type)
    {
      // 파라미터 값이 A의 경우
      if ("A".Equals(type, StringComparison.OrdinalIgnoreCase))
      {
        // AFactory 클래스 인스턴스를 생성해서 리턴
        return new AFactory();
      }
      // 파라미터 값이 B의 경우
      else if ("B".Equals(type, StringComparison.OrdinalIgnoreCase))
      {
        // BFactory 클래스 인스턴스를 생성해서 리턴
        return new BFactory();
      }
      // 조건에 맞지 않으면 null
      return null;
    }
    // 실행 함수
    public static void Main(string[] args)
    {
      // 팩토리 메서드 패턴 함수로 팩토리를 취득한다.
      var factory = GetFactory("A");
      // 취득된 팩토리에서 ATypeDao 인스턴스의 Print 함수를 실행
      factory.GetTypeDao().Print();
      // 팩토리 메서드 패턴 함수로 팩토리를 취득한다.
      factory = GetFactory("B");
      // 취득된 팩토리에서 BTypeDao 인스턴스의 Print 함수를 실행
      factory.GetTypeDao().Print();
      // 아무 키나 누르면 종료
      Console.WriteLine("Press Any key...");
      Console.ReadLine();
    }
  }
}

위 예제는 C/C++과 같이 팩토리 클래스에서 빌드 패턴으로 인스턴스를 취득 후에 실행합니다.


제가 Factory 클래스 말고 일반 클래스를 Dao라는 클래스 명으로 사용했습니다.

왜냐하면 이 추상 팩토리 패턴이 ORM 프레임워크에서 가장 많이 사용되는 패턴이기 때문입니다.


예를 들면 데이터 베이스의 각 테이블의 Dao 클래스를 만듭니다. 그런데 사양에 따라서 이게 Oracle이 될 수 있고, Mssql이 될 수 있고, Mysql(MariaDB)가 될 수도 있습니다.

각 데이터 베이스 시스템의 테이블의 설계 구조는 같다는 가정하에 이 추상 팩토리 패턴을 사용하면 사양에 따라 Oracle용 Dao 생성 팩토리를 생성할 수 있고, Mssql용 생성 팩토리를 생성할 수 있는 것입니다.


그 밖에 데이터 관리나 생성, PDF 생성이나 Excel 생성등에서 사양에 따른 장치를 구분할 때, 해당 클래스의 구조는 같게 만든다고 할 때, 자주 사용되는 패턴입니다.


여기까지 디자인 패턴의 추상 팩토리 패턴(Abstract factory pattern)에 대한 글이었습니다.


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