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