
팩토리 메서드 패턴은 객체 생성을 공장(Factory) 클래스로 캡슐화 처리하여 대신 생성하게 하는 생성 디자인 패턴이다. 즉, 클라이언트에서 직접 new 연산자를 통해 객체를 생성하는 것이 아닌, 객체들을 도맡아 생성하는 공장 클래스를 만들고, 이를 상속하는 서브 공장 클래스의 메서드에서 여러가지 객체 생성을 각각 책임 지는 것이다. 이를 위해 객체 생성에 필요한 과정을 템플릿 처럼 미리 구성해놓고, 객체 생성에 관한 전처리나 후처리를 통해 생성 과정을 다양하게 처리하여 객체를 유연하게 정할 수 있는 특징도 있다.
생성 패턴은 잘 알고 있는 것이 좋다. 실제 개발 업무는 대부분 협업으로 진행되고 그를 위해 형상 관리 도구(대표적으호 Git)을 사용하는데, 이로 인한 branch의 conflict가 빈번하게 일어난다. 그래서 최대한 협업을 진행할 때 개별의 공간에서 개발을 하는 것을 지양하는데, 이를 위해 동적 생성을 하는 것이 굉장히 유리하다. 지금 진행하는 생성 패턴들이 대부분 이러한 동적 생성의 방식을 다루고 있다고 볼 수 있다.

팩토리 객체와 생성된 객체 간의 느슨한 결합 구조로 되어있다.
Creator : 최상위 공장 클래스로서, 팩토리 메서드를 추상화하여 서브 클래스로 하여금 구현하도로 함ConcreteCreator : 각 서브 공장 클래스들은 이에 맞는 객체를 반환하도록 생성 추상 메소드를 재정의한다. 즉, 객체 하나당 그에 걸맞는 생산 공장 객체가 위치된다.Product : 객체 구현체를 추상화ConcreteProduct : 객체 구현체요약하자면, 팩토리 메서드 패턴은 객체를 만들어내는 공장(공장도 객체)을 만드는 패턴이다. 어떤 클래스의 인스턴스를 만들지는 미리 정의한 공장 서브 클래스에서 결정한다. 객체 생성을 이러한 과정을 통해 구현하는 이유는 객체 간의 결합도를 낮추고 유지보수에 용이해지기 때문이다.
항상 언급하지만 객체는 단순히 유형의 물체 뿐만 아니라, 전략 + 상태와 같은 무형 객체가 있을 수도 있고, 공장과 같은 중간 생성자 역할 또한 객체가 할 수 있다.
Template Method = Factory Method? : 이름이 비슷해서 비슷한 패턴인 것 같지만 전혀 다르다. 템플릿 메서드는 행동 패턴이고 팩토리 메서드는 생성 패턴이다. 클래스 구조의 결은 둘이 비슷하다고도 할 수는 있는데, 인스턴스를 생성하는 템플릿 메서드 패턴으로 구성한 것이 팩토리 메서드 패턴이 되기 때문이다.
템플릿 메서드 패턴에서는 하위 클래스에서 구체적인 처리 알고리즘의 내용을 만들도록 추상 메서드를 상속 시켰다. 이 로직을 알고리즘이 아닌 인스턴스 생성에 적용한 것이 팩토리 메서드 패턴이다.

// 제품 객체 추상화 (인터페이스)
interface IProduct {
void setting();
}
// 제품 구현체
class ConcreteProductA implements IProduct {
public void setting() {
}
}
class ConcreteProductB implements IProduct {
public void setting() {
}
}
// 공장 객체 추상화 (추상 클래스)
abstract class AbstractFactory {
// 객체 생성 전처리 후처리 메소드 (final로 오버라이딩 방지, 템플릿화)
final IProduct createOperation() {
IProduct product = createProduct(); // 서브 클래스에서 구체화한 팩토리 메서드 실행
product.setting(); // .. 이밖의 객체 생성에 가미할 로직 실행
return product; // 제품 객체를 생성하고 추가 설정하고 완성된 제품을 반환
}
// 팩토리 메소드 : 구체적인 객체 생성 종류는 각 서브 클래스에 위임
// protected 이기 때문에 외부에 노출이 안됨
abstract protected IProduct createProduct();
}
// 공장 객체 A (ProductA를 생성하여 반환)
class ConcreteFactoryA extends AbstractFactory {
@Override
public IProduct createProduct() {
return new ConcreteProductA();
}
}
// 공장 객체 B (ProductB를 생성하여 반환)
class ConcreteFactoryB extends AbstractFactory {
@Override
public IProduct createProduct() {
return new ConcreteProductB();
}
}

