工厂模式(Factory Pattern)解决的则是“如何优雅地创建对象”的问题。

在 Unity 开发中,我们经常需要生成(Spawn)各种东西:敌人、子弹、掉落物、特效。如果你在代码里到处写 Instantiate(prefab),一旦你需要修改生成逻辑(比如给生成的怪物加上特定的等级、或者引入对象池),你就得把所有写了 Instantiate 的地方都改一遍。

工厂模式的核心思想就是:把“创建对象”的过程封装起来,交给一个专门的“工厂”去处理。 外部代码不需要知道对象是怎么被 new(或 Instantiate)出来的,只需要向工厂“要”就行了。

为了让你循序渐进地理解,我们分两种最常见的写法来演示:简单工厂工厂方法


准备工作:定义我们要生产的“产品”

假设我们正在做一款 RPG 游戏,需要生成不同的敌人(Slime 和 Orc)。首先,我们需要为所有敌人定义一个共同的基类或接口,这样工厂生产出来的东西就可以统一管理。

using UnityEngine;

// 所有敌人的基类
public abstract class Enemy : MonoBehaviour
{
    public abstract void Attack();
}

// 具体敌人 A:史莱姆
public class Slime : Enemy
{
    public override void Attack()
    {
        Debug.Log("史莱姆吐出了酸液!");
    }
}

// 具体敌人 B:兽人
public class Orc : Enemy
{
    public override void Attack()
    {
        Debug.Log("兽人挥舞了巨斧!");
    }
}


一、 简单工厂 (Simple Factory) —— Unity中最常见的做法

“简单工厂”其实并不是 GoF 23 种设计模式之一,但它非常实用,是初学者最应该掌握的。在 Unity 中,我们可以用一个统一的 EnemyFactory 脚本来管理所有敌人的预制体(Prefab),并根据传入的参数来决定生成谁。

using UnityEngine;

public class SimpleEnemyFactory : MonoBehaviour
{
    // 在 Inspector 面板中拖入对应的预制体
    [Header("Enemy Prefabs")]
    public GameObject slimePrefab;
    public GameObject orcPrefab;

    // 也可以定义一个枚举,比用字符串更安全,不容易拼错
    public enum EnemyType { Slime, Orc }

    // 工厂的生产方法
    public Enemy CreateEnemy(EnemyType type, Vector3 spawnPosition)
    {
        GameObject enemyObj = null;

        // 根据传入的类型,决定实例化哪个预制体
        switch (type)
        {
            case EnemyType.Slime:
                enemyObj = Instantiate(slimePrefab, spawnPosition, Quaternion.identity);
                break;
            case EnemyType.Orc:
                enemyObj = Instantiate(orcPrefab, spawnPosition, Quaternion.identity);
                break;
            default:
                Debug.LogError("未知的敌人类型!");
                return null;
        }

        // 可以统一在这里进行初始化操作,比如播放出生特效、设置初始等级等
        // PlaySpawnEffect(spawnPosition);

        return enemyObj.GetComponent<Enemy>();
    }
}

如何使用它?
客户端代码(比如关卡管理器)不再需要自己去实例化预制体了:

public class LevelManager : MonoBehaviour
{
    // 引用我们的工厂
    public SimpleEnemyFactory enemyFactory;

    void Start()
    {
        // 告诉工厂:给我一个史莱姆,放在原点
        Enemy enemy1 = enemyFactory.CreateEnemy(SimpleEnemyFactory.EnemyType.Slime, Vector3.zero);
        enemy1.Attack(); 

        // 告诉工厂:给我一个兽人,放在(2,0,0)的位置
        Enemy enemy2 = enemyFactory.CreateEnemy(SimpleEnemyFactory.EnemyType.Orc, new Vector3(2, 0, 0));
        enemy2.Attack();
    }
}


二、 工厂方法模式 (Factory Method) —— 进阶解耦

简单工厂的缺点是什么? 假设你以后要给游戏增加 10 种新怪物(龙、哥布林、骷髅……),你的 switch 语句就会变得无限长。这就违背了设计模式中非常重要的一条原则:开闭原则(对扩展开放,对修改封闭)。我们希望在增加新怪物时,不要去修改现有的工厂代码。

“工厂方法”的做法是:定义一个创建对象的抽象方法,让子类工厂去决定到底要实例化谁。

using UnityEngine;

// 1. 定义一个抽象工厂基类
public abstract class EnemySpawner : MonoBehaviour
{
    // 这是一个抽象的工厂方法,交由子类去实现具体的生成逻辑
    public abstract Enemy Spawn(Vector3 position);
    
    // 我们还可以在基类里写一些所有生成器通用的逻辑
    protected void PlaySpawnSound()
    {
        Debug.Log("播放了怪物出生的通用音效");
    }
}

// 2. 具体的史莱姆生成器 (Slime Factory)
public class SlimeSpawner : EnemySpawner
{
    public GameObject slimePrefab; // 只关心史莱姆的预制体

    public override Enemy Spawn(Vector3 position)
    {
        GameObject obj = Instantiate(slimePrefab, position, Quaternion.identity);
        PlaySpawnSound();
        return obj.GetComponent<Enemy>();
    }
}

// 3. 具体的兽人生成器 (Orc Factory)
public class OrcSpawner : EnemySpawner
{
    public GameObject orcPrefab; // 只关心兽人的预制体

    public override Enemy Spawn(Vector3 position)
    {
        GameObject obj = Instantiate(orcPrefab, position, Quaternion.identity);
        PlaySpawnSound();
        // 兽人可以有自己特殊的出生逻辑,比如出生自带激怒效果
        obj.GetComponent<Orc>().Enrage();
        return obj.GetComponent<Enemy>();
    }
}

如何使用它?
这种方式的好处是,调用者根本不知道、也不需要关心到底有几种怪物。它只对着 EnemySpawner 这个抽象类说话。

public class SpawnPoint : MonoBehaviour
{
    // 这里我们可以拖入 SlimeSpawner,也可以拖入 OrcSpawner
    // 关卡设计师在 Inspector 里随意配置,代码一行都不用改!
    public EnemySpawner spawner; 

    void Start()
    {
        // 直接调用生成,不管 spawner 肚子里装的是史莱姆还是兽人工厂
        Enemy spawnedEnemy = spawner.Spawn(transform.position);
        spawnedEnemy.Attack();
    }
}


💡 总结与建议 (Unity 特供版)

  1. 什么时候用简单工厂?
    如果你的产品种类固定(比如只有3种不同颜色的金币),或者数量很少,用简单工厂(带 switch/if-else)是最高效、最直观的,不要为了炫技而强行拆分。
  2. 什么时候用工厂方法?
    当你的产品种类非常多,且经常需要扩展(比如有 50 种敌人、上百种技能特效),或者每种产品的“生成过程”都很复杂(不仅仅是 Instantiate,还需要读取配置表、绑定特定数据等),使用工厂方法能让代码更干净,团队协作时也不容易引起冲突。
  3. 终极形态:结合对象池(Object Pool)。
    在 Unity 中,频繁地 InstantiateDestroy 会导致严重的性能问题(GC 卡顿)。在实际商业项目中,工厂模式几乎总是和对象池配合使用的:工厂在生产对象时,不再是 Instantiate,而是优先从对象池里“捞”一个隐藏的预制体出来。这样你的代码架构既优雅,性能又极其强悍!