Factory 패턴

JJW·2024년 12월 3일
0

Unity

목록 보기
9/34

오늘은 Factory 패턴에 대해 알아보는 시간을 가지겠습니다.


Factory 패턴이란?

  • 객체 생성에 관한 디자인 패턴으로, 객체 생성 로직을 별도의 Factory(공장) 클래스로 분리하여 코드의 재사용성과 유지보수성을 높여줍니다.
  • 객체를 직접 생성하는 대신 Factory를 통해 생성하면, 새로운 객체 타입이 추가되거나 기존 로직이 수정되더라도 다른 코드에 영향을 주지 않도록 설계할 수 있습니다.

Factory 특징

  • 객체 생성 로직의 캡슐화
    • 객체 생성에 대한 로직을 클라이언트 코드에서 분리하여 Factory 클래스가 대신 담당합니다.
  • 객체 타입에 대한 유연성
    • Factory를 통해 생성되는 객체의 타입을 동적으로 결정할 수 있습니다.
  • 의존성 감소
    • 클라이언트가 객체의 구체적인 클래스에 의존하지 않고, 인터페이스나 추상 클래스에 의존하도록 설계됩니다.
  • 코드 재사용성 증가
    • 객체 생성 코드를 Factory 클래스에 집중시켜 중복 코드를 제거합니다.
  • 객체 생성의 제어
    • 객체 생성 과정을 제어하며, 복잡한 초기화 과정을 캡슐화할 수 있습니다.
  • 확장성
    • 새로운 객체 타입을 추가할 때 기존 코드 수정 없이 Factory 클래스만 수정하거나 확장이 가능합니다.

Factory 패턴의 종류

  • Simple Factory (간단한 팩토리)
    • 하나의 Factory 클래스가 모든 객체 생성을 담당
  • Factory Method (팩토리 메서드)
    • 객체 생성을 서브클래스에 위임
  • Abstract Factory (추상 팩토리)
    • 관련된 객체를 생성하는 인터페이스 제공
  • 먼저 Simple Factory에 대해 알아보겠습니다.

Simple Factory

1. Product

public interface IEnemy
{
    void Attack();
}
  • Factory가 생성할 객체의 부모 클래스 또는 인터페이스입니다.

2. ConcreteProduct

public class Slime : IEnemy
{
    public void Attack()
    {
        Debug.Log("Slime attacks by jumping!");
    }
}

public class Goblin : IEnemy
{
    public void Attack()
    {
        Debug.Log("Goblin attacks with a club!");
    }
}

public class Dragon : IEnemy
{
    public void Attack()
    {
        Debug.Log("Dragon attacks with fire breath!");
    }
}
  • 실제로 생성될 구체적인 객체입니다. 각 Attack 별로 다른 동작을 하게끔 구현합니다.

3. Factory

public class EnemyFactory
{
    public static IEnemy CreateEnemy(string enemyType)
    {
        switch (enemyType)
        {
            case "Slime":
                return new Slime();
            case "Goblin":
                return new Goblin();
            case "Dragon":
                return new Dragon();
            default:
                throw new System.ArgumentException("Invalid enemy type");
        }
    }
}
  • 객체를 생성하고 반환하는 클래스입니다.

4. GameManager

public class GameManager : MonoBehaviour
{
    void Start()
    {
        // 적 생성
        IEnemy slime = EnemyFactory.CreateEnemy("Slime");
        slime.Attack(); 

        IEnemy goblin = EnemyFactory.CreateEnemy("Goblin");
        goblin.Attack(); 

        IEnemy dragon = EnemyFactory.CreateEnemy("Dragon");
        dragon.Attack(); 
    }
}
  • 테스트를 위한 클래스입니다. Factory에서 Enemy를 생성하고 난 뒤 Attack을 실행시켜줍니다.

Simple Factory의 장단점

  • 장점

    • 객체 생성 로직을 캡슐화하여 클라이언트 코드가 간단해집니다.
    • 새로운 타입 추가 시 Factory만 수정하면 되므로 유지보수성이 증가
    • 소규모 프로젝트나 간단한 객체 생성 로직을 처리할 때 유용합니다.
  • 단점

    • 모든 객체 생성 로직이 한 클래스에 집중되므로, 규모가 커지면 복잡도가 증가할 수 있음
    • 새로운 타입 추가 시 Factory 클래스 수정이 필요하므로 Open-Closed Principle(OCP)을 완벽히 준수하지 못함.
    • Open-Closed Principle (OCP) : 개방 폐쇄 원칙

