
추상 팩토리 패턴은 연관성이 있는 객체 군이 여러개 있을 경우 이들을 묶어 추상화하고, 어떤 구체적인 상황이 주어지면 팩토리 객체에서 집합으로 묶은 객체 군을 구현화하는 생성 패턴이다. 유저가 특정 객체를 사용할 때 팩토리 클래스만을 참조하여 특정 객체에 대한 구현부를 감추어 역할과 구현을 분리시킬 수 있다.
즉, 추상 팩토리의 핵심은 객체 '군'집합을 타입 별로 찍어낼 수 있다는 점이 포인트이다. 예를 들어 모니터, 마우스, 키보드를 묶은 전자 제품 군이 있는데 이들을 또 삼성 제품군이냐 애플 제품군이냐 로지텍 제품군이냐에 따라 집합이 브랜드 명으로 여러 갈래 나뉘게 될 때, 복잡하게 묶이는 이러한 제품군들을 관리와 확장하기 용이하게 패턴화 한것이 추상 팩토리이다.

AbstractFactory : 최상위 공장 클래스. 여러개의 제품들을 생성하는 여러 메소드들을 추상화 한다.ConcreteFactory : 서브 공장 클래스들은 타입에 맞는 제품 객체를 반환하도록 메소드들을 재정의한다.AbstractProduct : 각 타입의 제품들을 추상화한 인터페이스ConcreteProduct (ProductA ~ ProductB) : 각 타입의 제품 구현체들. 이들은 팩토리 객체로부터 생성된다. Client : Client는 추상화된 인터페이스만을 이용하여 제품을 받기 때문에, 구체적인 제품, 공장에 대해서는 모른다.
// Product A 제품군
interface AbstractProductA {
}
// Product A - 1
class ConcreteProductA1 implements AbstractProductA {
}
// Product A - 2
class ConcreteProductA2 implements AbstractProductA {
}
// Product B 제품군
interface AbstractProductB {
}
// Product B - 1
class ConcreteProductB1 implements AbstractProductB {
}
// Product B - 2
class ConcreteProductB2 implements AbstractProductB {
}
interface AbstractFactory {
AbstractProductA createProductA();
AbstractProductB createProductB();
}
// Product A1와 B1 제품군을 생산하는 공장군 1
class ConcreteFactory1 implements AbstractFactory {
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
}
// Product A2와 B2 제품군을 생산하는 공장군 2
class ConcreteFactory2 implements AbstractFactory {
public AbstractProductA createProductA() {
return new ConcreteProductA2();
}
public AbstractProductB createProductB() {
return new ConcreteProductB2();
}
}

class Client {
public static void main(String[] args) {
AbstractFactory factory = null;
// 1. 공장군 1을 가동시킨다.
factory = new ConcreteFactory1();
// 2. 공장군 1을 통해 제품군 A1를 생성하도록 한다 (클라이언트는 구체적인 구현은 모르고 인터페이스에 의존한다)
AbstractProductA product_A1 = factory.createProductA();
System.out.println(product_A1.getClass().getName()); // ConcreteProductA1
// 3. 공장군 2를 가동시킨다.
factory = new ConcreteFactory2();
// 4. 공장군 2를 통해 제품군 A2를 생성하도록 한다 (클라이언트는 구체적인 구현은 모르고 인터페이스에 의존한다)
AbstractProductA product_A2 = factory.createProductA();
System.out.println(product_A2.getClass().getName()); // ConcreteProductA2
}
}
코드를 보면 똑같은 createProductA() 메서드를 호출하지만 어떤 팩토리 객체이냐에 따라 반환되는 제품군이 다르게 된다.
이러한 추상 팩토리 패턴을 사용하게 되면..
많은 사람들이 추상 팩토리와 팩토리 메서드 패턴 차이에 대해 어려워한다. 왜냐하면 둘 모두 팩토리 객체가 대신 인스턴스 객체를 생성해준다는 점에서 구조가 거의 동일하기 때문이다. 어떤 디자인 패턴 서적에서는 추상 팩토리와 팩토리 메서드를 하나의 팩토리 패턴 카테고리로 다루기도 한다.
하지만 이 둘은 완전히 별개의 패턴이다. 따라서 예제를 통해 팩토리 메서드와 추상 팩토리 메서드를 각각 구현해보고 이 둘을 합쳐볼 것이다.
팩토리 메서드에 대한 자세한 설명에 대해서는 해당 문서를 참고하자.
실제 게임에서 있을듯한 상황을 가정해보자. 특정 맵에서 맵에 환경 오브젝트를 추가하는 로직을 구현한다고 하자. 나무(Tree), 바위(Rock), 풀(Grass)를 각각 맵에 잘 배치되기 위해 각기 객체로 지정한다. 그런데 맵 종류가 숲(Forest)와 사막(Desert)으로 종류가 나뉘는데 어떤 종류냐에 따라 생성될 환경 오브젝트의 모습이 달라질 수 있기 때문에 각기 생성하여 구현하고자 한다.
정리하면 이 3가지의 오브젝트들은 하나의 환경 오브젝트(EnvObj) 군으로 묶을 수 있으면 또한 Map별 군으로 나뉘게 된다.