class Client {
public static void main(String[] args) {
// 1. 공장 객체 생성 (리스트)
AbstractFactory[] factory = {
new ConcreteFactoryA(),
new ConcreteFactoryB()
};
// 2. 제품A 생성 (안에서 createProduct() 와 생성 후처리 실행)
IProduct productA = factory[0].createOperation();
// 3. 제품B 생성 (안에서 createProduct() 와 생성 후처리 실행)
IProduct productB = factory[1].createOperation();
}
}
상황에 따라 적절한 객체를 생성하는 코드는 자주 중복될 수 있다. 그리고 객체 생성 방식의 변화는 해당되는 모든 코드 부분을 변경해야 하는 문제가 발생한다. 따라서 객체의 생성 코드를 별도의 클래스 / 메서드로 분리 함으로써 객체 생성의 변화에 대해 대비를 하기 위해 팩토리 메서드 패턴을 이용한다고 보면 된다. 특정 기능의 구현은 별개의 클래스로 제공되는 것이 바람직한 설계이기 때문이다.
이러한 팩토리 메서드 패턴을 사용하게 되면..

생성에 대한 추상화 인터페이스와 생성에 대한 구현 부분을 별도로 관리 가능
예제로 몬스터(Monster)를 만드는 로직을 구성한다고 가정하자.
가장 간단한 방법은 그냥 Monster 객체를 만들어 반환하는 메서드를 정의하는 것이다. 매개변수 입력 값에 따라 각기 다른 타입의 몬스터를 생성하도록 분기문도 적용해주었다.
using UnityEngine;
public class Monster : MonoBehaviour
{
public int monsterType;
public string monsterName;
public int monsterHp;
public bool monsterAgressive;
public string PrintAboutMonster() {
return string.Format("Monster {{ type: '{0}', name: '{1}', HP: '{2}', agressive: '{3}' }}", monsterType, monsterName, monsterHp, monsterAgressive);
}
}
using UnityEngine;
public class createMonster : MonoBehaviour
{
public static Monster createMob(int type) {
if (type == 0) {
throw new System.Exception("Monster Type Error!");
}
// 몬스터 객체 생성
GameObject monsterObject = new GameObject();
Monster monster = monsterObject.AddComponent<Monster>();
// 몬스터 개체 생성 후처리
monster.monsterType = type;
if(type == 1) {
monster.monsterName = "Goblin";
monster.monsterHp = 100;
monster.monsterAgressive = true;
} else if(type == 2) {
monster.monsterName = "Slime";
monster.monsterHp = 50;
monster.monsterAgressive = false;
} else if(type == 3) {
monster.monsterName = "Skeleton";
monster.monsterHp = 200;
monster.monsterAgressive = true;
}
//게임 오브젝트 이름을 몬스터 이름으로 설정
monsterObject.name = monster.monsterName;
Debug.Log($"{monster.name} 몬스터가 스폰 되었습니다!");
return monster;
}
private void Start() {
mobSpawn();
}
private void mobSpawn() {
Monster goblin1 = createMob(1);
Debug.Log(goblin1.PrintAboutMonster());
Monster slime1 = createMob(2);
Debug.Log(slime1.PrintAboutMonster());
}
}


