---
title: "new 를 부르는 자리를 한 곳으로 모은다"
description: "인터페이스로 갈아 끼울 수 있게 만들어 놔도 new 가 코드 곳곳에 흩어져 있으면 구체 타입 이름은 그대로 남습니다."
author: "유니얼"
pubDate: 2026-09-09
category: "game-dev/design-patterns"
tags: ["Unity"]
canonical: https://blog.unialgames.com/posts/unity-factory/
---
앞의 세 글에서 행동을 인터페이스 뒤로 숨겼습니다.
`AbilityRunner` 는 `Ability` 만 알고 `AirStrike` 를 모릅니다.

그런데 이런 코드가 남아 있다면 어떨까요.

```csharp
void OnItemPickup(string itemId)
{
    if (itemId == "airstrike") runner.Equip(new AirStrike());
    else if (itemId == "radar") runner.Equip(new RadarPulse());
    else if (itemId == "firstaid") runner.Equip(new FirstAid());
}
```

`switch` 를 없애려고 클래스를 나눴는데 **`switch` 가 여기로 옮겨 왔을 뿐**입니다.
그리고 이 코드는 구체 타입 이름을 셋 다 알고 있습니다.
아이템을 하나 추가하면 여기를 또 고쳐야 합니다.

`new` 는 생각보다 무거운 한 줄입니다. **그 줄을 쓰는 순간 그 클래스와 묶입니다.**

## 만드는 일을 따로 떼어 놓는다

가장 단순한 형태는 정적 메서드 하나입니다.

```csharp
public static class AbilityFactory
{
    public static Ability Create(string id) => id switch
    {
        "airstrike" => new AirStrike(),
        "radar"     => new RadarPulse(),
        "firstaid"  => new FirstAid(),
        _           => new NoAbility(),
    };
}
```

`switch` 가 사라진 것은 아닙니다. **한 곳으로 모였을 뿐입니다.**
그게 이 패턴의 전부이고, 그것만으로도 값을 합니다.

- 아이템을 추가할 때 **고칠 파일이 하나로 정해집니다.** 찾아 헤맬 일이 없습니다
- 다른 코드는 구체 타입 이름을 몰라도 됩니다
- 만드는 절차가 여러 줄이어도 부르는 쪽은 한 줄입니다

마지막 항목이 실제로는 가장 큽니다. 만드는 일이 `new` 한 줄로 끝나는 경우는 드뭅니다.

```csharp
public static Enemy CreateEnemy(EnemyData data, Vector3 pos)
{
    var go = Object.Instantiate(data.prefab, pos, Quaternion.identity);
    var enemy = go.GetComponent<Enemy>();
    enemy.Init(data.hp, data.speed);
    enemy.Machine.Change(enemy.IdleState);
    EventBus.Publish(new EnemySpawned(enemy));
    return enemy;
}
```

이 다섯 줄이 코드 네 군데에 복사돼 있으면, 한 줄을 추가할 때 네 군데를 고쳐야 합니다.
**그리고 한 곳을 빠뜨립니다.**

## 유니티에서는 이미 쓰고 있습니다

`Instantiate` 가 팩토리입니다. 프리팹이 "무엇을 만들지" 를 담은 데이터이고,
`Instantiate` 는 그걸 받아서 만들어 주는 메서드입니다.
클래스 이름을 코드에 적지 않아도 되는 이유가 이것입니다.

그럼 그 위에 팩토리를 또 만드는 이유는 뭘까요. 둘입니다.

**초기화 절차를 한 곳에 두기 위해서.** 위의 다섯 줄 이야기입니다.

**나중에 만드는 방법을 바꾸기 위해서.** 이쪽이 더 중요합니다.

## 나중에 바꿀 자리를 남겨 두는 것

총알을 만드는 코드가 이렇게 흩어져 있다고 해 봅시다.

<x-compare>
<script type="application/json">
{
  "label": "나중에 오브젝트 풀로 바꾼다면",
  "tabs": [
    { "label": "직접 만들면", "code": "// 무기 A\nvar b = Instantiate(bulletPrefab, muzzle.position, rot);\nb.GetComponent<Bullet>().Init(damage);\n\n// 무기 B, 적, 보스, 함정...\n// 같은 두 줄이 프로젝트 곳곳에", "stats": [["부르는 곳", "12군데"], ["풀로 바꾸려면", "12군데를 전부 고친다"], ["빠뜨리면", "일부만 풀을 쓴다"]], "tone": "bad" },
    { "label": "팩토리를 거치면", "code": "// 부르는 쪽은 어디서나 한 줄\nbulletFactory.Create(muzzle.position, rot, damage);\n\n// 나중에 팩토리 안만 바꾼다\npublic Bullet Create(...)\n{\n    var b = pool.Get();   // Instantiate 에서 이걸로\n    b.Init(damage);\n    return b;\n}", "stats": [["부르는 곳", "12군데"], ["풀로 바꾸려면", "팩토리 한 파일"], ["빠뜨리면", "빠뜨릴 곳이 없다"]], "tone": "good" }
  ]
}
</script>
</x-compare>

