Chapter 6. 추상 팩토리 패턴(Abstract Factory Pattern)
카테고리: Design Pattern
인프런에 있는 이재환님의 강의 게임 디자인 패턴 with Unity 를 듣고 정리한 필기입니다. 😀
Chapter 6. Abstract Factory Pattern
🔔 Abstract Factory Pattern
정의
클라이언트에 연관된 객체들의 패밀리를 반환한다.
여러 팩토리들마저도 추상화!
팩토리 메서드 패턴
과 비교- 유사하다.
- But 차이점
- 👉
팩토리 메서드 패턴
은 팩토리가 되는 클래스가 하나의 객체를 생성하고 리턴하는게 전부. - 👉
추상 팩토리 패턴
은 관련 있는 여러 종류의 객체를 특정 그룹으로 묶어 한번에 생성.
- 👉
- 예시
- 특정 라이브러리를 배포하는데 OS별로 지원하는 기능이 상이하다면
추상 팩토리 패턴
으로 OS별 기능들을 통합적으로 변경할 수 있다.
- 특정 라이브러리를 배포하는데 OS별로 지원하는 기능이 상이하다면
장점
- 관리 용이성
- 클래스 이름 대신 팩토리 메소드를 사용해 객체를 생성하므로 추후 실제 생성되는 객체가 바뀌거나 추가되어도 문제가 없다.
- 보안성
- 클래스의 대부분의 내용은 숨기고 싶을 때, 인터페이스나 abstract를 통해서만 객체에 접근하게 할 수 있다.
- 리소스 재활용성
- 반드시 객체를 새로 생성할 필요는 없고 상황에 따라 새로 생성될 수도, 기존의 것을 리턴할 수도 있다.
- 상속 구조
- 세밀한 팩토리 관리가 가능
1 ~ 3 장점은 팩토리 메서드 패턴과 같고 4 상속 구조는
추상 팩토리 패턴
만의 장점
🔔 예제 1 : 추상 팩토리 패턴을 사용 ❌
- 📜RaceCapacity.cs
- 스타크래프트
인구
수 - 📜RaceCapacity 추상 클래스
- 📜SupplyDepot 상속
- expand () 오버라이딩
- 테란의 인구가 8 만큼 늘어남
- expand () 오버라이딩
- 📜Pylont 상속
- expand () 오버라이딩
- 프로토스의 인구가 8 만큼 늘어남
- expand () 오버라이딩
- 스타크래프트
- 📜UnitBuilding.cs
- 유닛을 생성하는
건물
- 📜UnitBuilding 추상 클래스
- 📜Barracks 상속
- produce() 오버라이딩
- 테란 유닛 생산
- produce() 오버라이딩
- 📜Gateway 상속
- produce() 오버라이딩
- 프로토스 유닛 생산
- produce() 오버라이딩
- 유닛을 생성하는
- 📜Factory.cs
- 📜CapacityFactory
- 인구 생성 팩토리
makeBuilding함수
- 인수가 테란이면 테란 인구 생성하고 리턴
- 인수가 프로토스면 프로토스 인구 생성하고 리턴
- 인구 생성 팩토리
- 📜UnitBuildingFactory
- 건물 생성 팩토리
makeBuilding함수
- 인수가 테란이면 테란 유닛 생성하고 리턴
- 인수가 프로토스면 프로토스 유닛 생성하고 리턴
- 건물 생성 팩토리
- 📜CapacityFactory
- 📜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
);- 이렇게 인수만 바꿔주면 된다.
- CapacityFactory.makeBuilding(
- 문제점
- 그러나 각 종족마다 건물을 훨씬 다양하게 가지고 있다면 각각의 건물마다 팩토리 클래스가 존재할 것이기에 건물 수만큼 인수를 바꿔야 한다.
- 또한 새로운 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
();
- RaceFactory factory = new
- 종족을 바꿀 땐 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 추상 클래스를 상속받는다.
- 테란 유닛 생성 produce()
- 📜Barracks.cs
- 🟦SupplyDepot
- 📜SupplyDepot.cs
- 테란 인구 8 증가 expand()
- 인구증가 👉🏻 📜RaceCapacity 추상 클래스를 상속받는다.
- 테란 인구 8 증가 expand()
- 📜SupplyDepot.cs
- 🟦Gateway
- 📜Gateway.cs
- 프로토스 유닛 생성 produce()
- 유닛생성 👉🏻 📜UnitBuilding 추상 클래스를 상속받는다.
- 프로토스 유닛 생성 produce()
- 📜Gateway.cs
- 🟦Pylon
- 📜Pylon.cs
- 프로토스 인구 8 증가 expand()
- 인구증가 👉🏻 📜RaceCapacity 추상 클래스를 상속받는다.
- 프로토스 인구 8 증가 expand()
- 📜Pylon.cs
- ⬜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
- 어떤 종족인지 모른채로 그냥 팩토리 메서드를 통해 팩토리를 생성할 뿐이다.
- 📜TerranFactory.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();
}
}
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글남기기