당연하겠지만 위의 방법은 클린하지 않는 코드이다. 기존의 전략 패턴이나, 상태 패턴에서 언급했듯이 분기문 내부에 로직을 처리하는 코딩은 권장하지 않는다. 협업이 어려워질 뿐만 아니라 새로운 몬스터가 늘어날 수록 실제 분기문이 선형적으로 늘어나 복잡해질 것이다. 또한 향후에 몬스터 클래스 구성 자체가 변경되면 분기문 로직도 통째로 바꿔줘야 하는 번거로움이 생긴다.
심플 팩토리 메서드 패턴이란, 객체 생성을 담당하는 팩토리 클래스를 심플하게 하나만 구성하는 방법을 말한다. 아래 코드와 같이 몬스터 타입에 따른 몬스터 객체들을 상속 관계로 구성해주고, monsterFactory 클래스를 만들고 모든 몬스터 객체 생성을 이 공장 클래스에 위임한다.
using UnityEngine;
public class Monster : MonoBehaviour
{
public int monsterType;
public string monsterName;
public int monsterHp;
public bool monsterAgressive;
public string PrintAboutMonster() {
return string.Format("Monster {{ type: '{0}', name: '{1}', HP: '{2}', agressive: '{3}' }}", monsterType, monsterName, monsterHp, monsterAgressive);
}
}
public class Goblin : Monster {
public Goblin() {
monsterType = 1;
monsterName = "Goblin";
monsterHp = 100;
monsterAgressive = true;
}
}
public class Slime : Monster {
public Slime() {
monsterType = 2;
monsterName = "Slime";
monsterHp = 50;
monsterAgressive = false;
}
}
public class Skeleton : Monster {
public Skeleton() {
monsterType = 3;
monsterName = "Skeleton";
monsterHp = 200;
monsterAgressive = true;
}
}
using UnityEngine;
public class MonsterFactory
{
public Monster orderMonster(int type) {
validate(type);
GameObject monsterObject = new GameObject(); //빈 오브젝트
Monster monster = createMonster(type, monsterObject); // 몬스터 생성
monsterObject.name = monster.monsterName; // 게임 오브젝트 이름을 몬스터 이름으로 설정
return monster;
}
private Monster createMonster(int type, GameObject monsterObject) {
Monster monster = null;
if (type == 1) {
monster = monsterObject.AddComponent<Goblin>();
//monster = new Goblin();
} else if (type == 2) {
monster = monsterObject.AddComponent<Slime>();
//monster = new Slime();
} else if (type == 3) {
monster = monsterObject.AddComponent<Skeleton>();
//monster = new Skeleton();
}
Debug.Log($"{monster.monsterName} 몬스터가 스폰 되었습니다!");
return monster;
}
private void validate(int type) { // 유효성 검사
if (type == 0) {
throw new System.Exception("Monster Type Error!");
}
}
}
using UnityEngine;
public class MonsterSpawner : MonoBehaviour
{
void Start()
{
MonsterFactory factory = new MonsterFactory();
Monster goblin1 = factory.orderMonster(1);
Debug.Log(goblin1.PrintAboutMonster());
Monster slime1 = factory.orderMonster(2);
Debug.Log(slime1.PrintAboutMonster());
Monster skeleton1 = factory.orderMonster(3);
Debug.Log(skeleton1.PrintAboutMonster());
}
}

