흔한 프로그래밍 언어서 새로운 인스턴스를 만들 때  new를 사용한다. 

만약 내 게임의 스테이지1에서 그린고블린을 만든다면 아래와 같이 만들것이다.
GreenGoblin greenGoblin = new GreenGoblin();

만약 유니티 모노를 상속받았다면 아래와 같이 할것이다.
GreenGoblin greenGoblin = Instantiate(prefabGreenGoblin);

그런데 갑자기 기획자가 레드고블린, 블랙고블린을 추가한다고 한다면?

혹은 그린고블린을 아예 없앤다면?

그리고 새로운 몬스터로 오크도 만든다고 한다면?

 

수정사항이나 추가사항이 있을 수도 있는데 메인클래스에서 new를 해주는 것은 게임의 규모가 커지면 복잡해지거나 일일이 고쳐줘야해서 번거로워질 수 있다.

 

그래서 인스턴스를 생성하는 기능을 따로 빼주는 것이고, 그것을 팩토리 패턴이라고 말한다.

 

 

simple factory

 

규모가 크지 않을 때는 단순히 if문으로 만들어주기도 한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SimpleFactory : MonoBehaviour
{
    public static SimpleFactory instance = null;

    public GreenGoblin prefab_greenGoblin;
    public RedGoblin prefab_redGoblin;

    void Awake()
    {
        //싱글톤을 간단한 모양으로 구현.
        instance = this;
    }

    public Goblin CreateGoblin(string type)
    {
        Goblin goblin = null;

        if(type.Equals("green"))
        {
            goblin = Instantiate(prefab_greenGoblin);
        }
        else if(type.Equals("red"))
        {
            goblin = Instantiate(prefab_redGoblin);
        }

        return goblin;
    }
}

 

SimpleFactory라는 클래스를 따로 만들었고, 고블린을 생성해주는 함수 CreateGoblin을 만들어줬다. 

이렇게 단순히 인스턴스 생성기능만 따로 빼준 것을 simple Factory라고 하며, 디자인패턴 급은 아니고 간단히 만들 때 종종 쓰는 주입 방식이다. 고블린을 생성할 때는 아래와 같이 사용해주면 될것이다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Scene1 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Goblin greenGoblin = SimpleFactory.instance.CreateGoblin("green");

        greenGoblin.SetPosition();
    }
}

 

하지만 위의 심플 팩토리로 게임을 만든다면 문제가 있다. 지금은 레드고블린과 그린고블린밖에 없지만, 오크, 트롤, 코볼트, 다크엘프 등등의 수많은 몬스터를 만들어야 한다고 해보자. 몬스터를 추가할 때마다 함수를 고치거나 몬스터별로 만들어야할 수도 있다. 이는 객체지향의 OCP(클래스 만들 때 확장은 가능하게 하되, 한번 만들면 추후에 수정할 필요 없게 만들라는 원칙)를 위반하는데다가 그냥 프로그래머팀 팀장님한테 설계 잘못했다고 욕먹을 수도 있다.

 

그래서 나온 디자인패턴이 팩토리 메소드와 추상팩토리 패턴이다.

 

 

팩토리 메소드 패턴

 

 

 

 

각 몬스터별로 생성 함수를 따로 정의해주는 것이 팩토리 메소드 패턴의 요점이다.

 

팩토리 메소드 패턴을 만드는 방식은 아래와 같다.

1. 최상위 Factory를 abstract class로 만들어준다. 그 안에 CreateMonster라는 함수를 선언해준다.

2. 고블린은 GoblinFactory, 오크는 OrcFactory 이런식으로 몬스터별로 팩토리를 따로 만들어준다.

3. 각 몬스터 팩토리는 CreateMonster라는 함수를 각자 알아서 정의해준다.

4. 고블린을 만들어야 한다면 GoblinFactory를 하나 생성해주고 거기서 고블린을 만들어준다.

 

자세한 코드는 아래와 같다.

using System.Collections; 
using System.Collections.Generic; 
using UnityEngine; 

//최상위 팩토리 클래스. 각 몬스터별로 이 클래스를 상속받아 각자의 팩토리를 만들어준다.
public abstract class AbsMonsterFactory 
{ 
    public abstract void CreateMonster(); 
}

 

아래는 AbsMonsterFactory를 상속받은  GoblinFactory클래스이다. 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GoblinFactory : AbsMonsterFactory
{
    public GreenGoblin greenGoblin;
    public RedGoblin redGobline;

    public override Monster CreateMonster(string type)
    {
        Monster monster = null;

        if(type.Equals("green"))
        {
            monster = Instantiate(greenGoblin);
        }
        else if(type.Equals("red"))
        {
            monster = Instantiate(redGobline);
        }

        return monster;
    }
}

 OrcFactory역시 이런 방식으로 만들어주면 된다. override 받은 CreateMonster함수를 각 몬스터별로 따로 정의해주는게 포인트이다.

 

