Chapter 6. 추상 팩토리 패턴(Abstract Factory Pattern)

Date:     Updated:

카테고리:

태그:

인프런에 있는 이재환님의 강의 게임 디자인 패턴 with Unity 를 듣고 정리한 필기입니다. 😀

Chapter 6. Abstract Factory Pattern

🔔 Abstract Factory Pattern

정의

클라이언트에 연관된 객체들의 패밀리를 반환한다.

여러 팩토리들마저도 추상화!

  • 팩토리 메서드 패턴과 비교
    • 유사하다.
    • But 차이점
      • 👉 팩토리 메서드 패턴은 팩토리가 되는 클래스가 하나의 객체를 생성하고 리턴하는게 전부.
      • 👉 추상 팩토리 패턴관련 있는 여러 종류의 객체를 특정 그룹으로 묶어 한번에 생성.
  • 예시
    • 특정 라이브러리를 배포하는데 OS별로 지원하는 기능이 상이하다면 추상 팩토리 패턴으로 OS별 기능들을 통합적으로 변경할 수 있다.

장점

  1. 관리 용이성
    • 클래스 이름 대신 팩토리 메소드를 사용해 객체를 생성하므로 추후 실제 생성되는 객체가 바뀌거나 추가되어도 문제가 없다.
  2. 보안성
    • 클래스의 대부분의 내용은 숨기고 싶을 때, 인터페이스나 abstract를 통해서만 객체에 접근하게 할 수 있다.
  3. 리소스 재활용성
    • 반드시 객체를 새로 생성할 필요는 없고 상황에 따라 새로 생성될 수도, 기존의 것을 리턴할 수도 있다.
  4. 상속 구조
    • 세밀한 팩토리 관리가 가능

1 ~ 3 장점은 팩토리 메서드 패턴과 같고 4 상속 구조추상 팩토리 패턴만의 장점

🔔 예제 1 : 추상 팩토리 패턴을 사용 ❌

  • 📜RaceCapacity.cs
    • 스타크래프트 인구
    • 📜RaceCapacity 추상 클래스
    • 📜SupplyDepot 상속
      • expand () 오버라이딩
        • 테란의 인구가 8 만큼 늘어남
    • 📜Pylont 상속
      • expand () 오버라이딩
        • 프로토스의 인구가 8 만큼 늘어남
  • 📜UnitBuilding.cs
    • 유닛을 생성하는 건물
    • 📜UnitBuilding 추상 클래스
    • 📜Barracks 상속
      • produce() 오버라이딩
        • 테란 유닛 생산
    • 📜Gateway 상속
      • produce() 오버라이딩
        • 프로토스 유닛 생산
  • 📜Factory.cs
    • 📜CapacityFactory
      • 인구 생성 팩토리 makeBuilding함수
        • 인수가 테란이면 테란 인구 생성하고 리턴
        • 인수가 프로토스면 프로토스 인구 생성하고 리턴
    • 📜UnitBuildingFactory
      • 건물 생성 팩토리 makeBuilding함수
        • 인수가 테란이면 테란 유닛 생성하고 리턴
        • 인수가 프로토스면 프로토스 유닛 생성하고 리턴
  • 📜FactoryUse.cs


📜RaceCapacity.cs

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

public abstract class RaceCapacity
{
	public abstract void expand ();
}

public class SupplyDepot : RaceCapacity
{
	public override void expand()
	{
		Debug.Log ("Terran Capacity +8 !!!");
	}
}

public class Pylon : RaceCapacity
{
	public override void expand()
	{
		Debug.Log ("Protoss Capacity +8 !!!");
	}
}


📜UnitBuilding.cs

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

public abstract class UnitBuilding
{
	public abstract void produce();
}

public class Barracks : UnitBuilding
{
	public override void produce ()
	{
		Debug.Log ("Terran Unit 생산 !!!");
	}
}

public class Gateway : UnitBuilding
{
	public override void produce()
	{
		Debug.Log ("Protoss Unit 생산 !!!");
	}
}


📜Factory.cs

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

public enum Race
{
	Terran,
	Protoss,
	Zerg
}

public class CapacityFactory
{
	public static RaceCapacity makeBuilding(Race type)
	{
        RaceCapacity capacity = null;

        switch (type)
        {
            case Race.Terran:
                capacity = new SupplyDepot();
                break;
            case Race.Protoss:
                capacity = new Pylon();
                break;
        }

        return capacity;
    }
}

public class UnitBuildingFactory
{
	public static UnitBuilding makeBuilding(Race type)
	{
        UnitBuilding building = null;

        switch (type)
        {
            case Race.Terran:
                building = new Barracks();
                break;
            case Race.Protoss:
                building = new Gateway();
                break;
        }

        return building;
	}
}