Factory Method

1. Product

public interface IEnemy
{
    void Attack();
}
  • 생성할 객체의 인터페이스 또는 추상 클래스이며 공통 인터페이스를 정의합니다.

2. ConcreteProduct

public class Slime : IEnemy
{
    public void Attack()
    {
        Debug.Log("Slime attacks by jumping!");
    }
}

public class Goblin : IEnemy
{
    public void Attack()
    {
        Debug.Log("Goblin attacks with a club!");
    }
}

public class Dragon : IEnemy
{
    public void Attack()
    {
        Debug.Log("Dragon attacks with fire breath!");
    }
}
  • Product를 구현한 실제 객체입니다.

3. Creator

public abstract class EnemyFactory
{
    // Factory Method
    public abstract IEnemy CreateEnemy();

    // 공통 로직
    public void SpawnEnemy()
    {
        IEnemy enemy = CreateEnemy();
        enemy.Attack();
    }
}
  • Factory Method를 정의하는 추상 클래스 또는 인터페이스 입니다.

4. ConcreteCreator

public class SlimeFactory : EnemyFactory
{
    public override IEnemy CreateEnemy()
    {
        return new Slime();
    }
}

public class GoblinFactory : EnemyFactory
{
    public override IEnemy CreateEnemy()
    {
        return new Goblin();
    }
}

public class DragonFactory : EnemyFactory
{
    public override IEnemy CreateEnemy()
    {
        return new Dragon();
    }
}
  • Creator를 구현하거나 상속받아 구체적인 객체 생성 로직을 제공하는 클래스 입니다.

5. GameManager

public class GameManager : MonoBehaviour
{
    void Start()
    {
        EnemyFactory slimeFactory = new SlimeFactory();
        slimeFactory.SpawnEnemy(); 

        EnemyFactory goblinFactory = new GoblinFactory();
        goblinFactory.SpawnEnemy(); 

        EnemyFactory dragonFactory = new DragonFactory();
        dragonFactory.SpawnEnemy(); 
    }
}
  • 어떤 적이 생성되는지 알 필요 없이 Factory Method를 호출하여 적을 생성하고 사용합니다.

Factory Method의 장단점

  • 장점

    • 객체 생성과 사용 로직을 분리하여 코드가 간결해집니다.
    • 새로운 객체 타입을 추가할 때, 기존 코드를 수정하지 않아도 됩니다.
    • 다양한 서브클래스를 통해 객체 생성을 제어가 가능합니다.
    • 클라이언트는 구체적인 객체를 몰라도 동작이 가능합니다.
  • 단점

    • 클래스 수가 늘어나면서 코드 관리가 어려워질 수 있습니다.
    • 설계 단계에서 객체 타입과 Factory를 명확히 정의해야 합니다.

Abstract Factory

1. Abstract Factory

public interface IEnemy
{
    void Attack();
}

public interface IWeapon
{
    void Use();
}
  • 관련된 객체 군의 생성을 위한 인터페이스를 정의합니다.

2. Concrete Factory

public class Slime : IEnemy
{
    public void Attack()
    {
        Debug.Log("Slime attacks by jumping!");
    }
}

public class SlimeWeapon : IWeapon
{
    public void Use()
    {
        Debug.Log("Slime throws a sticky ball!");
    }
}

public class Goblin : IEnemy
{
    public void Attack()
    {
        Debug.Log("Goblin attacks with a club!");
    }
}

public class GoblinWeapon : IWeapon
{
    public void Use()
    {
        Debug.Log("Goblin swings its club!");
    }
}
  • Abstract Factory를 구현하여 구체적인 객체를 생성합니다.
  • 2개의 객체만 구현하였습니다.

3. Abstract Product

public interface IEnemyFactory
{
    IEnemy CreateEnemy();
    IWeapon CreateWeapon();
}
  • 객체 군에 포함되는 각 객체의 인터페이스를 정의합니다.

4. Concrete Product