using UnityEngine;
interface EnvObj
{
public void Create(); //환경 오브젝트 생성
}
public abstract class Tree : EnvObj {
public abstract void Create();
}
class ForestTree : Tree
{
public override void Create()
{
Debug.Log("Forest Tree 생성 완료");
}
}
class DesertTree : Tree
{
public override void Create()
{
Debug.Log("Desert Tree 생성 완료");
}
}
/*-------------------------------------------------------*/
public abstract class Rock : EnvObj {
public abstract void Create();
}
class ForestRock : Rock
{
public override void Create()
{
Debug.Log("Forest Rock 생성 완료");
}
}
class DesertRock : Rock
{
public override void Create()
{
Debug.Log("Desert Rock 생성 완료");
}
}
/*-------------------------------------------------------*/
public abstract class Grass : EnvObj {
public abstract void Create();
}
class ForestGrass : Grass
{
public override void Create()
{
Debug.Log("Forest Grass 생성 완료");
}
}
class DesertGrass : Grass
{
public override void Create()
{
Debug.Log("Desert Grass 생성 완료");
}
}
위의 구성을 팩토리 메서드 패턴으로 구현해보자. 팩토리 메서드 패턴의 공장 객체는 한가지 종류의 EnvObj만 생성하는 구조이다. 팩토리 메서드의 초점은 추상화된 팩토리 메서드를 각 서브 공장 클래스가 재정의하여 걸맞는 객체를 생성하는 것이기 때문이다. 그렇기 때문에 Tree를 생성한다고 해도 어느 Map에서 생성한 것인지는 메서드 내에서 분기문을 통해 구분해 주어야한다.
interface EnvObjFactoryMethod
{
EnvObj CreateOperation(string type); // 템플릿
EnvObj CreateEnvObj(string type); // 팩토리 메서드
}
class TreeFactory : EnvObjFactoryMethod
{
public EnvObj CreateOperation(string type)
{
EnvObj tree = CreateEnvObj(type);
//tree.후처리();
return tree;
}
public EnvObj CreateEnvObj(string type)
{
Tree tree = null;
switch (type)
{
case "Forest" :
tree = new ForestTree();
break;
case "Desert" :
tree = new DesertTree();
break;
}
return tree;
}
}
class RockFactory : EnvObjFactoryMethod
{
public EnvObj CreateOperation(string type)
{
EnvObj rock = CreateEnvObj(type);
//rock.후처리();
return rock;
}
public EnvObj CreateEnvObj(string type)
{
Rock rock = null;
switch (type)
{
case "Forest" :
rock = new ForestRock();
break;
case "Desert" :
rock = new DesertRock();
break;
}
return rock;
}
}
class GrassFactory : EnvObjFactoryMethod
{
public EnvObj CreateOperation(string type)
{
EnvObj grass = CreateEnvObj(type);
//tree.후처리();
return grass;
}
public EnvObj CreateEnvObj(string type)
{
Grass grass = null;
switch (type)
{
case "Forest" :
grass = new ForestGrass();
break;
case "Desert" :
grass = new DesertGrass();
break;
}
return grass;
}
}
using UnityEngine;
public class Env : MonoBehaviour
{
private void Start()
{
EnvObjFactoryMethod factory = null;
Tree tree = null;
Rock rock = null;
// Forest Tree 생성
factory = new TreeFactory();
tree = (Tree)factory.CreateOperation("Forest");
tree.Create();
// Desert Tree 생성
factory = new TreeFactory();
tree = (Tree)factory.CreateOperation("Desert");
tree.Create();
// Forest Rock 생성
factory = new RockFactory();
rock = (Rock)factory.CreateOperation("Forest");
rock.Create();
// Desert Rock 생성
factory = new RockFactory();
rock = (Rock)factory.CreateOperation("Desert");
rock.Create();
}
}