하지만 아무리 객체 생성을 공장 클래스에 위임했더라도, 공장 클래스 내에서 여전히 분기 로직이 잔존해 있다. 즉, 확장엔 열려있고 수정엔 닫힌 개방 폐쇄 원칙을 만족하지 못하게 된다. 따라서 새로운 종류의 몬스터를 만든다거나 몬스터를 만드는 공정을 변경할 수 있으면서도 기존에 작성했던 코드를 유지할 수 있는 구조로 변경해야 한다.
만일 생성할 몬스터 구현체의 종류가 적고, 앞으로 몬스터 종류가 추가되지 않는다면, 그냥 심플하게
Simple Factory Method로 구성하는 것도 나쁘지는 않다.
잔존해 있던 분기문을 제거하고 객체 지향으로 팩토리를 구성해보자. 먼저 monsterFactory 클래스를 추상 클래스로 변환하고 monster 인스턴스 생성을 책임지는 createMonster() 메서드를 추상 메서드로 추상화 시킨다. 그리고 각 몬스터 종류에 맞게 monsterFactory 클래스를 상속하는 서브 팩토리 클래스를 만들고 createMonster() 추상 메서드를 각 객체 특징에 맞게 재정의 하도록 한다.
즉, orderMonster() 메서드의 공통 코드는 그대로 두고 monster 인스턴스를 만드는 작업 코드는 서브 클래스가 결정하도록 한다. (템플릿 메서드와 결이 비슷하다고 볼 수 있다) 또한 공장 생성 메서드 내부에서는 필요한 동작을 자유롭게 구현할 수 있는데, 인자를 받거나 상태에 따라서 생성할 객체를 바꿀 수도 있다. 이렇게 하면 좀 더 다양한 기능을 수행하거나 수정에 용이한 구조를 만들어 낼 수 있다.
using UnityEngine;
public class Monster : MonoBehaviour {
public int monsterType;
public string monsterName;
public int monsterHp;
public bool monsterAgressive;
public override string ToString() {
return string.Format("Monster {{ type: '{0}', name: '{1}', HP: '{2}', agressive: '{3}' }}", monsterType, monsterName, monsterHp, monsterAgressive);
}
public void Initialize(int monsterType, string monsterName, int monsterHp, bool monsterAgressive) {
this.monsterType = monsterType;
this.monsterName = monsterName;
this.monsterHp = monsterHp;
this.monsterAgressive = monsterAgressive;
}
}
public class Goblin : Monster {
// Goblin 관련 로직 + 이벤트 관리
}
public class Slime : Monster {
// Slime 관련 로직 + 이벤트 관리
}
public class Skeleton : Monster {
// Skeleton 관련 로직 + 이벤트 관리
}
using UnityEngine;
public abstract class MonsterFactory
{
// 객체 생성 전처리 + 후처리 메서드 (상속 불가)
public Monster orderMonster() {
// 객체 생성 전처리
// 객체 생성
GameObject monsterObject = new GameObject(); //빈 오브젝트
Monster monster = createMonster(monsterObject); // 몬스터 생성
monsterObject.name = monster.monsterName; // 게임 오브젝트 이름을 몬스터 이름으로 설정
// 객체 생성 후처리
Debug.Log($"{monster.monsterName} 몬스터가 스폰 되었습니다!");
return monster;
}
// 팩토리 메서드
abstract protected Monster createMonster(GameObject monsterObject);
}
public class GoblinFactory : MonsterFactory {
protected override Monster createMonster(GameObject monsterObject) {
Goblin goblin = monsterObject.AddComponent<Goblin>();
goblin.Initialize(1, "Goblin", 100, true);
return goblin;
}
}
public class SlimeFactory : MonsterFactory {
protected override Monster createMonster(GameObject monsterObject) {
Slime slime = monsterObject.AddComponent<Slime>();
slime.Initialize(2, "Slime", 50, false);
return slime;
}
}
public class SkeletonFactory : MonsterFactory {
protected override Monster createMonster(GameObject monsterObject) {
Skeleton skeleton = monsterObject.AddComponent<Skeleton>();
skeleton.Initialize(3, "Skeleton", 200, true);
return skeleton;
}
}
using UnityEngine;
public class MonsterSpawner : MonoBehaviour
{
void Start()
{
Monster goblin1 = new GoblinFactory().orderMonster();
Debug.Log(goblin1);
Monster slime1 = new SlimeFactory().orderMonster();
Debug.Log(slime1);
Monster skeleton1 = new SkeletonFactory().orderMonster();
Debug.Log(skeleton1);
}
}

몬스터 타입 변수를 입력값으로 넘겨주고 분기문을 통해 몬스터를 생성하는 것이 아니라, 전용 몬스터 생성 공장 객체를 통해 몬스터를 생성함으로써, 수정에 닫혀있고 확장에 열려있는 구조를 구성할 수 있게 된다.
만약 Zombie라는 새로운 몬스터를 추가한다라고 가정하면, 간단하게 몬스터 객체와 몬스터 공장 객체만 정의하고 상속 시키면 기존의 코드 수정 없이 확장 가능하다.
public class Zombie : Monster {
// Zombie 관련 로직 + 이벤트 관리
}
public class ZombieFactory : MonsterFactory {
protected override Monster createMonster(GameObject monsterObject) {
Zombie zombie = monsterObject.AddComponent<Zombie>();
zombie.Initialize(4, "Zombie", 500, true);
return zombie;
}
}
using UnityEngine;
public class MonsterSpawner : MonoBehaviour
{
void Start()
{
Monster zombie1 = new ZombieFactory().orderMonster();
Debug.Log(zombie1);
}
}

