본문 바로가기
Study/디자인 패턴

[C#/Unity][디자인패턴] 추상 팩토리 패턴(Abstract Factory Pattern)

by 스테디코디스트 2023. 11. 13.
반응형

1. 추상 팩토리 패턴이란?

- 관련성 있는 여러 종류의 객체를 일관된 방식으로 생성하는 경우 사용됨.

- 관련 객체들의 구상 클래스들을 지정하지 않고도 관력 객체들의 모음을 생성할 수 있도록 하는 생성패턴

- 연관성이 있는 객체 군이 여러개 있을 경우 이들을 묶어 추상화하고, 어떤 구체적인 상황이 주어지면 팩토리 객체에서 집합으로 묶은 객체 군을 구현화 하는 생성 패턴.

 

2.  팩토리 메서드 패턴과의 공통점과 차이점

- 먼저, 추상 팩토리 패턴이 팩토리 메서드 패턴의 상위 호환이 아니라는 것에 주의!

- 팩토리 메서드 패턴은 어떤 객체를 생성할 지에 집중하고, 추상 팩토리 패턴은 연관된 객체들을 모아둔다는 것에 집중함.

- 조건에 따라 객체 생성을 팩토리 클래스로 위임하여 팩토리 클래스에서 객체를 생성하는 팩토리 메서드 패턴과 달리, 추상 팩토리 패턴은 서로 관련이 있는 객체들을 통째로 묶어서 팩토리 클래스로 만들고, 이 팩토리를 조건에 따라 생성하도록 다시 팩토리를 만들어서 객체를 생성하는 패턴(팩토리 메서드를 좀 더 캡슐화한 방식)

  팩토리 메서드 패턴 추상 팩토리 패턴
공통점 1. 객체 생성 과정을 추상화한 인터페이스를 제공
2. 객체 생성을 캡슐화함으로써 구체적인 타입을 감추고 느슨한 결합 구조를 가짐
차이점 구체적인 객체 생성 과정을
하위 또는 구체적인 클래스로 옮기는 것이 목적
관련있는 여러 객체를 구체적인 클래스에
의존하지 않고 만들 수 있게 해주는 것이 목적
한 팩토리 당,
한 종류의 객체 생성 지원
한 팩토리에서, 서로 연관된
여러 종류의 객체 생성을 지원(제품군 생성 지원)
메소드 레벨에서 포커스를 맞춰,
클라이언트의 ConcreateProduct 인스턴스의 생성 및 구성에 대한 의존을 감소
클래스 레벨에서 포커스를 맟춰,
클라이언트 ConcreateProduct 인스턴스 군의 생성 및 구성에 대한 의존을 감소

 

3. 추상 팩토리 패턴의 장점

1) 일관성 : 제품 군 집합을 타입 별로 일관된 방식으로 찍어낼 수 있다.

2) 확장성 : 관리와 기능 확장이 용이함.

3) 은닉성 : 구체적인 내용을 클라이언트에게 감출 수 있음

4) 낮은 결합도 : 구체적인 클래스 대신 인터페이스를 사용하기 때문에, 추상화된 팩토리 인터페이스에 의존하게 되어 클라이언트의 코드와 구체적인 클래스 사이의 결합도를 낮출 수 있음.

 

4. 추상 팩토리 패턴의 단점

1) 복잡성 : 객체들의 생성을 추상화하기 때문에 추가적인 추상화 계층을 도입하게 되어 시스템 구조가 복잡해지고 코드의 양이 증가할 수 있음.

2) 유연성 제한  : 미리 정의된 팩토리들을 사용하여 객체들을 생성하기 때문에 동적인 객체 생성에는 제한이 있다. 즉, 실행중에 팩토리를 변경하여 다른 종류의 객체를 생성하는 것을 어렵다.

 