📜FactoryUse.cs

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

public class FactoryUse : MonoBehaviour {

    void Start () {

        RaceCapacity capacity = CapacityFactory.makeBuilding(Race.Protoss);
        UnitBuilding building = UnitBuildingFactory.makeBuilding(Race.Protoss);

        capacity.expand();
        building.produce();

    }
}

문제점

  • Terran대신 protoss로 바꿀 경우
    • CapacityFactory.makeBuilding(Race.Terran); 👉 CapacityFactory.makeBuilding(Race.Protoss);
      • 이렇게 인수만 바꿔주면 된다.
  • 문제점
    • 그러나 각 종족마다 건물을 훨씬 다양하게 가지고 있다면 각각의 건물마다 팩토리 클래스가 존재할 것이기에 건물 수만큼 인수를 바꿔야 한다.
    • 또한 새로운 Zerg를 지원해야 하는 경우, 즉 종족을 추가하는 경우 각각의 팩토리 클래스의 switch 문 마다 case Zerg: 문을 추가해줘야 한다.

해결책 : 추상 팩토리 패턴을 사용

엘리베이터는 A_Company라면 모든 부품이 A_Company일 것이다. 이렇게 여러 종류의 객체를 생성할 때 객체들 사이의 관련성이 있는 경우라면 각 종류별로 별도의 Factory 클래스를 사용하는 대신 관련 객체들을 일관성 있게 생성하는 추상 팩토리 패턴을 적용하는 것이 편리하다.


🔔 예제 2 : 추상 팩토리 패턴 사용 ⭕

추상 팩토리 패턴을 적용했다.

  • 📜RaceCapacity.cs, 📜UnitBuilding.cs 은 예제 1과 같음.
  • 📜Factory.cs
    • 📜RaceFactory 추상 클래스 ⭐⭐
    • 📜CapacityFactory 상속
      • 인구 생성 팩토리 makeCapacityBuilding함수 오버라이딩
        • 인수가 테란이면 테란 인구 생성하고 리턴
        • 인수가 프로토스면 프로토스 인구 생성하고 리턴
    • 📜UnitBuildingFactory 상속
      • 건물 생성 팩토리 makeUnitBuilding함수 오버라이딩
        • 인수가 테란이면 테란 유닛 생성하고 리턴
        • 인수가 프로토스면 프로토스 유닛 생성하고 리턴
  • 📜FactoryUse.cs


📜Factory.cs

  • public abstract class RaceFactory
    • 종족이 Terran이라면 모든 건물이 Terran용일 것이다.
    • 이렇게 여러 종류의 객체를 생성할 때 객체들 사이의 관련성이 있는 경우라면 각 종류별로 별도의 Factory 클래스를 사용하는 대신 관련 객체들을 일관성 있게 생성하는 추상 팩토리 패턴을 적용하는 것이 편리하다.
  • public class TerranFactory : RaceFactory
    • Terran 건물을 이용할 때는 TerranFactory객체를 생성할 수 있고
  • public class ProtossFactory : RaceFactory
    • Protoss 건물을 이용할 때는 ProtossFactory 객체를 생성할 수 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum Race
{
    Terran,
    Protoss,
    Zerg
}

public abstract class RaceFactory{
	public abstract RaceCapacity makeCapacityBuilding();
	public abstract UnitBuilding makeUnitBuilding();
}

public class TerranFactory : RaceFactory
{
	public override RaceCapacity makeCapacityBuilding()
	{
		return new SupplyDepot();
	}

	public override UnitBuilding makeUnitBuilding()
	{
		return new Barracks();
	}
}

public class ProtossFactory : RaceFactory
{
	public override RaceCapacity makeCapacityBuilding()
	{
		return new Pylon();
	}

	public override UnitBuilding makeUnitBuilding()
	{
		return new Gateway();
	}
}


📜FactoryUse.cs

하나의 팩토리 객체로 모든 건물을 다 만들 수 있다. 다형성

  • 다형성 하나의 팩토리 객체로 모든 건물을 다 만들 수 있다.
    • RaceFactory factory = new TerranFactory();
    • RaceFactory factory = new ProtossFactory();
  • 종족을 바꿀 땐 Race type = Race.Protoss; 부분만 수정하면 된다.
  • 종족이 추가 될 땐 📜Factory.cs에 클래스만 추가해주면 된다.
  • 종족이 추가/수정 되더라도 아래 코드는 바뀌지 않는다.
          RaceCapacity capacity = factory.makeCapacityBuilding();  // facory가 가리키는 객체가 TerranFactory면 테란의 인구수 (다형성, 추상 함수 오버라이딩)
          UnitBuilding building = factory.makeUnitBuilding(); // facory가 가리키는 객체가 TerranFactory면 테란의 인구수 (다형성, 추상 함수 오버라이딩)
    
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FactoryUse : MonoBehaviour {

	void Start () {

    RaceFactory factory = null;
		Race type = Race.Protoss;

		if (type == Race.Terran) {
			factory = new TerranFactory();
		} else if (type == Race.Protoss) {
			factory = new ProtossFactory();
		}

        RaceCapacity capacity = factory.makeCapacityBuilding();
        UnitBuilding building = factory.makeUnitBuilding();

        capacity.expand();
        building.produce();
	}
}