**팩토리를 두는 진짜 이유는 지금 편해지려는 게 아니라 나중에 바꿀 자리를 하나로 만들어 두는 것**입니다.
총알을 오브젝트 풀에서 꺼내 쓰도록 바꾸는 작업이 뒤에 나오는데,
그때 고칠 곳이 열두 군데인지 한 군데인지가 여기서 정해집니다.

인터페이스로 두면 통째로 갈아 끼울 수도 있습니다.

```csharp
public interface IBulletFactory
{
    Bullet Create(Vector3 pos, Quaternion rot, int damage);
}

public class InstantiateBulletFactory : IBulletFactory { ... }
public class PooledBulletFactory : IBulletFactory { ... }
```

앞에서 본 서비스 로케이터나 의존성 주입이 여기서 쓰입니다.
**등록만 바꾸면 프로젝트 전체의 총알 생성 방식이 바뀝니다.**

## 팩토리가 다시 지옥이 될 때

`switch` 를 한 곳에 모았다고 했는데, 그 한 곳이 백 줄이 되면 결국 같은 문제입니다.
아이템 종류가 계속 늘어난다면 **분기 대신 등록**으로 갑니다.

```csharp
public class AbilityFactory
{
    readonly Dictionary<string, Func<Ability>> makers = new();

    public void Register(string id, Func<Ability> maker) => makers[id] = maker;

    public Ability Create(string id)
        => makers.TryGetValue(id, out var m) ? m() : new NoAbility();
}
```

이제 새 능력을 추가할 때 팩토리도 안 고칩니다.
자기를 등록하는 한 줄만 어딘가에 있으면 됩니다.

유니티에서는 더 간단한 답이 있습니다. **`ScriptableObject` 목록을 인스펙터에 두는 것**입니다.

```csharp
[SerializeField] List<Ability> catalog;   // 에셋을 끌어다 놓는다

public Ability Find(string id) => catalog.FirstOrDefault(a => a.Id == id);
```

코드를 안 고치고 에셋만 추가하면 됩니다.
프로젝트가 커지면 어드레서블로 필요할 때만 불러오는 방식으로 넘어갑니다.

> 팩토리를 만들면 `switch` 가 사라진다고 오해하기 쉬운데,
> **분기는 어딘가에 반드시 남습니다.** 무엇을 만들지는 누군가 정해야 하니까요.
> 패턴이 하는 일은 그 분기를 **한 곳에 모으고, 데이터로 옮길 수 있게 하는 것**입니다.

## 언제 쓰지 않는가

`new` 한 줄로 끝나고, 부르는 곳이 한 군데고, 앞으로 바뀔 일이 없다면
**그냥 `new` 하면 됩니다.**

팩토리는 간접 층을 하나 더 놓는 일입니다.
코드를 읽는 사람이 "이게 실제로 뭘 만들지" 를 알려면 한 번 더 따라가야 합니다.
그 비용을 치를 이유가 있을 때만 놓습니다.

> **만드는 절차가 여러 줄이거나, 부르는 곳이 여러 군데이거나,
> 만드는 방법이 바뀔 것 같으면** 팩토리를 놓는다.

## 정리

- `new` 를 쓰는 순간 그 클래스와 묶인다. 인터페이스로 나눠 놔도 `new` 가 흩어지면 소용이 적다.
- 팩토리는 분기를 없애지 않는다. **한 곳으로 모을 뿐이다.** 그것만으로 고칠 자리가 정해진다.
- 만드는 절차가 여러 줄일 때 이득이 가장 크다. 복사된 다섯 줄 중 하나는 반드시 빠진다.
- 유니티의 `Instantiate` 와 프리팹이 이미 팩토리다. 그 위에 또 두는 이유는 초기화와 교체다.
- 진짜 이유는 **나중에 바꿀 자리를 하나로 만들어 두는 것**이다. 풀로 바꿀 때 드러난다.
- 팩토리 안이 다시 길어지면 분기 대신 등록으로 간다. 딕셔너리, 에셋 목록, 어드레서블.
- 부르는 곳이 하나고 `new` 한 줄이면 그냥 `new` 한다. 간접 층도 비용이다.