0x03.抽象工厂模式
抽象工厂就是为了解决“如何成套地生产相互关联的产品”。
💡 为什么要用抽象工厂模式?
假设你正在开发一款像《星际争霸》或《魔兽争霸》那样的 RTS(即时战略)游戏。游戏里有不同的阵营(Faction),比如“人类(Human)”和“外星人(Alien)”。
每个阵营都有对应的近战兵(Melee Unit)和远程兵(Ranged Unit):
- 人类阵营: 剑士(近战)、火枪手(远程)。
- 外星阵营: 异形虫(近战)、激光射手(远程)。
如果用普通的工厂模式会有什么问题?
你可能会写一个“近战兵工厂”和一个“远程兵工厂”。但在游戏生成兵种时,如果不小心把“人类阵营的剑士”和“外星阵营的激光射手”混在了一起,就会导致严重的逻辑 Bug 或画风割裂。
抽象工厂模式的优雅解法:
不再按“兵种类型”来建工厂,而是按“产品家族(阵营/主题)”来建工厂。一个“人类兵工厂”会负责生产所有人类相关的兵种。这样就能保证生产出来的产品是“一套”的,绝对不会混搭出错!
一、 纯 C# 代码实现:生产一套“阵营”
我们要保证客户端(GameManager)不管当前是哪个阵营,都能用同一套代码逻辑去生成对应的兵种。
1. 定义产品家族的接口
无论是什么阵营,近战兵和远程兵的本质行为是一样的。
using UnityEngine;
// 抽象产品 A:近战兵
public interface IMeleeUnit
{
void Attack();
}
// 抽象产品 B:远程兵
public interface IRangedUnit
{
void Shoot();
}
2. 实现具体的兵种(产品)
// 人类家族的产品
public class HumanSwordsman : IMeleeUnit
{
public void Attack() { Debug.Log("人类剑士:挥舞铁剑劈砍!"); }
}
public class HumanMusketeer : IRangedUnit
{
public void Shoot() { Debug.Log("人类火枪手:发射铅弹!"); }
}
// 外星家族的产品
public class AlienBug : IMeleeUnit
{
public void Attack() { Debug.Log("异形虫:用利爪撕咬!"); }
}
public class AlienLaserShooter : IRangedUnit
{
public void Shoot() { Debug.Log("外星射手:发射等离子激光!"); }
}
3. 核心灵魂:抽象工厂接口
这个接口规定了一个家族必须能生产哪些东西。
// 抽象工厂:规定了必须能成套生产近战和远程单位
public interface IFactionFactory
{
IMeleeUnit CreateMeleeUnit();
IRangedUnit CreateRangedUnit();
}
4. 实现具体的阵营工厂
// 具体工厂 1:人类阵营兵工厂
public class HumanFactory : IFactionFactory
{
public IMeleeUnit CreateMeleeUnit() { return new HumanSwordsman(); }
public IRangedUnit CreateRangedUnit() { return new HumanMusketeer(); }
}
// 具体工厂 2:外星阵营兵工厂
public class AlienFactory : IFactionFactory
{
public IMeleeUnit CreateMeleeUnit() { return new AlienBug(); }
public IRangedUnit CreateRangedUnit() { return new AlienLaserShooter(); }
}
5. 见证奇迹的时刻(客户端代码)
现在,我们的游戏管理器根本不需要知道具体有哪些兵种,它只要拿着对应的工厂,就能“一键换皮/换阵营”。
public class GameLevelManager : MonoBehaviour
{
// 当前正在使用的工厂
private IFactionFactory _currentFactory;
void Start()
{
// 假设玩家选择了外星阵营
SetFaction(new AlienFactory());
SpawnArmy();
// 随时可以无缝切换到人类阵营!
Debug.Log("--- 切换阵营 ---");
SetFaction(new HumanFactory());
SpawnArmy();
}
public void SetFaction(IFactionFactory factory)
{
_currentFactory = factory;
}
// 生成军队的逻辑完全不需要修改!
private void SpawnArmy()
{
// 无论当前是什么工厂,都会成套地生成对应阵营的兵
IMeleeUnit melee = _currentFactory.CreateMeleeUnit();
IRangedUnit ranged = _currentFactory.CreateRangedUnit();
melee.Attack();
ranged.Shoot();
}
}
二、 🌟 Unity 开发者特供版:多主题 UI 切换器
在 Unity 的实际开发中,除了做阵营,抽象工厂最常被用来做“UI 主题系统”或者“皮肤系统”。我们可以再次请出我们的老朋友 ScriptableObject,让策划可以一键切换整个游戏的画风!
using UnityEngine;
// 1. 定义 UI 抽象工厂(把一整套 UI 的预制体打包)
[CreateAssetMenu(fileName = "New UI Theme", menuName = "UI/Theme Factory")]
public class UIThemeFactorySO : ScriptableObject
{
// 这就是一个“主题家族”
public GameObject buttonPrefab;
public GameObject windowPrefab;
public GameObject sliderPrefab;
// 工厂方法
public GameObject CreateButton(Transform parent) { return Instantiate(buttonPrefab, parent); }
public GameObject CreateWindow(Transform parent) { return Instantiate(windowPrefab, parent); }
}
怎么用?
- 策划在项目里右键创建两个资产:
SciFiTheme和FantasyTheme。 - 在
SciFiTheme里拖入充满科技感、蓝色发光的按钮和窗口预制体。 - 在
FantasyTheme里拖入木质纹理的按钮和窗口预制体。 - 游戏里的
UIManager只要开放一个public UIThemeFactorySO currentTheme;变量。拖入哪个主题,整个游戏生成的 UI 就全套变成那个风格!
🆚 经典对比:工厂方法 vs 抽象工厂
这是面试中最爱问的问题,也是初学者最容易搞混的地方,记住下面这两句话就够了:
- 工厂方法 (Factory Method): 生产一种产品。主要解决的是“到底是造史莱姆,还是造哥布林?”的问题。(一个接口,一个方法)。
- 抽象工厂 (Abstract Factory): 生产一套产品家族。主要解决的是“到底是造【全套人类装备】,还是造【全套精灵装备】?”的问题。(一个接口,多个方法)。
🚨 避坑指南(抽象工厂的致命弱点)
抽象工厂虽然完美解决了“成套匹配”的问题,但它极其违反开闭原则中“对修改封闭”的部分。
假设现在我们要给所有阵营增加一种新兵种:“魔法飞行兵”。
为了实现这个需求,你必须:
- 修改
IFactionFactory接口,增加CreateFlyingUnit()方法。 - 导致所有实现过这个接口的类(人类工厂、外星工厂、兽人工厂...)全部报错,你必须打开每一个具体工厂,去把这套逻辑补全。
结论: 抽象工厂非常适合产品家族稳定(近战和远程基本不会变),但产品系列经常增加(后续可能会出新阵营)的场景。如果你的“兵种分类”经常要增加,千万不要用抽象工厂,会让你改代码改到怀疑人生!