[2025_12_18] Factory패턴의 실적용 여부

2025. 12. 18. 21:04TIL

12-18 Todo

1. 유닛 스킬 시스템(Base Skill)
2. 시간 남으면 오브젝트 풀링
3. 최대 유닛 수 제한(UI 포함)

팩토리 패턴 (Factory Pattern) - 확장성을 위한 객체 생성 패턴

배경: 스킬 시스템 설계 고민

스킬 시스템을 구현하면서 팩토리 패턴을 적용할지 고민했다.

 

처음 생각했던 것:

  • 다양한 스킬들 (공격, 버프, 힐링 등)을 생성해야 함
  • 스킬 타입에 따라 생성 로직이 다를 수 있음
  • 팩토리 패턴으로 스킬 생성을 관리하면 좋지 않을까?

하지만 현재 상황을 분석해보니...


팩토리 패턴이 필요 없는 이유

현재 구조

 
 
csharp
public class UnitBase : MonoBehaviour
{
    public List<SkillBase> skills;
    
    public void UseSkill(int index)
    {
        if (index < skills.Count)
        {
            skills[index].Execute(this);
        }
    }
}

public abstract class SkillBase
{
    public abstract void Execute(UnitBase caster);
}

public class AttackSkill : SkillBase
{
    public override void Execute(UnitBase caster)
    {
        // 공격 스킬 로직
    }
}

스킬 생성 방식

 
 
csharp
// UnitBase 초기화 시
public void InitSkills()
{
    skills = new List<SkillBase>
    {
        new AttackSkill(),
        new BuffSkill(),
        new HealSkill()
    };
}

데이터 기반 설계 

JSON 데이터로 스킬 관리

 
csharp
[System.Serializable]
public class SkillData
{
    public int id;
    public SkillType skillType;
    public string name;
    public int damage;
    public int healAmount;
    public float range;
    public float cooldown;
    public string effectPrefabPath;
}

public class DataDrivenSkillFactory
{
    private SkillDataManager dataManager;
    
    public DataDrivenSkillFactory(SkillDataManager manager)
    {
        dataManager = manager;
    }
    
    public SkillBase CreateSkill(int skillID)
    {
        SkillData data = dataManager.GetSkillData(skillID);
        
        SkillBase skill = data.skillType switch
        {
            SkillType.Attack => CreateAttackSkill(data),
            SkillType.Heal => CreateHealSkill(data),
            SkillType.Buff => CreateBuffSkill(data),
            _ => null
        };
        
        // 공통 속성 설정
        skill.skillName = data.name;
        skill.cooldown = data.cooldown;
        
        return skill;
    }
    
    private AttackSkill CreateAttackSkill(SkillData data)
    {
        AttackSkill skill = new AttackSkill();
        skill.damage = data.damage;
        skill.range = data.range;
        return skill;
    }
    
    private HealSkill CreateHealSkill(SkillData data)
    {
        HealSkill skill = new HealSkill();
        skill.healAmount = data.healAmount;
        skill.range = data.range;
        return skill;
    }
}

장점:

  • 코드 수정 없이 JSON만 수정하면 됨
  • 기획자가 밸런스 조정 가능
  • 새 스킬 추가가 매우 쉬움

왜 팩토리 패턴이 불필요한가?

  1. 스킬을 사용하는 주체가 현재는 UnitBase 하나뿐
  2. 각 유닛이 자신의 스킬을 직접 관리하면 충분
  3. 스킬 타입이 정해져 있고 동적 생성이 필요 없음
  4. 단순히 new로 생성해도 문제없음

