Design Pattern

추상 팩토리 패턴 ( Design Pattern )

JinHyo040105 2022. 9. 19. 20:29

2022.09.19

 

프로그래밍 디자인 패턴을 공부했습니다.

먼저 기본 개념을 다이어그램으로 정리 해보았습니다.

 

  1. 추상적인 팩토리를 구성한다.
  2. 구성한 팩토리를 받아오는 팩토리 클래스
  3. 클라이언트에서 가상화된 요소들로 ProductA 와 ProductB를 가진다.
  4. 상황 or 조건에 맞는 구체적인 요소들을 구현한다.

먼저 만들어야 할 컴포넌트들을 추상적으로 정하고, 구체적인 상황이 주어지면 컴포넌트들을 상황에 맞게 구체적으로 구현하는 패턴이라고 볼 수 있습니다. 각 종류별로 Factory를 만들어 내는 것 보다, 관련있는 객체들끼리 묶어서 사용합니다.

 

팩토리 메서드 패턴과 비슷하지만, 객체 하나하나를 반환하는 메서드 패턴과 달리 구조의집합 그 자체를 반환합니다.

 

보안성 부분에서 클래스의 내부 부분은 다른 빼서 인터페이스나 Abstract로만 접근 가능하게 한다. 

 

국민게임 스타크래프트로 예시를 많이 참고하였습니다.

 

스타크래프트에는 3가지 종족이 있습니다. 각각 유닛도, 능력도, 이름도 다 다릅니다. 이와 같이 여러가지 건물과 유닛을 전부 따로 관리를 해야하는데 추상 팩토리 패턴을 이용하면 특정 그룹으로 묶어 관리 할 수 있습니다.

 

먼저 인구관리 건물을 추상클래스로 구현했습니다.

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

public abstract class RaceCapacity
{
    //인구 수 확장
    public abstract void expend();
}

public class Pylon: RaceCapacity
{
    public override void expend()
    {
        Debug.Log("프로토스 인구 확장 + 8");
    }
}

public class SupplyDepot: RaceCapacity
{
    public override void expend()
    {
        Debug.Log("테란 인구 확장 + 8");
    }
}

public class Overload: RaceCapacity
{
    public override void expend()
    {
        Debug.Log("저그 인구 확장 + 8");
    }
}

expend()를 구현해야하며, 종족별 인구를 늘리는 건물의 기능을 함유하고 있습니다.

종족별로 프로토스 : 파일런, 테란 : 서플라이, 저그 : 오버로드(유닛) 이렇게 인구를 관리하는 점을 공통으로 묶어 구현했습니다. 

 

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

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

public class GateWay : UnitBuilding
{
  public override void Produce()
  {
    Debug.Log("프로토스 유닛 생산");
  }
}
public class barrucks : UnitBuilding
{
  public override void Produce()
  {
    Debug.Log("테란 유닛 생산");
  }
}
public class Lava : UnitBuilding
{
  public override void Produce()
  {
    Debug.Log("저그 유닛 생산");
  }
}

다음은 유닛을 생성하는 건물의 클래스 입니다. 구조는 위와 같습니다.

 

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

public class Factory : MonoBehaviour
{
    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 Barrucks();
    }
}

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

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

public class ZergFactory : RaceFactory
{
    public override RaceCapacity makeCapacityBuilding()
    {
        return new Overload();
    }
    public override UnitBuilding makeUnitBuilding()
    {
        return new Lava();
    }
}
}

종족별 건물을 모았습니다. 우리는 종족(세부 요소)를 몰라도 makeCapacityBuilding과 makeUnitBuilding을 호출하면 각각 맞는 역할을 수행할 것 입니다.

 

아래는 사용해 볼수 있는 에제 코드입니다.

 

using UnityEngine;

public class RaceAbstractFactory : 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;
            case Race.Zerg:
                factory = new ZergFactory();
                break;
        }

        return factory;
    }
}

switch문으로 enum의 종족값을 받아와서 맞는 case에 넣고 실행하며 Protoss 인지 Terran인지 Zerg인지 확인하고 결과를 리턴 받으면 된다. 다음은 위의 코드를 사용하는 팩토리 코드입니다

sing UnityEngine;

public class FactoryUse : MonoBehaviour
{
    void Start()
    {
        RaceAbstractFactory raceFactoryUse = new RaceAbstractFactory();

        RaceFactory factory = raceFactoryUse.getFactory();

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

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

getFactory로 받아온 종족값의 의한 factory의 makeCapacityBuilding 과 makeUnitBuilding에 접근하여 각각의 기능인 expend()와 produce()를 수행할수 있는 코드를 작성했습니다.

 

여기서 가장 중요한 점을 말하며 마무리 하자면, 사용하는 입장(client)에서 종족(세부요소)는 무엇인지 알 필요가 없습니다. 오로지 추상 팩토리의 기능을 수행하며 그에 맞는 세부 요소는 상황과 조건에 의해 충분히 바꿔 사용할 수 있기 때문입니다.

 

이렇게 여러 종류의 객체를 생성할 때 객체들 사이 규칙이나 관련송(조건, 특성 등) 으로 추상 팩토리 패턴을 적용하여 관리하면 편합니다.