객체를 생성하는 공장 객체가 여러 개 있을 필요가 없다. (다 낭비다!) 싱글톤 객체로 구성하여 유일한 인스턴스로 만들어 두는 것이 메모리 성능 면에서 유리하다.
Monster goblin1 = new GoblinFactory().orderMonster();
Debug.Log(goblin1);
Monster slime1 = new SlimeFactary().orderMonster();
Debug.Log(slime1);
Monster skeleton1 = new SkeletonFactory().orderMonster();
Debug.Log(skeleton1);
위처럼 new 키워드를 사용할 필요가 없다는 뜻이다. 위와 같이 메서드 호출용으로 일회용으로 쓰인 인스턴스는 가비지 컬렉션에 의해 자동으로 지워지긴 하지만, 저번에도 언급했듯이 가비지 값이 늘어나면 나중에 객체 제거 과정에서 Stop-the-world, 쉽게 말해 렉이 걸리게 된다. 따라서 각 팩토리 클래스들을 싱글톤화 시켜 메모리 적으로 최적화 시킬 수 있다.
using UnityEngine;
public abstract class MonsterFactory
{
// 객체 생성 전처리 + 후처리 메서드 (상속 불가)
public Monster orderMonster() {
// 객체 생성 전처리
// 객체 생성
GameObject monsterObject = new GameObject(); //빈 오브젝트
Monster monster = createMonster(monsterObject); // 몬스터 생성
monsterObject.name = monster.monsterName; // 게임 오브젝트 이름을 몬스터 이름으로 설정
// 객체 생성 후처리
Debug.Log($"{monster.monsterName} 몬스터가 스폰 되었습니다!");
return monster;
}
// 팩토리 메서드
abstract protected Monster createMonster(GameObject monsterObject);
}
public class GoblinFactory : MonsterFactory {
// Thread-safe 한 싱글톤 객체화
private GoblinFactory() {}
private static class MonsterInstanceHolder {
public static readonly GoblinFactory INSTANCE = new GoblinFactory();
}
public static GoblinFactory getInstance() {
return MonsterInstanceHolder.INSTANCE;
}
protected override Monster createMonster(GameObject monsterObject) {
Goblin goblin = monsterObject.AddComponent<Goblin>();
goblin.Initialize(1, "Goblin", 100, true);
return goblin;
}
}
public class SlimeFoctory : MonsterFactory {
// Thread-safe 한 싱글톤 객체화
private SlimeFoctory() {}
private static class MonsterInstanceHolder {
public static readonly SlimeFoctory INSTANCE = new SlimeFoctory();
}
public static SlimeFoctory getInstance() {
return MonsterInstanceHolder.INSTANCE;
}
protected override Monster createMonster(GameObject monsterObject) {
Slime slime = monsterObject.AddComponent<Slime>();
slime.Initialize(2, "Slime", 50, false);
return slime;
}
}
public class SkeletonFoctory : MonsterFactory {
// Thread-safe 한 싱글톤 객체화
private SkeletonFoctory() {}
private static class MonsterInstanceHolder {
public static readonly SkeletonFoctory INSTANCE = new SkeletonFoctory();
}
public static SkeletonFoctory getInstance() {
return MonsterInstanceHolder.INSTANCE;
}
protected override Monster createMonster(GameObject monsterObject) {
Skeleton skeleton = monsterObject.AddComponent<Skeleton>();
skeleton.Initialize(3, "Skeleton", 200, true);
return skeleton;
}
}
using UnityEngine;
public class MonsterSpawner : MonoBehaviour
{
void Start()
{
Monster goblin1 = GoblinFactory.getInstance().orderMonster();
Debug.Log(goblin1);
Monster slime1 = SlimeFoctory.getInstance().orderMonster();
Debug.Log(slime1);
Monster skeleton1 = SkeletonFoctory.getInstance().orderMonster();
Debug.Log(skeleton1);
}
}

동일하게 잘 작동한다.
Abstract Factory 패턴은 Factory Method 패턴보다 한 단계 높은 추상화 수준을 제공한다.
Factory Method
Abstract Factory
Factory Method는 Template Method의 특수화된 경우라고 볼 수 있다.
Factory Method
Abstract Factory
이렇게 다루고 나니깐 어떤가?
기존에는 Monster 클래스를 만들고 프리팹을 여러개 만들어서 몬스터 별로 프리팹을 만들어주고 별도의 값을 넣어주는 과정을 진행했었다.
몬스터의 종류가 늘어날 수록 프리팹의 개수도 늘어나는 것은 물론이고 몬스터 별 로직 구현을 위해서는 Monster 클래스 내부에 Monster를 상속 받은 클래스를 만들고 분기문을 통한 몬스터 타입 식별 및 로직 구현을 해줘야 했다.
물론 개발 시간 자체는 프리팹을 찍어내는게 시간 면에서는 유리할지 몰라도 유지보수 + 협업을 따졌을때는 오늘 배운 팩토리 메서드 패턴이 굉장히 유리하다는 것을 알 수 있다.
새로운 몬스터를 만든다거나, 몬스터 별 로직을 추가 구현 및 수정할 때 분기문을 수정하여 개방 폐쇄 원칙을 위반하지 않고도!
관리(유지보수) 및 확장성이 굉장히 향상된다는 것을 알 수 있다.