결론: 지금은 YAGNI(You Aren't Gonna Need It) 원칙에 따라 단순하게 구현하는 게 맞다.


추후 리팩토링 관점

나중에 이런 상황이 오면?

시나리오 1: 유저(플레이어)도 스킬 사용

 
csharp
public class Player : MonoBehaviour
{
    public void UseUltimateSkill()
    {
        // 플레이어 궁극기 스킬
    }
}

 

시나리오 2: 건물(타워)도 스킬 사용

 
csharp
public class Tower : MonoBehaviour
{
    public void UseDefenseSkill()
    {
        // 타워 방어 스킬
    }
}

문제 발생!

  • UnitBase, Player, Tower 모두 스킬을 사용
  • 각각 스킬 생성 로직을 따로 구현? 
  • 스킬 데이터 관리는 어떻게?
  • 코드 중복 증가

팩토리 패턴이 필요한 시점

상황 1: 여러 타입의 객체가 같은 스킬을 사용할 때

 
csharp
public interface ISkillUser
{
    void UseSkill(SkillBase skill);
}

public class UnitBase : MonoBehaviour, ISkillUser { }
public class Player : MonoBehaviour, ISkillUser { }
public class Tower : MonoBehaviour, ISkillUser { }

 

상황 2: 스킬을 데이터 기반으로 생성할 때

 
json
{
    "skills": [
        {
            "id": 1,
            "type": "Attack",
            "damage": 100,
            "range": 5.0
        },
        {
            "id": 2,
            "type": "Heal",
            "healAmount": 50
        }
    ]
}

 

상황 3: 스킬을 동적으로 장착/해제할 때

 
csharp
// 스킬 장착 시스템
public void EquipSkill(int skillID)
{
    SkillBase skill = skillFactory.CreateSkill(skillID);
    equippedSkills.Add(skill);
}

리팩토링 시점: 유닛 + 유저 + 건물

미래에 이렇게 확장될 때:

공통 인터페이스

 
csharp
public interface ISkillUser
{
    Transform Transform { get; }
    void UseSkill(SkillBase skill);
}

유닛 구현

 
csharp
public class UnitBase : MonoBehaviour, ISkillUser
{
    public Transform Transform => transform;
    private List<SkillBase> skills;
    
    public void Init(SkillFactory factory, int[] skillIDs)
    {
        foreach (int id in skillIDs)
        {
            skills.Add(factory.CreateSkill(id));
        }
    }
    
    public void UseSkill(SkillBase skill)
    {
        skill.Execute(this);
    }
}

플레이어 구현

 
csharp
public class Player : MonoBehaviour, ISkillUser
{
    public Transform Transform => transform;
    private SkillBase ultimateSkill;
    
    public void Init(SkillFactory factory, int ultimateID)
    {
        ultimateSkill = factory.CreateSkill(ultimateID);
    }
    
    public void UseSkill(SkillBase skill)
    {
        skill.Execute(this);
    }
    
    public void UseUltimate()
    {
        UseSkill(ultimateSkill);
    }
}

타워 구현

 
csharp
public class Tower : MonoBehaviour, ISkillUser
{
    public Transform Transform => transform;
    private SkillBase defenseSkill;
    
    public void Init(SkillFactory factory, int defenseSkillID)
    {
        defenseSkill = factory.CreateSkill(defenseSkillID);
    }
    
    public void UseSkill(SkillBase skill)
    {
        skill.Execute(this);
    }
    
    public void ActivateDefense()
    {
        UseSkill(defenseSkill);
    }
}

이렇게 하면:

  • 모든 엔티티가 SkillFactory를 통해 스킬 생성
  • 스킬 생성 로직이 한 곳에 집중
  • 각 엔티티는 자신의 역할에만 집중

팩토리 패턴 적용 시점 판단 기준

적용하면 좋을 때

  1. 2개 이상의 클래스가 같은 타입의 객체를 생성할 때
  2. 객체 생성 로직이 복잡할 때
  3. 데이터 기반으로 객체를 생성해야 할 때
  4. 생성할 객체 타입이 런타임에 결정될 때

적용하지 않아도 될 때

  1. 한 곳에서만 객체를 생성할 때 (현재 상황)
  2. 생성 로직이 매우 단순할 때
  3. 확장 계획이 없을 때
  4. 과도한 추상화가 오히려 복잡도를 높일 때

결론: 현재와 미래의 균형

  • 유저, 건물 등 다양한 엔티티가 스킬 사용
  • 스킬을 데이터 기반으로 관리
  • 동적 스킬 장착 시스템 추가

이럴 때 팩토리 패턴으로 리팩토링하면 좋다!


핵심 교훈

"지금 당장 필요 없으면 추가하지 말되, 나중에 필요할 때를 대비해 알아는 두자"

실무 개발 원칙

  1. YAGNI (You Aren't Gonna Need It) - 필요하지 않으면 만들지 마라
  2. KISS (Keep It Simple, Stupid) - 단순하게 유지하라
  3. 하지만 확장성은 염두에 두라 - 리팩토링 시점을 판단할 줄 알아야 함