여기까지가 저번에 다뤘던 팩토리 메서드 패턴을 통해 구현한 예제이다. 실제 코드 실행 자체는 문제가 없지만 만일 기능을 확장할 필요가 있으면 문제가 생긴다. 예를 들어 맵 종류에 늪(Swamp)를 추가한다고 해보자. 그러면 각 메서드마다 있는 분기문 로직을 모두 수정해야 하는데, 그러면 OCP 원칙에 위배된다.
switch (type)
{
case "Forest" :
tree = new ForestTree();
break;
case "Desert" :
tree = new DesertTree();
break;
case "Swamp" : // 다른 rock과 grass도 모두 수정해야됨
tree = new SwampTree();
break;
}
그럼 이것을 추상 팩토리 패턴으로 구현하면 어떨까?
팩토리 메서드의 공장 객체는 한 종류의 EnvObj만 생성하지만, 추상 팩토리의 공장 객체는 하나의 객체에서 여러 종류의 컴포넌트들을 골라 생산할 수 있도록 구성한다.
interface EnvObjAbstractFactory
{
Tree createTree();
Rock createRock();
Grass createGrass();
}
class ForestFactory : EnvObjAbstractFactory
{
public Tree createTree() {
return new ForestTree();
}
public Rock createRock() {
return new ForestRock();
}
public Grass createGrass() {
return new ForestGrass();
}
}
class DesertFactory : EnvObjAbstractFactory
{
public Tree createTree() {
return new DesertTree();
}
public Rock createRock() {
return new DesertRock();
}
public Grass createGrass() {
return new DesertGrass();
}
}
using UnityEngine;
public class Env : MonoBehaviour
{
// 추상 팩토리에서 객체를 생성하는 부분 코드는 같기 때문에 따로 메서드로 묶음 분리
public static Tree CreateTree(EnvObjAbstractFactory fac)
{
return fac.CreateTree();
}
private void Start()
{
EnvObjAbstractFactory factory = null;
// Forest Tree 생성
factory = new ForestFactory();
Tree forestTree = CreateTree(factory);
forestTree.Create();
// Desert Tree 생성
factory = new DesertFactory();
Tree desertTree = CreateTree(factory);
desertTree.Create();
}
}