5. 코드 구현(Unity, C#)

<예시>

- 고블린 생성 시스템

- 참고 블로그 : https://welcomeheesuk.tistory.com/66

 

1. 가장 기반이 되는 추상클래스 EqipmentFactory

// 슈퍼클래스
public abstract class EquipmentFactory : MonoBehaviour
{
    // 몬스터 객체를 꾸며줄 추상 메소드들
    public abstract Weapon CraeteWeapon();
    public abstract Helmet CreateHelmet();
    public abstract BodyCloth CreateBodyCloth();
}

 

2. 추상클래스를 상속받는 서브 클래스

// 고블린 몬스터를 꾸며주는 고블린의 장비 팩토리
public class GoblinEquipmentFactory : EquipmentFactory
{
    // 장비 프리팹을 멤버변수로 가짐
    [SerializeField]
    public Weapon goblinWeapon = null;
    
    [SerializeField]
    public Helmet goblinHelmet = null;
    
    [SerializeField]
    public BodyCloth goblinBodyCloth = null;
    
    // 추상 메소드를 상속받아 재정의
    public override Weapon CreateWeapon
    {
    	Weapon weapon = Instantiate(this.goblinWeapon).GetComponent<Weapon>();
        return weapon;
    }
    
    public override Helmet CreateHelmet
    {
    	Helmet helmet = Instantiate(this.goblinHelmet).GetComponent<Helmet>();
        return helmet;
    }
    
    public override BodyCloth CreateBodyCloth
    {
    	BodyCloth bodyCloth = Instantiate(this.goblinBodyCloth).GetComponent<BodyCloth>();
        return bodyCloth;
    }
}

 

3. 장비 멤버변수를 몬스터 클래스에 추가해줌

public abstract class Monster : MonoBehaviour
{
    // 몬스터 클래스의 무기, 헬멧, 옷 멤버변수 추가
    protected Weapon weapon = null;
    protected Helmet helmet = null;
    protected BodyCloth bodyCloth = null;
    
    // 추상팩토리를 매개변수로 받는 장비를 세팅하는 메소드 추가
    public abstract void SetEquipment(EquipmentFactory _equipmentFactory);
}

 

4. 고블린을 꾸며주기 위한 SetEquipment 메소드 구현

public class Goblin : Monster
{
    // 장비 팩토리 클래스를 멤버변수로 가짐(재사용이 되지 않는다면 없어도 됨)
    private EquipmentFactory goblinEquipmentFactory = null;
    
    // 장비를 세팅해주는 추상 메소드를 재정의
    public override void SetEquipment(EquipmentFactory _equipmentFactory)
    {
    	this.goblinEquipmentFactory = _equipmentFactory;
        
        this.weapon = this.goblinEquipmentFactory.CreateWeapon();
        this.weapon = this.transform.SetParent(this.transform, false);
        
        this.helmet = this.goblinEquipmentFactory.CreateHelmet();
        this.helmet = this.transform.SetParent(this.transform, false);
        
        this.bodyCloth = this.goblinEquipmentFactory.CreateBodyCloth();
        this.bodyCloth = this.transform.SetParent(this.transform, false);
    }
}

 

5. 실행부

public class GoblinMonsterFactory : MonsterFactory<MONSTER_GOBLIN>
{
    // 고블린과 관련된 객체를 멤버변수로 가짐
    [SerializeField]
    private GameObject goblinPrefab = null;
    
    [SerializeField]
    private GameObject goblinBigPrefab = null;
    
    [SerializeField]
    private GameObject goblinKingPrefab = null;
    
    [SerializeField]
    private EquipmentFactory equipmentFactory = null;
    
    // MonsterFactory에서 상속받은 create 함수를 재정의
    protected override Monster Create(MONSTER_GOBLIN _type)
    {
    	Goblin goblin = null;
        
        switch(_type)
        {
            // 매개변수로 받은 Enum 변수에 따라 고블린 객체 생성
            case MONSTER_GOBLIN.NORMAL :
            	goblin = Instantiate(this.goblinPrefab).GetComponent<Goblin>():
                break;
            case MONSTER_GOBLIN.BIG :
            	goblin = Instantiate(this.goblinBigPrefab).GetComponent<Goblin>():
            	break;
            case MONSTER_GOBLIN.KING :
            	goblin = Instantiate(this.goblinKingPrefab).GetComponent<Goblin>():
            	break;
        }
        
        // 고블린이 생성된 이후 장비를 세팅하여 생성된 고블린 객체의 정의를 결정해줌
        goblin.SetEquipment(this.equipmentFactory);
        return goblin;
    }
}