지난번에는 싱글톤에 대해 알아보았다면 이번에는 생성 디자인 패턴팩토리 메서드에 대해서 알아보도록 하자!

 

Q1. 팩토리 메서드(Factory Method)가 뭘까?

 

팩토리 메서드의 핵심: 객체 생성의 책임서브 클래스에게 맡기는 생성 패턴

팩토리 메서드 간단 설명

 

 

이번에도 게임으로 비유해보자!

게임에서 적 캐릭터 종류에는 여러가지가 있다고 치자 (좀비, 슬라임, 고블린, 오크, 트롤.... 등등)

이 객체들을 하나하나 다 지정해서 만들자고 생각하니 복잡할거 같다...

그러면 그냥 몬스터 공장에서 만들라고 요청만 하면 어떨까?

 

우리가 '좀비 3마리만 만들어 주세요!' 라는 주문을 공장 클래스요청하면

몬스터 공장 좀비 3마리를 만들어 준다. 이 방법이 바로 팩토리 메서드(Factory Method)이다!

 

팩토리 메서드(Factory Method)는 객체 생성의 책임을 서브 클래스(하위 클래스)에서 맡기는 생성 패턴.

즉, 객체를 Main에서 직접 생성하지 않고,

Main에서 서브클래스어떤 객체를 생성할지만 요청해주는 패턴이다.

 

(무언가를 만드는 공장이니까 당연히 디자인 패턴생성인 건 덤!)

 


 

Q2. 그럼 팩토리 메서드는 어떻게 사용하는 건가요?

일단 코드를 통해서 구현해보자!  C#: 콘솔앱(. NET Framework)

// 팩토리 메서드: 객체 생성을 서브 클래스에서 결정
// 직접 new를 사용하지 않고 팩토리 메서드를 통해 객체를 생성함
namespace CreatePattern_02_FactoryMethod
{
    abstract class Enemy
    {
        public string name;
        public abstract void Attack(int damage);
    }

    class Enemy_Goblin : Enemy
    {
        public Enemy_Goblin()
        {
            name = "고블린";
        }
        
        public override void Attack(int damage)
        {
            Console.WriteLine($"{name}이 {damage} 데미지로 공격!");
        }
    }

    class Enemy_Orc : Enemy
    {
        public Enemy_Orc()
        {
            name = "오크";
        }
        public override void Attack(int damage)
        {
            Console.WriteLine($"{name}가 검을 휘둘러 {damage} 데미지로 공격!!");
        }
    }

    // 이곳이 몬스터 공장
    class EnemyFactory
    {
        public static Enemy CreateEnemy(string type)
        {
            if (type == "Goblin")
                return new Enemy_Goblin();
            if (type == "Orc")
                return new Enemy_Orc();
            throw new ArgumentException("존재하지 않는 적 유형입니다...ㅠㅠ");
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            Enemy enemy1 = EnemyFactory.CreateEnemy("Goblin");
            Enemy enemy2 = EnemyFactory.CreateEnemy("Orc");

            enemy1.Attack(10);
            enemy2.Attack(25);
        }
    }
}

 

실행결과


Q3-1. 코드를 하나하나 해석해보자!

먼저 몬스터 개별 클래스이다. (고블린과 오크)

class Enemy_Goblin : Enemy
{
    public Enemy_Goblin()
    {
        name = "고블린";
    }

    public override void Attack(int damage)
    {
        Console.WriteLine($"{name}이 {damage} 데미지로 공격!");
    }
}

class Enemy_Orc : Enemy
{
    public Enemy_Orc()
    {
        name = "오크";
    }
    public override void Attack(int damage)
    {
        Console.WriteLine($"{name}가 검을 휘둘러 {damage} 데미지로 공격!!");
    }
}

 

부모 클래스(Enemy)에서 상속받은 몬스터 클래스(고블린, 오크)이며,

각각 공격 메서드 Attack() 를 가지고 있다(오버라이딩).

 

몬스터 공장(EnemyFactory)은 새로운 몬스터를 생성하는 것을 담당한다.

 // 이곳이 몬스터 공장
 class EnemyFactory
 {
     public static Enemy CreateEnemy(string type)
     {
         if (type == "Goblin")
             return new Enemy_Goblin();
         if (type == "Orc")
             return new Enemy_Orc();
         throw new ArgumentException("존재하지 않는 적 유형입니다...ㅠㅠ");
     }
 }

 

Enemy enemy1 = EnemyFactory.CreateEnemy("Goblin");
Enemy enemy2 = EnemyFactory.CreateEnemy("Orc");

 

Main 에서 몬스터 생성 요청만 하고 있는 것을 확인할 수 있다.


Q3-2. ??? 팩토리 메서드를 사용하지 않는 방법과 차이점이 뭔가요?

- 팩토리 메서드를 사용하지 않는 경우, Main에서 직접 객체를 생성해야 한다.

Enemy enemy1 = new Goblin();
Enemy enemy2 = new Orc();

만약 팩토리 메서드를 사용하지 않으면, 이렇게 일일히 Main에서 객체를 직접 생성해야 한다.

팩토리 메서드를 사용하면 위에 나온 요청 방식으로 활용할 수 있는 것이다!