기존 팩토리 메서드에서는 다른 맵의 환경 오브젝트를 생성하기 위해선 문자열을 인자로 주어 메서드 내에서 분기문으로 객체 생성을 처리하였지만, 추상 팩토리에선 어떠한 팩토리 객체를 생성하느냐에 따라 같은 메서드를 호출해도 반환되는 결과가 다르게 된다.
하지만 추상 팩토리가 팩토리 메서드보다 무조건 좋다는 것은 아니다. 이 예제처럼 어떠한 환경 오브젝트를 '맵'에 따라 묶어 생성해야 할 때 추상 팩토리로 구성하는 것이 유지보수와 확장에 있어 더 유리하다는 것을 보여주는 것이다.
예를 들어 늪(Swamp)라는 새로운 맵이 추가된다고 해도, 기존의 코드 수정 없이 늪 오브젝트 구현체 클래스와 늪 팩토리 클래스만 적절하게 추가하면 확장이 완료되게 된다.
기존 팩토리 메서드로 설계 했을때는 메서드의 분기문을 일일이 뜯어 고치는 것에 비하면 확실히 OCP 원칙의 수정에는 닫혀있고 확장에는 열려있다는 말이 무슨 의미인지 체감이 될 것이다.
class SwampTree : Tree
{
public override void Create()
{
Debug.Log("Swamp Tree 생성 완료");
}
}
class SwampRock : Rock
{
public override void Create()
{
Debug.Log("Swamp Rock 생성 완료");
}
}
class SwampGrass : Grass
{
public override void Create()
{
Debug.Log("Swamp Grass 생성 완료");
}
}
class SwampFactory : EnvObjAbstractFactory
{
public Tree CreateTree() {
return new SwampTree();
}
public Rock CreateRock() {
return new SwampRock();
}
public Grass CreateGrass() {
return new SwampGrass();
}
}
그러나 모든 확장에 대해 유연하게 대처할 수 있는 것은 아니다. 새로운 맵이 아니라 새로운 환경 오브젝트인 꽃을 추가한다고 해보자. 그러면 모든 서브 팩토리 클래스마다 꽃을 생성하는 CreateFlower() 메서드를 추가해야되니 이때는 오히려 문제점으로 작용한다.
public interface EnvObjAbstractFactory
{
Tree CreateTree();
Rock CreateRock();
Grass CreateGrass();
Flower CreateFlower();
}
class ForestFactory : EnvObjAbstractFactory
{
public Tree CreateTree() {
return new ForestTree();
}
public Rock CreateRock() {
return new ForestRock();
}
public Grass CreateGrass() {
return new ForestGrass();
}
public Flower CreateFlower() {
return new ForestFlower();
}
}
기본적으로 팩토리 클래스는 호출되면 객체를 생성하기만 하면 되기 때문에 메모리 최적화를 위해 각 팩토리 클래스마다 싱글톤을 적용하는 것이 옳다.
// Forest Tree 생성
factory = new ForestFactory();
Tree forestTree = CreateTree(factory);
forestTree.Create();
// Desert Tree 생성
factory = new DesertFactory();
Tree desertTree = CreateTree(factory);
desertTree.Create();
물론 GC가 가종으로 지워주긴 하지만, 결국에 객체 제거 과정에서 Stop-the-world가 일어나게 된다. 가비지 컬렉션 = 렉! 꼭 기억하고 있자. 따라서 각 팩토리 클래스를 싱글톤화 시켜 메모리 적으로 최적화 시킨다.
public interface EnvObjAbstractFactory
{
Tree CreateTree();
Rock CreateRock();
Grass CreateGrass();
}
class ForestFactory : EnvObjAbstractFactory
{
private ForestFactory() { }
private static class SingleInstanceHolder
{
public static readonly ForestFactory INSTANCE = new ForestFactory();
}
public static ForestFactory GetInstance()
{
return SingleInstanceHolder.INSTANCE;
}
public Tree CreateTree() {
return new ForestTree();
}
public Rock CreateRock() {
return new ForestRock();
}
public Grass CreateGrass() {
return new ForestGrass();
}
}
class DesertFactory : EnvObjAbstractFactory
{
private DesertFactory() { }
private static class SingleInstanceHolder
{
public static readonly DesertFactory INSTANCE = new DesertFactory();
}
public static DesertFactory GetInstance()
{
return SingleInstanceHolder.INSTANCE;
}
public Tree CreateTree() {
return new DesertTree();
}
public Rock CreateRock() {
return new DesertRock();
}
public Grass CreateGrass() {
return new DesertGrass();
}
}
class SwampFactory : EnvObjAbstractFactory
{
private SwampFactory() { }
private static class SingleInstanceHolder
{
public static readonly SwampFactory INSTANCE = new SwampFactory();
}
public static SwampFactory GetInstance()
{
return SingleInstanceHolder.INSTANCE;
}
public Tree CreateTree() {
return new SwampTree();
}
public Rock CreateRock() {
return new SwampRock();
}
public Grass CreateGrass() {
return new SwampGrass();
}
}
// Forest Tree 생성
factory = ForestFactory.GetInstance();
Tree forestTree = CreateTree(factory);
forestTree.Create();
// Desert Tree 생성
factory = DesertFactory.GetInstance();
Tree desertTree = CreateTree(factory);
desertTree.Create();