🔔 예제 3 : 추상 팩토리 패턴 & 팩토리 메서드 패턴 섞어서 사용

추상 팩토리 패턴팩토리 메서드 패턴를 함께 적용했다.

  • 팩토리 자체를 생성하고 리턴하는 함수를 둔 클래스를 따로 둔다.
  • 팩토리 메서드 패턴 적용 전 후 차이는 별로 없으나 종족별 Factory 객체를 생성하는 방식을 캡슐화 했다는 점이 다르다.

구조

  • 📜RaceCapacity.cs, 📜UnitBuilding.cs 예제 1와 같음.
  • 📜Facotry.cs 예제 2와 같음.
    • 추상 팩토리 패턴
  • 📜FactoryMethod.cs
    • 팩토리 메서드 패턴
      • 팩토리를 생성하는 팩토리 클래스(팩토리를 생성하고 리턴하는 함수)
      • 기존에 예제 2의 📜FactoryUse.cs 에서 테란이면 테란 공장 만들고 프로토스면 프로토스 공장 만드는 아래에서 주석처리한 코드 부분을 여기 📜FactoryMethod.cs 에서 구현해 준 것.
  • 📜FactoryMethodUse.cs
    • 종족에 수정이 가해져도 이 코드는 수정할 필요가 없다.
    • 그냥 어떻게 생성되는지 모른채, 뭘 생성해야하는지 모른채, 그저 FatcoryMethod를 사용하여 찍어내고 리턴받기만 하면 됨.


📜FactoryMethod.cs

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

public class FactoryMethod : MonoBehaviour {

    public Race type = Race.Terran;

    public RaceFactory getFactory()  // 팩토리 메서드 💚
	  {
		  RaceFactory factory = null;

		  switch (type)
		  {
		  case Race.Terran:
			  factory = new TerranFactory ();
			  break;
		  case Race.Protoss:
			  factory = new ProtossFactory();
			  break;
		  }
		  return factory;
	  }
}


📜FactoryMethodUse.cs

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

public class FactoryMethodUse : MonoBehaviour {

	void Start () {
/*  예제 2의 이전 코드
        RaceFactory factory = null;
        Race type = Race.Terran;

        if (type == Race.Terran)
        {
            factory = new TerranFactory();
        }
        else if (type == Race.Protoss)
        {
            factory = new ProtossFactory();
        }
*/

        // ⭐FactoryMethod 클래스는 정적 클래스로 만들어 바로 사용해도 된다.
        FactoryMethod factoryMethod = new FactoryMethod();

        RaceFactory factory = factoryMethod.getFactory();

        // ⭐하나의 팩토리 객체로 모든 건물을 다 만들 수 있다.
        RaceCapacity capacity = factory.makeCapacityBuilding();
        UnitBuilding building = factory.makeUnitBuilding();

        capacity.expand();
        building.produce();
    }
}


🔔 예제 4 : 출력 말고 유니티 오브젝트에 적용

추상 팩토리 패턴팩토리 메서드 패턴를 함께 섞어서 적용하였고 유니티 오브젝트에 적용하였다.

  • 실제 유니티 오브젝트에 적용하면
    • 프리팹들에 관련 스크립트를 붙이고
    • new가 아닌
      • Instanitiate으로 프리팹을 오브젝트화 하고
      • GetComponent으로 사용할 팩토리(📜TerranFactory, 📜ProtossFactory)를 가져온다.

구조