Q4. 코드에서는 별차이가 없어보이는데요??

지금은 간단하게 몬스터만 구현한 방식이기 때문에 별차이가 없어보일 수 있다.

하지만 생각을 해보자... 게임에는 몇 종류의 몬스터 존재할까?

좀비, 오크, 흡혈귀, 스켈레톤, 고블린, 하이고블린, 엘리트 고블린... 이렇게 수많은 몬스터가 존재할 수 있다.

 

이들을 Main에서 하나하나 전부 관리를 할 수 있을까?

그럼 비교를 위해 몬스터 생성을 예시로 들어보자!

 

 4-1. (대량의 몬스터 종류가 있는 경우 객체 생성) 팩토리 메서드사용하지 않는 방법

internal class Program
{
    static void Main(string[] args)
    {
        // 서버에서 받은 몬스터 타입 리스트
        string[] monsterTypes = { "Goblin", "Orc", "Zombie", "Dragon", "Vampire", "Skeleton" };

        // 몬스터 객체를 직접 생성하기
        Enemy[] monsters = new Enemy[monsterTypes.Length];

        for (int i = 0; i < monsterTypes.Length; i++)
        {
            if (monsterTypes[i] == "Goblin")
                monsters[i] = new Goblin();
            else if (monsterTypes[i] == "Orc")
                monsters[i] = new Orc();
            else if (monsterTypes[i] == "Zombie")
                monsters[i] = new Zombie();
            else if (monsterTypes[i] == "Dragon")
                monsters[i] = new Dragon();
            else if (monsterTypes[i] == "Vampire")
                monsters[i] = new Vampire();
            else if (monsterTypes[i] == "Skeleton")
                monsters[i] = new Skeleton();
        }

        // 몬스터 공격 실행
        foreach (var monster in monsters)
        {
            monster.Attack(20);
        }
    }
}

 

이렇게 새로운 몬스터가 추가될 때마다 else if를 계속 써줘야 한다.

이는 SOLID 원칙 OCP 원칙에 위배되는 방식이다!

 

이 방식에서 새로운 몬스터 종류가 추가된다고 가정하자!

monsterTypes 리스트에서 확장을 하고, else if를 통해서 수정도 해야한다는 것!

또 몬스터마다 특별한 특성이나 공격 기술을 가지게 된다면 이 코드만으로 다룰 수 있을까? 

 


 

그렇다면 이번엔 팩토리 메서드 방식을 사용한 코드를 알아보자.

 4-2. (대량의 몬스터 종류가 있는 경우 객체 생성) 팩토리 메서드사용한 방법

internal class Program
{
    static void Main(string[] args)
    {
        // 서버에서 받은 몬스터 타입 리스트
        string[] monsterTypes = { "Goblin", "Orc", "Zombie", "Dragon", "Vampire", "Skeleton" };

        // 팩토리 메서드를 이용해 객체 생성
        List<Enemy> monsters = new List<Enemy>();

        foreach (var type in monsterTypes)
        {
            Enemy monster = EnemyFactory.CreateEnemy(type);
            if (monster != null)
                monsters.Add(monster);
        }

        // 몬스터 공격 실행
        foreach (var monster in monsters)
        {
            monster.Attack(20);
        }
    }
}

 

객체 생성을 Main이 아닌 EnemyFactory 한 곳에서만 생성을 담당하기에

새로운 몬스터 종류가 추가된다면 팩토리 클래스에처 추가만 하면 된다!


 

마지막으로 이를 통해서 알 수 있는 팩토리 메서드의 특징을 정리해보자!

 

1. 객체 생성 책임하위클래스에 맡긴다. 

    팩토리 메서드는 객체 생성 방식과 종류하위 클래스에서 결정하며, 상위 클래스는 요청 작업만 한다.

 

2. 객체 생성 캡슐화(Encapsulation) → 상위 클래스는 요청만 하기때문에 생성 방법을 알 수 없다.


 

게임 스테이지마다 등장하는 몬스터의 종류와 숫자가 다를 때, 다양한 몬스터들을 EnemyFactory에서 관리하면,

어떤 몬스터를 생성할지에 대한 결정을 한 곳에서 모아서 관리할 수 있다.

새로운 몬스터가 추가된다면, 팩토리 클래스에서 관리하면 되므로 객체 생성 방식이 일관성 있게 유지한다는 뜻!

 

다만.... 너무 많은 몬스터 종류가 추가되면, 팩토리 메서드 방식만으로는 코드량이 급증할 수 있다.

왜냐하면 수 많은 몬스터 클래스를 정의하고 관리해야 때문에, 클래스가 지나치게 많아지는 문제가 발생할 수 있는 것이다!

 

 몇년 전에 유행한 뱀서라이크로 예시를 들어보자!

몬스터가 한꺼번에 떼지어서 등장하고, 한 번 생성된 몬스터가 반복적으로 등장하는 특징이 있다.

 

이런 경우, 팩토리 메서드보다는 오브젝트 풀링 방식을 사용하는 것이 더 적합할 수 있다.

객체 생성과 관리를 어디서 어떻게 할지에 따라 디자인 패턴을 선택하는 것이 중요한 부분이다!

+ Recent posts