사람들이 많이 착각하는게 추상 팩토리와 팩토리 메서드를 병행해서 사용할 수 없다고 알고 있다는 점이다. 이 둘은 엄연히 별개의 코드 패턴이고 그렇기에 동시에 사용할 수 있다.
팩토리 메서드는 추상 메서드를 통한 제품 구현과 더불어 객체 생성에 관한 전처리, 후처리를 해주는 로직이 핵심이며, 추상 팩토리는 여러 타입의 객체 군을 생성할 수 있는 것이 핵심이다. 따라서 필요하다면 둘을 적절히 조합해서 사용할 수도 있어야 한다. 추상 팩토리와 팩토리 메서드 패턴 둘을 조합하게 된다면, 여러 타입의 객체 군을 생성하면서 동시에 팩토리 메서드를 통해 전처리, 후처리 작업을 해주는 것이 가능해진다.
아래의 코드는 맵 군 별로 추상 팩토리를 구성하며, 각 객체 생성 메서드에 대해서 팩토리 메서드로 구성한 예제이다. 팩토리 메서드의 템플릿은 한꺼번에 환경 오브젝트들을 생성하고 추가 세팅한다는 컨셉으로써 하나의 리스트로 묶어 반환한다.
using System;
using System.Collections;
using System.Collections.Generic;
public interface EnvObjAbstractFactoryMethod
{
Tree CreateTree();
Rock CreateRock();
Grass CreateGrass();
public List<EnvObj> createOperation()
{
Tree tree = CreateTree();
Rock rock = CreateRock();
Grass grass = CreateGrass();
// tree.추가세팅(); //후처리
// rock.추가세팅(); //후처리
// grass.추가세팅(); //후처리
return new List<EnvObj> { tree, rock, grass };
}
}
class ForestFactoryMethod : EnvObjAbstractFactoryMethod
{
private ForestFactoryMethod() { }
private static class SingleInstanceHolder
{
public static readonly ForestFactoryMethod INSTANCE = new ForestFactoryMethod();
}
public static ForestFactoryMethod GetInstance()
{
return SingleInstanceHolder.INSTANCE;
}
public Tree CreateTree() {
return new ForestTree();
}
public Rock CreateRock() {
return new ForestRock();
}
public Grass CreateGrass() {
return new ForestGrass();
}
}
class DesertFactoryMethod : EnvObjAbstractFactoryMethod
{
private DesertFactoryMethod() { }
private static class SingleInstanceHolder
{
public static readonly DesertFactoryMethod INSTANCE = new DesertFactoryMethod();
}
public static DesertFactoryMethod GetInstance()
{
return SingleInstanceHolder.INSTANCE;
}
public Tree CreateTree() {
return new DesertTree();
}
public Rock CreateRock() {
return new DesertRock();
}
public Grass CreateGrass() {
return new DesertGrass();
}
}
class SwampFactoryMethod : EnvObjAbstractFactoryMethod
{
private SwampFactoryMethod() { }
private static class SingleInstanceHolder
{
public static readonly SwampFactoryMethod INSTANCE = new SwampFactoryMethod();
}
public static SwampFactoryMethod GetInstance()
{
return SingleInstanceHolder.INSTANCE;
}
public Tree CreateTree() {
return new SwampTree();
}
public Rock CreateRock() {
return new SwampRock();
}
public Grass CreateGrass() {
return new SwampGrass();
}
}
using System.Collections.Generic;
using UnityEngine;
public class Env : MonoBehaviour
{
private void Start()
{
EnvObjAbstractFactoryMethod factory = null;
factory = ForestFactoryMethod.GetInstance();
List<EnvObj> list = factory.createOperation();
Debug.Log(list);
foreach (EnvObj obj in list)
{
obj.Create();
}
}
}

만일 Tree 따로, Rock 따로 하고 싶다면 별도의
CreateTreeOperation()이나CreateRockOperation()메서드로 각기 구현해주면 된다.
둘 다 공장 객체를 통해 구체적인 타입을 감추고 객체 생성에 관여하는 패턴 임은 동일하다. 또한 공장 클래스와 인스턴스 클래스를 각각 나뉘어 느슨한 결합 구조를 구성하는 모습 역시 유사하다.
그러나 주의할 것은 추상 팩토리 패턴이 팩토리 메서드 패턴의 상위 호환이 아니라는 점이다. 두 패턴의 차이는 명확하기 때문에 적절한 패턴을 선택해서 사용해야 한다.
예를 들어 팩토리 메소드 패턴은 객체 생성 이후 해야 할 일의 공통점을 정의하는데 초점을 맞추는 반면, 추상 팩토리 패턴은 생성해야 할 객체 집합 군의 공통점에 초점을 맞춘다.
단, 이 둘을 유사점과 차이점을 조합해서 복합 패턴을 구성하는 것도 가능하다.