참고로 여기서는 예제이기 때문에 프리팹을 클래스의 멤버 변수로 선언해놓고서 내용물을 정의해주는 부분을 생략했다. 하지만 실전에서는 에셋번들에서 추출해서 넣어주거나, 에셋번들 자체를 멤버변수로 해주는 방식으로 해야할 것이다. 또는 프리팹이 들어있는 에셋번들을 관리해주는 클래스를 따로 만들어 참조할 수도 있을 것이다.

 

마지막으로 아래와 같이 사용해주면 된다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Scene1 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        AbsMonsterFactory gf = new GoblinFactory();
        Monster monster = gf.CreateMonster("green");
    }
}

 

여기서 포인트는 Scene1클래스에서 고블린 팩토리를 하나 만들어줄 때, AbsMonsterFactory 타입으로 만들어주었다는 점이다. 신규 몬스터로 오크나 코볼트가 나오더라도, AbsMonsterFactory타입의 변수로 만들어준다면 코드가 일관성이 있게 되고 수정이 최소화 되게 된다.

 

 

추상 팩토리 패턴

일단 위의 게임과는 다른 게임을 만든다고 생각해보자.

 

이 게임은 보스몬스터들이 무기를 들고 있고, 몬스터의 종족과 무기종류에 따라 공략하는 맛이 있는 게임이라고 가정해보자. 보스의 패턴은 계속 랜덤으로 나온다고 가정해보자.

 

보스 종류 1 : 고블린 & 칼

보스 종류 2 : 오크 & 도끼

 

지금은 2종류의 보스 뿐이지만, 계속해서 보스와 무기가 추가될 예정이라면? 칼을든 고블린 뿐만 아니라 창을 든 고블린, 활을 든 고블린, 또는 오크가 칼을 들고 있다면? 이런식으로 부품을 끼워 맞추듯이 몬스터와 무기를 매칭시켜주는 방식이라면 추상팩토리 패턴을 사용하는게 좋다.

 

클래스 구조 설계는 아래 이미지들을 통해 설명하겠다.

 

팩토리를 통해 만들어지는 보스. 보스는 몬스터와 웨폰으로 이루어져있다.

 

 

 

 

고블린과 오크는 몬스터 클래스를 상속받고, 소드와 액스는 웨폰 클래스를 상속받는다.

 

 

 

 

외부에서 실질적으로 호출되는 함수는 CreateBoss함수 뿐이다. CreateMonster와 CreateWeapon 함수는 CreateBoss함수 내부에서 호출되는 함수이다.

 

실질적인 코드는 아래와 같다. 설명의 편의상 Instantiate를 사용하지 않고 new로 인스턴스를 만들어주도록 하겠다.

public abstract class AbsBossFactory
{
    //실제 보스를 만들어주는 부분. 
    public Boss CreateBoss()
    {
        Boss boss = new Boss
        {
            monster = CreateMonster(),
            weapon = CreateWeapon()
        };

        return boss;
    }

    public abstract Monster CreateMonster();
    public abstract Weapon CreateWeapon();
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//보스유형 1은 칼을 든 고블린.
public class BossFactory1 : AbsBossFactory
{
    public override Monster CreateMonster()
    {
        Monster monster = new Goblin();
        return monster;
    }

    public override Weapon CreateWeapon()
    {
        Weapon weapon = new Sword();
        return weapon;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//보스유형 2는 도끼를 든 오크.
public class BossFactory2 : AbsBossFactory
{
    public override Monster CreateMonster()
    {
        Monster monster = new Orc();
        return monster;
    }

    public override Weapon CreateWeapon()
    {
        Weapon weapon = new Axe();
        return weapon;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//실질적으로 팩토리를 써주는 부분
public class BattleGenerator : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        AbsBossFactory factory1 = new BossFactory1();
        Boss boss1 = factory1.CreateBoss();
    }
}

지금은 Start함수에서 1번 패턴의 보스를 대놓고 만들었지만, 이 팩토리들을 관리하는 클래스를 따로 만들거나 해서 Random.Range를 사용해서 무작위 보스가 계속 출현하게 만들면 될 것 같다.

 

 

 

추상팩토리 VS 팩토리 메소드 패턴 

그럼 추상팩토리와 팩토리 메소드 패턴은 뭐가 더 좋은걸까? 답은 없다. 상황에 따라 다르다. GoblinFactory처럼 하나의 카테고리 안의 완제품(레드고블린, 그린고블린)을 매개변수에 따라 만들어줄 때는 팩토리 메소드가 좋다. 하지만 2개 이상의 인스턴스를 조합하는 공정에서는(여기서는 몬스터와 무기를 골라서 보스를 만들때) 추상팩토리가 더 좋다. 경우에 따라서는 2개의 장점을 섞어서 만들거나, 둘 다 필요가 없는 아주 간단한 기능의 구현 때는 심플 팩토리로 시간을 절약하며 개발하는 경우도 있다. 또한 다른 디자인 패턴들과 섞어서 만들면 더욱 독특하고 효율적인 코드가 나올 수 있다. 지식을 많이 쌓아 지혜를 끌어올리도록 하자.

+ Recent posts