public class SlimeFactory : IEnemyFactory
{
    public IEnemy CreateEnemy()
    {
        return new Slime();
    }

    public IWeapon CreateWeapon()
    {
        return new SlimeWeapon();
    }
}

public class GoblinFactory : IEnemyFactory
{
    public IEnemy CreateEnemy()
    {
        return new Goblin();
    }

    public IWeapon CreateWeapon()
    {
        return new GoblinWeapon();
    }
}
  • Abstract Product를 구현한 실제 객체입니다.

5. GameManager

public class GameManager : MonoBehaviour
{
    void Start()
    {
        // Slime Factory 사용
        IEnemyFactory slimeFactory = new SlimeFactory();
        IEnemy slime = slimeFactory.CreateEnemy();
        IWeapon slimeWeapon = slimeFactory.CreateWeapon();

        slime.Attack();         
        slimeWeapon.Use();       

        // Goblin Factory 사용
        IEnemyFactory goblinFactory = new GoblinFactory();
        IEnemy goblin = goblinFactory.CreateEnemy();
        IWeapon goblinWeapon = goblinFactory.CreateWeapon();

        goblin.Attack();         
        goblinWeapon.Use();      
    }
}
  • 추상 팩토리와 추상 제품의 인터페이스만 사용하여 객체를 생성 및 사용합니다.

Abstract Factory의 장단점

  • 장점

    • 적과 무기처럼 관련된 객체들이 함께 생성되므로 객체 간의 일관성을 유지가 가능합니다.
    • 새로운 객체 군이 추가될 때 기존 코드를 수정하지 않고 팩토리만 추가하면 됩니다.
    • 객체 생성 로직이 팩토리 내부에 캡슐화되어 클라이언트 코드는 단순화됩니다.
  • 단점

    • 클래스와 인터페이스가 많아져 설계가 복잡해질 수 있습니다.
    • 객체 군과 팩토리의 관계를 명확히 정의해야 합니다.

SimpleFactory vs FactoryMethod vs Abstract Factory

특징Simple FactoryFactory MethodAbstract Factory
객체 생성 방식팩토리 클래스에서 모든 객체를 생성객체 생성 책임을 서브클래스로 위임객체 군을 생성
확장성낮음 (기존 클래스 수정 필요)중간 (새로운 서브클래스 추가로 확장 가능)높음 (새 객체 군 추가가 용이)
유연성낮음중간높음
객체 간 관계없음없음객체 간의 관계를 고려한 생성
적합한 프로젝트 규모소규모중규모대규모
사용 예시단순 객체 생성각 객체 타입별 팩토리관련된 객체 군 생성

어떤 패턴을 선택해야 할까?

  • 단순한 객체 생성이 필요하고, 객체 타입이 자주 변경이 안되며 소규모 프로젝트의 경우
    Simple Factory
  • 객체 타입이 자주 추가되거나 변경될 가능성이 높고, 확장성과 유지보수성이 중요하며 중규모 이상의 프로젝트에서 객체를 유연하게 관리를 하려면
    Factory Method
  • 객체들이 군으로 묶여서 관리되고 여러 객체 간의 관계를 유지해야 하며 대규모 프로젝트나 테마별,그룹별로 객체를 관리해야 한다면
  • Abstract Factory

테스트

  • 각 팩토리 별 테스트 결과는 구현 방식에 차이가 있을 뿐 결과는 동일하게 나옵니다.
  • 추상 팩토리만 결과가 다르지만 그건 무기 코드가 붙어있으므로.. 다를 수 밖에 없네용

느낀 점

추상 팩토리가 쓰임새가 어디쓰일 지 아직 잘 생각이 안납니다.
제가 쓰게 된다면 Simple Factory 나 Factory Method를 주로 사용할 것 같은데
아직 좋은 점은 잘 못느끼겠습니다.. 더 알게 되고 지식이 넓어진다면 나중에 구조를 설계할 때 생각의 폭이 달라지겠지 싶습니다.

  • 제가 조사한 내용이 맞지 않거나 잘못 된 경우에 댓글로 잘못된 점 지적해주시면 감사합니다 ! (´._.`)
profile
Unity 게임 개발자를 준비하는 취업준비생입니다..

0개의 댓글