一、工厂模式核心动机
解耦创建与使用
将Instantiate(prefab)、new Class()等逻辑从业务模块剥离,业务代码仅通过工厂接口获取实例。支持多版本/多平台
同一接口下,不同工厂可返回不同实现,便于在 Android/iOS 或运营服/测试服间快速切换。集中管理依赖
工厂内部可统一处理资源加载、初始化顺序、对象池取用等,避免重复分散的初始化代码。简化测试
客户端只依赖工厂接口,测试时可替换为 MockFactory,快速验证业务流程。
二、Factory Method(工厂方法)
2.1 意图
为 一组同类产品 定义一个创建方法(接口),由子类决定实例化哪一个具体产品。工厂方法使得工厂类与产品类的耦合度最小。
2.2 UML 类图

2.3 Unity 示例:技能工厂
2.3.1 定义产品接口与实现
public interface ISkill {
void Execute(Transform caster);
}
public class FireballSkill : MonoBehaviour, ISkill {
public float speed = 10f;
public GameObject effectPrefab;
public void Execute(Transform caster) {
var go = Instantiate(effectPrefab, caster.position, caster.rotation);
go.GetComponent<Rigidbody>().velocity = caster.forward * speed;
}
}
public class IceBlastSkill : MonoBehaviour, ISkill {
public float slowDuration = 2f;
public void Execute(Transform caster) {
// 冰冻范围逻辑
}
}
2.3.2 实现工厂方法
public interface ISkillFactory {
ISkill CreateSkill();
}
public class FireballFactory : MonoBehaviour, ISkillFactory {
public FireballSkill prefab;
public ISkill CreateSkill() {
return Instantiate(prefab);
}
}
public class IceBlastFactory : MonoBehaviour, ISkillFactory {
public IceBlastSkill prefab;
public ISkill CreateSkill() {
return Instantiate(prefab);
}
}
2.3.3 客户端使用
public class PlayerCaster : MonoBehaviour {
[SerializeField] ISkillFactory skillFactory;
void Update() {
if (Input.GetButtonDown("Fire1")) {
var skill = skillFactory.CreateSkill();
skill.Execute(transform);
}
}
}
优点:新增一种技能类型,只需新增
NewSkill与NewSkillFactory,旧代码无需修改。
缺点:每种技能都要写一个工厂类,若技能数目庞大,工厂类会激增。
三、Abstract Factory(抽象工厂)
3.1 意图
提供一个接口,用于创建一 族相关或相互依赖 的一组产品,而无需指定它们的具体类。
3.2 UML 类图

3.3 Unity 示例:UI 抽象工厂
3.3.1 定义 UI 产品接口
public interface IButton {
void SetText(string text);
void OnClick(Action callback);
}
public interface IWindow {
void Show();
void Hide();
}
3.3.2 ScriptableObject 驱动的抽象工厂
public abstract class UIFactory : ScriptableObject {
public abstract IButton CreateButton();
public abstract IWindow CreateWindow();
}
[CreateAssetMenu("UI/MainMenuFactory")]
public class MainMenuFactory : UIFactory {
public GameObject buttonPrefab;
public GameObject windowPrefab;
public override IButton CreateButton() {
return Instantiate(buttonPrefab).GetComponent<IButton>();
}
public override IWindow CreateWindow() {
return Instantiate(windowPrefab).GetComponent<IWindow>();
}
}
[CreateAssetMenu("UI/HUDFactory")]
public class HUDFactory : UIFactory {
public GameObject buttonPrefab;
public GameObject windowPrefab;
public override IButton CreateButton() {
return Instantiate(buttonPrefab).GetComponent<IButton>();
}
public override IWindow CreateWindow() {
return Instantiate(windowPrefab).GetComponent<IWindow>();
}
}
3.3.3 客户端使用
public class UIManager : MonoBehaviour {
[SerializeField] UIFactory uiFactory;
private List<IWindow> windows = new List<IWindow>();
void Start() {
var playBtn = uiFactory.CreateButton();
playBtn.SetText("PLAY");
playBtn.OnClick(OnPlayClicked);
var mainWin = uiFactory.CreateWindow();
windows.Add(mainWin);
mainWin.Show();
}
void OnPlayClicked() {
foreach (var w in windows) w.Hide();
// 切换到游戏 HUD
uiFactory = /* assign HUDFactory */;
// …
}
}
优点:同一工厂实例可保证按钮与窗口风格一致;
缺点:新增新 UI 族(如设置界面)需创建新ScriptableObject资产。
四、与依赖注入(DI)结合
使用 Zenject 在容器中注册抽象工厂,客户端仅依赖 ISkillFactory 或 UIFactory:
public class GameInstaller : MonoInstaller {
public UIFactory mainMenuFactory;
public UIFactory hudFactory;
public override void InstallBindings() {
Container.Bind<ISkillFactory>().To<FireballFactory>().FromComponentInHierarchy().AsTransient();
Container.Bind<UIFactory>().FromInstance(mainMenuFactory).AsSingle();
}
}
优势:
- 工厂实例通过容器注入,切换实现零侵入;
- 支持运行时热切换、A/B 测试。
五、注意事项
场景 陷阱 建议 工厂类泛滥 每种产品写一个工厂导致类数量爆炸 使用 ScriptableObject 资产驱动工厂,静态配置减少脚本数量 资源依赖裸 Instantiate直接在工厂中 Instantiate难以 Mock,测试时需加载 Prefab让工厂依赖 GameObjectProvider,便于替换实现业务与工厂逻辑混合 工厂不仅创建,还包含业务初始化 保持工厂纯粹,只做创建,额外初始化放到专用初始化器/构造器 多平台/多版本切换 工厂逻辑分散,不易统一管理 利用配置表或 DI 容器,根据平台或 AB 测试标志统一绑定工厂实现
六、小结
- 职责单一:工厂只负责实例化,不做任何后续逻辑;
- 接口抽象:客户端依赖
IFactory和产品抽象,无需关心具体类型; - 资源驱动:结合 ScriptableObject,将工厂配置化到资源中,方便迭代与版本管理;
- 容器管理:使用 DI 容器统一注册/切换,增强可测试性与扩展性;
- 稳定接口:工厂接口一旦定义,尽量不修改方法签名,避免破坏客户端调用。