유니티 팩토리 패턴에 대해서 #UML 작성해보기

팩토리 패턴은 객체 생성을 담당하는 팩토리 클래스를 도입하여 객체 생성 로직을 캡슐화하는 디자인 패턴입니다. 팩토리  패턴을 사용하면 객체를 생성하는 코드를 분리하여 유연성을 높이고, 객체 생성 방식을 변경할 때 코드 수정을 최소화할 수 있습니다.

 

팩토리 패턴을 유니티에서 사용하는 경우, 다음과 같은 상황에서 유용합니다.

  • 동일한 객체를 반복적으로 생성해야 하는 경우
  • 객체 생성 로직을 단일 위치에 집중하여 코드를 관리하고자 하는 경우

UML을 바탕으로 코드 작성해보기

출처 - https://learn.unity.com/tutorial/65e0df08edbc2a2447bf0b98#

 

제가 직접 짠거는 아니고 유니티 공식 자료에서 가져와봤습니다.

저번시간에 UML 작성법을 공부했다고 저 화살표가 무엇을 의미하는지 알게 되어 뜻을 이해할 수 있게 되었습니다.

 

Factory라는 클래스를 ConcreteFactory에서 상속을 받아 사용하고, Factory클래스는 IProduct 인터페이스를 의존하고 있습니다. Product 클래스는 IProduct 인터페이스를 실체화 하고 있습니다.

 

UML을 바탕으로 코드를 작성해보겠습니다.


해당 코드는 모두 유니티 디자인 패턴 샘플에 나와있습니다.

using UnityEngine;

namespace DesignPatterns.Factory
{
	public abstract class Factory : MonoBehaviour
	{
    		public abstract IProduct GetProduct(Vector3 position); //IProduct를 의존하고 있다

    		public string GetLog(IProduct product)
    		{
        		string logMessage = "Factory: created product " + product.ProductName;
        		return logMessage;
    		}
	}
}

 

Factory 클래스는 IProduct를 의존하고 있습니다. 

GetProduct에서도 IProduct를 return해주고 있고, GetLog함수에서도 IProduct를 인자로 받고 있으므로 의존하고 있다고 볼 수 있습니다.

 

using UnityEngine;

namespace DesignPatterns.Factory
{
    public interface IProduct
    {
        public string ProductName { get; set; }

        public void Initialize();
    }
}

 

IProduct는 기본적으로 구현해야할 구조를 가지고 있습니다.

 

using UnityEngine;

namespace DesignPatterns.Factory
{
    public class ConcreteFactoryA : Factory
    {
        [SerializeField] private ProductA productPrefab;

        public override IProduct GetProduct(Vector3 position) //오버라이딩
        {
            GameObject instance = Instantiate(productPrefab.gameObject, position, Quaternion.identity);
            ProductA newProduct = instance.GetComponent<ProductA>();

            newProduct.Initialize();

            return newProduct;
        }
    }
}

 

ConcreteFactory는 Factory를 상속받아서 실제 기능을 실체화 하고 있습니다.

ConceteFactory성격에 따라 다양한 객체를 생성할 수 있습니다. 여기서는 하나의 팩토리는 하나의 객체에만 대응하고 있습니다.

 

using UnityEngine;

namespace DesignPatterns.Factory
{
    public class ProductA : MonoBehaviour, IProduct
    {
    	[SerializeField] private string productName = "ProductA";
    	public string ProductName { get => productName; set => productName = value ; }

    	private ParticleSystem particleSystem;

    	public void Initialize()
    	{
        	gameObject.name = productName;
        	particleSystem = GetComponentInChildren<ParticleSystem>();
        	particleSystem?.Stop();
        	particleSystem?.Play();
    	}
    }
}

 

Product 클래스는 IProduct 인터페이스를 실체화하고 있습니다.

Factory 클래스에서는 IProduct를 의존하고 있기 때문에 ProductA 클래스를 사용할 수 있습니다.

 

using UnityEngine;

namespace DesignPatterns.Factory
{
    public class ClickToCreate : MonoBehaviour
    {
        [SerializeField] private LayerMask layerToClick;
        [SerializeField] private Vector3 offset;
        [SerializeField] Factory[] factories;

        private Factory factory;

        private void Update()
        {
            GetProductAtClick();
        }

        private void GetProductAtClick()
        {
            if (Input.GetMouseButtonDown(0))
            {
                // 팩토리를 선택!
                factory = factories[Random.Range(0, factories.Length)];

                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hitInfo;

                if (Physics.Raycast(ray, out hitInfo, Mathf.Infinity, layerToClick) && factory != null)
                {
                    factory.GetProduct(hitInfo.point + offset);
                }
            }
        }
    }
}

 

위 코드는 실제로 팩토리를 사용하고 있습니다.

 

클라이언트 입장에서는 팩토리가 더 늘어나도 코드가 바뀌지 않고, 인스펙터에서 팩토리를 추가하는 작업만 거치면 됩니다. 

 

개발자 입장에서도 새로운 팩토리가 생겨나면 ConcreteFactoryC 와 ProductC 이렇게 새로운 제품을 만들기만 하면 되므로 유지보수하기에도 쉽게 되었습니다.

 

팩토리 패턴도 종류가 많기에 다양하게 응용할 수 있습니다. 이 예시는 극히 일부이므로 본인 프로젝트에 맞게 팩토리 패턴을 변형해서 사용하시면 되겠습니다.