본문 바로가기

유니티/디자인 패턴

[Unity] 팩토리 패턴 (Factory Pattern)

반응형

팩토리 패턴이란?

직역하면 공장 패턴이라는 의미 그대로, 어떤 객체를 만드는 공장과 제품 그리고 소비자로 이루어진 디자인 패턴이다.

따라서 객체를 생성하고 다루는 부분이 분리될 수 있다.

이는 소프트웨어의 유연성과 확장성을 증가시킴을 의미한다.

 

유니티에서 어떤 오브젝트를 특정 지점에 생성하는 경우를 살펴보자.

원래는 Instantiate 메서드를 사용하여 변수에 담은 다음 해당 오브젝트의 위치나 상태를 변경해주어야 했을 것이다.

 

public GameObject CreateSword()
{
    var clone = Instantiate(product.gameObject);
    clone.transform.position = Vector3.zero;
    clone.transform.localScale = Vector3.one * 1.5f;
    clone.GetComponent<Material>().color = Color.white;
    return clone;
}

 

그런데 만약 생성해야 하는 프리팹의 종류가 다양하다고 가정해 보자.

예를 들어, Sword와 Bow, Hammer 등 여러 가지가 있고 각 오브젝트마다 생성할 때 필요한 로직이 다르다면 어떻게 해야 할까?

 

public void Create(WeaponType type)
{
    switch (type)
    {
        case WeaponType.Sword:
            // Sword 생성
            break;
        case WeaponType.Bow:
            // Bow 생성
            break;
        case WeaponType.Hammer:
            // Hammer 생성
            break;
    }
}

 

Switch문은 분명 좋은 선택처럼 보이지 않는다. 클래스 간의 결합이 강해서 스파게티 코드가 되기 쉽다.

 

이때 팩토리 패턴을 사용하여 객체 생성을 분리하는 것이다.

여기서 만들어진 객체는 Product에 해당하며 이를 만드는 클래스는 Factory가 된다.

크게 Product와 Factory로 이루어진 간단한 디자인 패턴이다. (하지만 사용했을 때의 장점이 많다.)

 

팩토리 패턴 구조

 

구체적인 구현방법은 글의 하단에 작성되어 있다.

지금은 구현 이전에 팩토리 패턴을 사용했을 때의 장점에 대해 더 살펴볼 필요가 있다.


왜 사용하는 것일까?

팩토리 패턴의 장점은 객체의 생성을 분리하는 장점이 무엇인지 알면 된다.

일단 첫 번째로 위의 예시처럼 복잡하게 서로를 참조하는 구조로부터 벗어날 수 있다.

이는 유지 보수에 필요한 유연성과 확장성을 증가시켜 주는 이점이 있다.

 

또한 객체의 생성에 대해서는 팩토리 클래스가 책임을 지고 있으므로 코드의 중복이나 오류를 최소화할 수 있다.

 

장점

단일 책임 원칙 : 객체의 생성과 관련된 기능은 팩토리 클래스가 책임져서 분리

개방/폐쇄 원칙 : IProduct와 Factory를 상속받는 구체 클래스를 추가만 함으로써 확장에는 열려있는 구조

 

단점

복잡성 : 클래스의 구조가 복잡해지는 가능성 존재


어떻게 사용할 수 있을까?

처음의 Sword와 Bow를 팩토리 패턴으로 제작하는 것을 예제로 나타내면 다음과 같다.

 

우선 IProduct에 해당하는 인터페이스이다.

 

public interface IWeapon
{
    void Equip();
}

 

IWeapon은 IProduct의 역할을 하며 Sword와 Bow를 상속시킨다.

아래는 Sword와 Bow 클래스이며 Product에 해당한다.

 

public class Sword : IWeapon
{
    public void Equip()
    {
        Debug.Log("Fitted with a sword");
    }
}

public class Bow : IWeapon
{
    public void Equip()
    {
        Debug.Log("Fitted with a bow");
    }
}

 

Product 클래스를 작성하였으니 제품을 생산할 공장인 Factory 클래스를 작성하면 된다.

그전에 구체 팩토리 클래스의 부모가 될 추상 팩토리 클래스를 먼저 작성한다.

 

public abstract class WeaponFactory : MonoBehaviour
{
    public static List<IWeapon> inventory = new();
    public abstract IWeapon CreateAddInventory();
}

 

WeaponFactory는 Factory에 해당하며 Sword Factory와 Bow Factory를 각각 만들 것이다.

 

public class SwordFactory : WeaponFactory
{
    public override IWeapon CreateAddInventory()
    {
        var sword = new Sword();
        inventory.Add(sword);
        return sword;
    }
}

public class BowFactory : WeaponFactory
{
    public override IWeapon CreateAddInventory()
    {
        var bow = new Bow();
        inventory.Add(bow);
        return bow;
    }
}

 

이제 위의 팩토리를 사용할 클라이언트인 Player 클래스를 마지막으로 작성하면 된다.

 

public class Player : MonoBehaviour
{
    public WeaponFactory weaponFactory;
    private IWeapon weapon;

    void Start()
    {
        weapon = weaponFactory.CreateAddInventory();
        weapon.Equip();
    }
}

 

이때 WeaponFactory는 유니티 에디터에서 직접 참조하도록 작성하였다.

(팩토리들을 Scriptable Obejct로 작성하여 넣어줘도 된다.)

 

에디터 설정

 

실행을 하면 다음과 같은 결괏값을 얻을 수 있다.

 

실행 결과

 

팩토리 패턴은 논리 구조를 쉽게 분리할 수 있는 유용한 패턴이기 때문에 적재적소에 잘 활용하면 클린코드를 유지하는데 많은 도움이 된다.

 

UML 다이어그램

반응형