抽象工厂就是为了解决“如何成套地生产相互关联的产品”。

💡 为什么要用抽象工厂模式?

假设你正在开发一款像《星际争霸》或《魔兽争霸》那样的 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); }
}

怎么用?

  1. 策划在项目里右键创建两个资产:SciFiThemeFantasyTheme
  2. SciFiTheme 里拖入充满科技感、蓝色发光的按钮和窗口预制体。
  3. FantasyTheme 里拖入木质纹理的按钮和窗口预制体。
  4. 游戏里的 UIManager 只要开放一个 public UIThemeFactorySO currentTheme; 变量。拖入哪个主题,整个游戏生成的 UI 就全套变成那个风格!

🆚 经典对比:工厂方法 vs 抽象工厂

这是面试中最爱问的问题,也是初学者最容易搞混的地方,记住下面这两句话就够了:

  • 工厂方法 (Factory Method): 生产一种产品。主要解决的是“到底是造史莱姆,还是造哥布林?”的问题。(一个接口,一个方法)。
  • 抽象工厂 (Abstract Factory): 生产一套产品家族。主要解决的是“到底是造【全套人类装备】,还是造【全套精灵装备】?”的问题。(一个接口,多个方法)。

🚨 避坑指南(抽象工厂的致命弱点)

抽象工厂虽然完美解决了“成套匹配”的问题,但它极其违反开闭原则中“对修改封闭”的部分。

假设现在我们要给所有阵营增加一种新兵种:“魔法飞行兵”
为了实现这个需求,你必须:

  1. 修改 IFactionFactory 接口,增加 CreateFlyingUnit() 方法。
  2. 导致所有实现过这个接口的类(人类工厂、外星工厂、兽人工厂...)全部报错,你必须打开每一个具体工厂,去把这套逻辑补全。

结论: 抽象工厂非常适合产品家族稳定(近战和远程基本不会变),但产品系列经常增加(后续可能会出新阵营)的场景。如果你的“兵种分类”经常要增加,千万不要用抽象工厂,会让你改代码改到怀疑人生!