🟦 프리팹 ⬜오브젝트

  • 🟦Barracks
    • 📜Barracks.cs
      • 테란 유닛 생성 produce()
        • 유닛생성 👉🏻 📜UnitBuilding 추상 클래스를 상속받는다.
  • 🟦SupplyDepot
    • 📜SupplyDepot.cs
      • 테란 인구 8 증가 expand()
        • 인구증가 👉🏻 📜RaceCapacity 추상 클래스를 상속받는다.
  • 🟦Gateway
    • 📜Gateway.cs
      • 프로토스 유닛 생성 produce()
        • 유닛생성 👉🏻 📜UnitBuilding 추상 클래스를 상속받는다.
  • 🟦Pylon
    • 📜Pylon.cs
      • 프로토스 인구 8 증가 expand()
        • 인구증가 👉🏻 📜RaceCapacity 추상 클래스를 상속받는다.
  • ⬜GameManager
    • 📜TerranFactory.cs
      • 📜Factory.cs 의 RaceFactory 추상 클래스를 상속받는다.
      • 변수에 각각 🟦Barracks, 🟦SupplyDepot 프리팹 연결
    • 📜ProtossFactory.cs
      • 📜Factory.cs 의 RaceFactory 추상 클래스를 상속받는다.
      • 변수에 각각 🟦Gateway, 🟦Pylon 프리팹 연결
    • 📜FactoryMethod.cs
      • 어떤 종류의 팩토리를 생성할지는 얘에서 결정되며, 팩토리를 생성하고 리턴하는 팩토리 메서드를 가진다.
      • Terran을 선택하면 Terran의 suply, barracks 이 생성될 것이고
      • Protoss을 선택하면 Protoss의 pylon, gateway 이 생성될 것.
    • 📜FactoryMethodUse.cs
      • 어떤 종족인지 모른채로 그냥 팩토리 메서드를 통해 팩토리를 생성할 뿐이다.


📜UnitBuilding.cs

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

public abstract class UnitBuilding : MonoBehaviour
{
    public abstract void produce();
}


📜RaceCapacity.cs

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

public abstract class RaceCapacity : MonoBehaviour
{
    public abstract void expand();
}


📜Barracks.cs

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

public class Barracks : UnitBuilding {

	public override void produce()
	{
        Debug.Log("Terran Unit 생산 !!!");
    }
}


📜SupplyDepot.cs

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

public class SupplyDepot : UnitBuilding {

	public override void expand()
	{
        Debug.Log("Terran Capacity +8 !!!");
    }
}


📜Gateway.cs

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

public class Gateway : UnitBuilding {

	public override void produce()
	{
        Debug.Log("Protoss Unit 생산 !!!");
  }
}


📜Pylon.cs

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

public class Pylon : UnitBuilding {

	public override void expand()
	{
        Debug.Log("Protoss Capacity +8 !!!");
    }
}


📜Factory.cs

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

public enum Race
{
    Terran,
    Protoss,
    Zerg
}

public abstract class RaceFactory : MonoBehaviour
{
    public abstract GameObject makeCapacityBuilding();
    public abstract GameObject makeUnitBuilding();
}


📜TerranFactory.cs

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

public class TerranFactory : RaceFactory
{
    public GameObject supply;
    public GameObject barracks;

    public override GameObject makeCapacityBuilding()
    {
        //return new SupplyDepot();
        return Instantiate(supply, new Vector3(-1.0f, 1.0f, 0.0f), Quaternion.identity);
    }

    public override GameObject makeUnitBuilding()
    {
        //return new Barracks();
        return Instantiate(barracks, new Vector3(1.0f, 0.5f, 0.0f), Quaternion.identity);
    }
}


📜ProtossFactory.cs

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

public class ProtossFactory : RaceFactory
{
    public GameObject pylon;
    public GameObject gateway;

    public override GameObject makeCapacityBuilding()
    {
        //return new Pylon();
        return Instantiate(pylon, new Vector3(-1.0f, 1.0f, 0.0f), Quaternion.identity);
    }

    public override GameObject makeUnitBuilding()
    {
        //return new Gateway();
        return Instantiate(gateway, new Vector3(1.0f, 0.5f, 0.0f), Quaternion.identity);
    }
}


📜FactoryMethod.cs

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

public class FactoryMethod : MonoBehaviour {

	public Race type = Race.Terran;

	public RaceFactory getFactory()
	{
        RaceFactory factory = null;

		switch (type)
		{
		case Race.Terran:
			//factory = new TerranFactory ();
			factory = GetComponent<TerranFactory>();
			break;
		case Race.Protoss:
			//factory = new ProtossFactory();
			factory = GetComponent<ProtossFactory>();
			break;
		}

		return factory;
	}
}


📜FactoryMethodUse.cs

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

public class FactoryMethodUse : MonoBehaviour {

    RaceFactory factory = null;

	void Start () {
		factory = GetComponent<FactoryMethod>().getFactory();

		GameObject capacity = factory.makeCapacityBuilding();
		GameObject building = factory.makeUnitBuilding();

    capacity.GetComponent<RaceCapacity>().expand();
		building.GetComponent<UnitBuilding>().produce();
  }
}


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

댓글남기기