0x0b.外观模式
外观模式(Facade Pattern,也叫门面模式)。如果说之前的“建造者模式”是为了解决单个复杂对象的组装问题,那么“外观模式”则是为了解决一堆复杂子系统的混乱调用问题。
在 Unity 独立开发或中大型项目重构时,外观模式是帮你维持代码整洁、实现“一键轰炸”的绝对拯救者。
💡 为什么要用外观模式?
想象一下,当玩家在主菜单点击“开始游戏”时,你的后台需要发生多少事情:
SaveSystem(存档系统):读取玩家的上一次存档数据。SceneLoader(场景加载器):异步加载游戏主关卡。AudioManager(音频管理器):淡出主菜单 BGM,播放关卡背景音乐。UIManager(UI 系统):关闭主菜单,显示加载条,加载完成后显示玩家 HUD(血条等)。NetworkManager(网络系统):向服务器同步玩家上线状态。
新手的灾难写法:
在主菜单的“开始按钮”脚本里,把这 5 个系统的引用全部拖进来,然后在一行按钮点击事件里挨个调用它们的方法。
这会导致主菜单脚本和这 5 个系统死死地捆绑在一起。以后如果网络系统要换、或者增加一个“成就系统”,你都得去改主菜单按钮的代码。
外观模式的优雅解法:
给这堆复杂的子系统套上一个“外壳”或者设一个“前台接待员”(Facade)。主菜单只需要对接待员说一句:“我要开始游戏!”接待员自己转身去调度那 5 个复杂的系统,主菜单根本不需要知道后面发生了什么。
一、 Unity 场景下的外观模式代码实现
我们把上面提到的复杂子系统封装到一个叫 GameFlowFacade 的外观类中。
1. 杂乱无章的各个子系统(底层细节)
using UnityEngine;
public class SaveSystem : MonoBehaviour
{
public void LoadUserData() { Debug.Log("存档系统:已成功读取玩家进度。"); }
}
public class AudioManager : MonoBehaviour
{
public void SwitchToGameplayMusic() { Debug.Log("音频系统:已切换至战斗 BGM。"); }
}
public class UIManager : MonoBehaviour
{
public void ShowGameplayHUD() { Debug.Log("UI系统:隐藏主菜单,显示游戏内血条和UI。"); }
}
public class SceneLoader : MonoBehaviour
{
public void LoadLevel(string levelName) { Debug.Log($"场景系统:正在异步加载【{levelName}】..."); }
}
2. 核心灵魂:外观类 (GameFlowFacade)
外观类把所有的子系统组合在一起,并向外暴露几个极其简单的“一键式”方法。
using UnityEngine;
public class GameFlowFacade : MonoBehaviour
{
// 内部持有所有子系统的引用(可以在 Inspector 里拖入,也可以用单例)
[Header("Subsystems")]
public SaveSystem saveSystem;
public AudioManager audioManager;
public UIManager uiManager;
public SceneLoader sceneLoader;
// 外观模式提供的核心高层接口:一键启动游戏
public void EnterGameplay(string targetLevel)
{
Debug.Log("==== 外观模式:开始协调各个子系统进行场景切换 ====");
// 按正确的业务逻辑顺序,一条龙服务
saveSystem.LoadUserData();
audioManager.SwitchToGameplayMusic();
uiManager.ShowGameplayHUD();
sceneLoader.LoadLevel(targetLevel);
Debug.Log("==== 外观模式:所有系统就绪 ====");
}
// 外观模式提供的另一个接口:一键退出游戏
public void ExitGame()
{
Debug.Log("外观模式:正在保存数据并安全退出...");
// 可以在这里调用存档系统的保存、网络系统的断开等
}
}
3. 客户端调用(主菜单按钮)
现在,主菜单的按钮脚本变得异常干净,它只需要知道 GameFlowFacade 的存在。
using UnityEngine;
public class MainMenuController : MonoBehaviour
{
// 只需要引入外观类即可,不需要引入 4、5 个管理器
public GameFlowFacade gameFacade;
// 绑定到主菜单“开始游戏”按钮的点击事件上
public void OnStartButtonClick()
{
// 客户端只需要发出一个简单的指令,底层细节全被屏蔽了
gameFacade.EnterGameplay("World_1_Level_1");
}
}
二、 外观模式在 Unity 中的王牌拍档:结合单例
细心的你可能已经发现了:在 Unity 中,为了能让任何地方都方便地调用这个“前台接待员”,我们经常会把外观模式(Facade)和单例模式(Singleton)结合在一起。
比如,你写了一个 GameManager,它肚子里装着 LevelManager、InputManager、BuffManager。
这个 GameManager 本身是一个单例(方便全局访问 GameManager.Instance),但它对外的职责是一个外观(封装了游戏暂停、游戏胜利、游戏失败等一系列涉及多系统的复杂操作)。
public class GameManager : Singleton<GameManager> // 这是一个单例
{
// 它同时也是一个外观,底下管着一堆复杂的系统
public CombatSystem combatSystem;
public ScoreSystem scoreSystem;
public AchievementSystem achievementSystem;
// 玩家死亡时,一键触发结算外观
public void OnPlayerDied()
{
combatSystem.DisableAllEnemies(); // 敌人停止攻击
scoreSystem.CalculateFinalScore(); // 结算分数
achievementSystem.CheckDeathCount(); // 检查是否达成“屡败屡战”成就
// ...
}
}
💡 总结与避坑指南
-
核心思想: 通过创造一个高层接口,把一堆散乱的子系统(Subsystems)包起来,给上层调用者提供一个简单、统一的“全面屏”界面。
-
适用场景:
-
游戏初始化 / 游戏启动引导(Bootstrapping)。
-
关卡切换、过渡结算(胜利、失败、死亡)。
-
当你引入了庞大的第三方插件(比如某个复杂的网络 SDK 或物理引擎),你不想让它污染你的核心代码时,写一个 Facade 把它隔离起来。
-
🚨 避坑指南 —— 不要让 Facade 变成“万能的上帝类”:
外观模式的职责是“路由和转发”(像前台接待员一样,指引工作给对应部门),它自己不应该包含具体的业务逻辑代码。如果你发现你的GameFlowFacade里面开始写如何计算伤害、如何移动物体的具体代码时,说明你走偏了,赶紧把这些逻辑放回到对应的子系统里去。