개념공부

Factory Pattern with Unity

Cadi 2025. 1. 1. 20:58
  • 객체의 '생산'을 위한 패턴
  • 객체의 생성에 관련된 설계를 단순화, 객체간의 의존성을 줄이는 목적이 있음
    구체적인 공장 클래스가 구체적인 클래스 대신 인터페이스나 추상 클래스를 참조하여 특정 구현에 대한 
    의존성을  제거한다. 
    새로운 객체 유형이 추가될 때에, 기존 코드를 수정하지 않고도 팩토리를 확장하여 객체를 생성할 수 있다.
  • 전통적인 방식, 추상화 메서드 방식 등 여러 가지가 있음(추상 팩토리, 팩토리 메서드 등)

 

 대략적인 구조를 보면 이렇다. 

IProduct라는 인터페이스는 Product들이 가져야 할 변수나 이름 등을 정의한다

이를 바탕으로 프로덕트들을 확장시켜 나갈 수 있다. 

 

팩토리는 추상 메서드로 구현되며 IProduct 타입을 반환한다.

이를 통해 구체적인 생성 관련 기능과 책임은 구체적인 공장 클래스에 위임한다.(서브클래스에 위임)

구체적인 팩토리들은 추상 메서드를 override해 각각 저장해 두었던 IProduct 타입의 프리팹들을 생성하는 기능을 한다.

 

사실 이 방식은 처음에 무언가를 생성할 때 떠올릴 수 있는 방법은 아니다.

무언가를 생성한다면 직접적으로 구현체를 참조해서 생성하는 방법을 떠올릴 수 있는다.

그러나 이는 의존성이 높고, 확장성도 위 방식에 비해 뒤떨어진다. 

그래서 직접적으로 참조하기보다, 추상 클래스가 인터페이스를 참조하여 의존성을 낮추고 확장성을 올린 것이다.

구체적인 팩토리가 IProduct나 각각의 Product들을 참조했다면 확장성 측면에서 아쉬운 결과를 가져온다.

 

 

 

 

코드 예시를 보자면 다음과 같다. 

public interface IProduct
{
 public string ProductName { get; set; }
 public void Initialize();
}
public abstract class Factory : MonoBehaviour
{
 public abstract IProduct GetProduct(Vector3 position);
 // 모든 공장이 공유하는 메서드
 …
}

 

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();
 }
}
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;
 }
}

 

ProductA는 IProduct라는 인터페이스를 상속받아 반드시 이름과 Initialize라는 고유 로직을 구현해야 한다. 

 

ConcreteFactoryA는 Factory라는 추상 클래스를 상속받아 반드시 GetProdcut를 구현할 필요는 없지만,

override를 통해 생성하는 로직을 구현한다. 이 때 생성하는 프리팹은 반드시 추상 클래스에서 참조한 IProduct의 성질을 갖고 있어야만 생성이 가능하다.(return newProduct를 할 수 있다.)

 

 

팩토리 패턴은 많은 종류의 객체들을 생성할 때에 유리하다.  새 제품 유형을 정의하더라도, 추상 메서드는 변하지 않으며 코드를 수정할 필요 없이 새 제품 유형을 생성하는 팩토리를 만들어 주면 된다. (같은 인터페이스를 공유하는 제품일 시)

다만 생성해야 하는 제품의 종류가 많지 않다면 약간의 오버헤드가 발생할 가능성이 있다. ( 다수의 클래스와 서브클래스)

 

 

 

의문점

 

의문 1. 구체적인 팩토리 클래스가 IProduct를 참조했을 때에 어떤 단점이 생기는가 ?

사실 구체적인 팩토리 클래스가 IProduct를 참조했을 때 생기는 단점들에 대한 명확한 이해가 되지 않아 조금더 찾아봤다.

 

  1.  팩토리 간의 공통된 인터페이스 제공(일관성 유지)
    추상 클래스를 도입해 모든 팩토리가 동일한 GetProduct 기능을 사용하게 강제할 수 있다. 
  2. 코드 재사용성 증가
    공통적으로 사용하는 로직을 추상 클래스에서 정의하면, 중복을 줄이고 유지 보수를 쉽게 할 수 있다.

  3. 클라이언트 코드의 독립성 보장
    클라이언트 코드가 구체적인 공장을 직접 참조하면, 팩토리의 변경 사항이 클라이언트에 영향을 미친다.
    추상 클래스를 통해 팩토리를 추상화하면, 클라이언트는 팩토리의 구체적인 구현을 몰라도 동작 가능하다. 
  4. 테스트의 용이성
    테스트 환경에서 Factory를 모조품 등으로 대체하여 제품 생성 로직을 독립적으로 검증 가능

* 클라이언트 코드 : 다른 클래스나 객체를 사용하는 코드
  클라리언트 : 어떤 서비스나 기능을 요청하여 사용하는 쪽을 말함.

 

의문 2. 결국 구체적인 공장 클래스는 생산물의 프리팹을 등록하지 않는가 ? 과연 의존성은 약화가 되는 것인가 ?

 

  1.  팩토리 패턴에서 의존성을 약화시키는 대상은 클라이언트 코드와 구체적인 제품간의 의존성이다.
    클라이언트 코드가 구체적인 제품 클래스를 직접 의존하지 않도록 만드는 것이다. 
    제품 생성 과정을 몰라도 동작하게 한다.

  2. 구체적인 결합은 한 곳에 집중시키고, 외부로 퍼지지 않도록 막는 것이 목표이다.
    클라이언트는 팩토리를 통해 생성된 제품을 사용할 뿐, 어떤 방식으로 생성되었는지 알지 못한다.
  3.  유니티에서도 팩토리 패턴의 확장성과 관리 용이성은 존재한다.
    프리팹 교체가 용이하고, 추상화된 인터페이스로 클라이언트 코드를 보호한다.

 

 

 

참고 자료

 

https://www.youtube.com/watch?v=AmwEIt0vhxA


밑의 영상이 진짜 설명을 잘해주신다.
https://www.youtube.com/watch?v=W-5N5BrR2dE&t=1393s

 

https://chatgpt.com/share/67752dc7-bd88-800d-8a09-86f8d0d384be

 

ChatGPT - 팩토리 패턴 개선 조언

Shared via ChatGPT

chatgpt.com

팩토리 패턴과의 싸움 기록

'개념공부' 카테고리의 다른 글

의존성 주입(Dependency Injection)  (0) 2025.01.08
델리게이트, 람다, Action,Func  (0) 2025.01.07
Singleton in Unity  (1) 2025.01.01
About UI ( with Unity)  (1) 2024.12.27
Unity에서 Sound의 기본 개념  (2) 2024.12.24