设计模式——结构型模式(下)
外观模式(Facade):为复杂子系统提供 “统一的简化接口”,降低调用者复杂度。
外观模式相关具体代码如下所示:
using UnityEngine;
using System;// 子系统1:音频系统
public class AudioSystem
{public void PlayBackgroundMusic(string trackName){Debug.Log($"播放背景音乐: {trackName}");}public void StopBackgroundMusic(){Debug.Log("停止背景音乐");}public void PlaySoundEffect(string effectName, Vector3 position){Debug.Log($"在位置 {position} 播放音效: {effectName}");}
}// 子系统2:UI系统
public class UISystem
{public void ShowMainMenu(){Debug.Log("显示主菜单");}public void HideMainMenu(){Debug.Log("隐藏主菜单");}public void ShowHUD(){Debug.Log("显示游戏HUD界面");}public void UpdateScore(int score){Debug.Log($"更新分数显示: {score}");}
}// 子系统3:场景管理系统
public class SceneSystem
{public void LoadGameScene(string sceneName){Debug.Log($"加载游戏场景: {sceneName}");// 实际项目中会调用SceneManager.LoadScene()}public void UnloadCurrentScene(){Debug.Log("卸载当前场景");}public void RestartCurrentScene(){Debug.Log("重启当前场景");}
}// 子系统4:存档系统
public class SaveSystem
{public void SaveGame(string saveName){Debug.Log($"保存游戏到存档: {saveName}");}public void LoadGame(string saveName){Debug.Log($"从存档加载游戏: {saveName}");}public bool HasSaveData(){return true; // 简化示例,始终返回有存档}
}// 外观类:提供统一接口封装子系统
public class GameManagerFacade : MonoBehaviour
{// 子系统实例private AudioSystem audioSystem;private UISystem uiSystem;private SceneSystem sceneSystem;private SaveSystem saveSystem;private void Awake(){// 初始化子系统audioSystem = new AudioSystem();uiSystem = new UISystem();sceneSystem = new SceneSystem();saveSystem = new SaveSystem();}// 封装的开始新游戏流程public void StartNewGame(){Debug.Log("\n=== 开始新游戏 ===");uiSystem.HideMainMenu();sceneSystem.LoadGameScene("Level1");audioSystem.PlayBackgroundMusic("BattleTheme");uiSystem.ShowHUD();uiSystem.UpdateScore(0);}// 封装的继续游戏流程public void ContinueGame(){if (saveSystem.HasSaveData()){Debug.Log("\n=== 继续游戏 ===");uiSystem.HideMainMenu();saveSystem.LoadGame("AutoSave");audioSystem.PlayBackgroundMusic("BattleTheme");uiSystem.ShowHUD();}else{Debug.Log("\n没有可用存档,开始新游戏");StartNewGame();}}// 封装的游戏结束流程public void GameOver(){Debug.Log("\n=== 游戏结束 ===");audioSystem.StopBackgroundMusic();audioSystem.PlaySoundEffect("GameOver", Vector3.zero);uiSystem.ShowMainMenu();sceneSystem.UnloadCurrentScene();}// 封装的保存游戏流程public void SaveCurrentGame(){Debug.Log("\n=== 保存游戏 ===");saveSystem.SaveGame("AutoSave");audioSystem.PlaySoundEffect("SaveSuccess", Vector3.zero);}// 封装的更新分数流程public void AddScore(int points){// 内部可能涉及多个子系统协作uiSystem.UpdateScore(points);audioSystem.PlaySoundEffect("ScoreUp", Vector3.zero);}
}// 客户端:只与外观类交互
public class GameClient : MonoBehaviour
{public GameManagerFacade gameManager;private void Start(){if (gameManager == null){gameManager = FindObjectOfType<GameManagerFacade>();}// 显示主菜单(初始状态)// 注意:客户端不需要知道具体哪个子系统处理这个请求// 模拟用户输入Invoke("UserStartNewGame", 1f);Invoke("UserAddScore", 2f);Invoke("UserSaveGame", 3f);Invoke("UserGameOver", 4f);}private void UserStartNewGame(){gameManager.StartNewGame();}private void UserAddScore(){gameManager.AddScore(150);}private void UserSaveGame(){gameManager.SaveCurrentGame();}private void UserGameOver(){gameManager.GameOver();}
}
外观模式的优势:
简化了客户端的使用,将复杂的子系统交互封装为简单接口;降低了客户端与子系统之间的耦合度,子系统的变化不会影响客户端;隐藏了系统的内部复杂性,使代码更易于维护和扩展;可以有选择地暴露子系统功能,控制访问权限。在 Unity 中,外观模式非常适合实现GameManager
这类核心管理器,将音频、UI、场景、存档等分散的系统整合起来,为其他脚本提供简洁的接口。
享元模式(Flyweight):复用大量 “相同 / 相似属性” 的对象,减少内存消耗
享元模式相关具体代码如下所示:
using UnityEngine;
using System.Collections.Generic;// 享元对象:存储可共享的精灵数据
public class SpriteFlyweight
{public Sprite Sprite; // 共享的精灵图public string SpriteName; // 精灵名称public Vector2 Pivot; // 精灵锚点public Vector2 Size; // 精灵尺寸public SpriteFlyweight(Sprite sprite){Sprite = sprite;SpriteName = sprite.name;Pivot = sprite.pivot;Size = sprite.rect.size;}
}// 享元工厂:管理和提供享元对象
public class SpriteFlyweightFactory
{private Dictionary<string, SpriteFlyweight> flyweights = new Dictionary<string, SpriteFlyweight>();// 获取或创建享元对象public SpriteFlyweight GetFlyweight(Sprite sprite){if (sprite == null) return null;string key = sprite.name;// 如果已存在则返回共享实例,否则创建新的享元对象if (!flyweights.ContainsKey(key)){flyweights[key] = new SpriteFlyweight(sprite);Debug.Log($"创建新的享元对象: {key}");}else{Debug.Log($"复用享元对象: {key}");}return flyweights[key];}// 获取当前缓存的享元对象数量public int GetFlyweightCount(){return flyweights.Count;}
}// 具体游戏对象:包含不可共享的状态和共享的享元对象
public class GameSpriteObject : MonoBehaviour
{private SpriteFlyweight flyweight; // 共享的享元部分private Vector3 position; // 不可共享的位置private Quaternion rotation; // 不可共享的旋转private Color color; // 不可共享的颜色// 初始化方法:组合享元对象和特有状态public void Initialize(SpriteFlyweight flyweight, Vector3 pos, Quaternion rot, Color col){this.flyweight = flyweight;this.position = pos;this.rotation = rot;this.color = col;// 应用精灵和属性到游戏对象ApplySprite();}private void ApplySprite(){if (flyweight?.Sprite != null){// 添加SpriteRenderer并设置共享的精灵SpriteRenderer renderer = GetComponent<SpriteRenderer>();if (renderer == null)renderer = gameObject.AddComponent<SpriteRenderer>();renderer.sprite = flyweight.Sprite;transform.position = position;transform.rotation = rotation;renderer.color = color;gameObject.name = $"{flyweight.SpriteName}_Instance";}}
}// 客户端:使用享元模式创建大量游戏对象
public class FlyweightClient : MonoBehaviour
{public Sprite[] sprites; // 可在Inspector中赋值的精灵数组public int objectsPerType = 50; // 每种精灵创建的实例数量private SpriteFlyweightFactory factory;private List<GameSpriteObject> gameObjects = new List<GameSpriteObject>();private void Start(){// 初始化享元工厂factory = new SpriteFlyweightFactory();// 创建大量游戏对象CreateMultipleObjects();// 显示统计信息Debug.Log($"创建了 {gameObjects.Count} 个游戏对象");Debug.Log($"但只使用了 {factory.GetFlyweightCount()} 个共享的享元对象");}private void CreateMultipleObjects(){foreach (Sprite sprite in sprites){if (sprite == null) continue;// 获取共享的享元对象SpriteFlyweight flyweight = factory.GetFlyweight(sprite);// 创建多个实例,共享同一个享元对象但拥有不同的位置和颜色for (int i = 0; i < objectsPerType; i++){// 随机位置和颜色(特有状态)Vector3 randomPos = new Vector3(Random.Range(-10f, 10f),Random.Range(-5f, 5f),0);Color randomColor = new Color(Random.Range(0.8f, 1f),Random.Range(0.8f, 1f),Random.Range(0.8f, 1f));// 创建游戏对象并初始化GameObject go = new GameObject();GameSpriteObject gameSprite = go.AddComponent<GameSpriteObject>();gameSprite.Initialize(flyweight, randomPos, Quaternion.identity, randomColor);gameObjects.Add(gameSprite);}}}
}
享元模式的优势:
显著减少内存占用,尤其在需要创建大量相似对象时;降低了系统资源消耗,提高性能;将对象的共享数据和特有数据分离,清晰管理不同类型的状态;适合处理纹理、字体、配置数据等重复使用的资源。在 Unity 中,享元模式特别适合 2D 游戏中的大量精灵实例、粒子系统配置、地形瓦片等场景,能够有效优化内存使用和加载性能。
代理模式(Proxy):为对象提供 “代理类”,控制对原对象的访问(如权限、缓存、远程调用)。
代理模式的相关具体代码如下所示:
using UnityEngine;
using System;// 主题接口:定义真实对象和代理的共同行为
public interface IHeavyResource
{void Load();void Unload();bool IsLoaded { get; }string GetResourceName();
}// 真实主题:需要被代理的重量级资源(如大型模型、关卡数据等)
public class HeavyResource : IHeavyResource
{private string resourcePath;private bool isLoaded;public HeavyResource(string path){resourcePath = path;isLoaded = false;}public void Load(){if (isLoaded){Debug.Log($"{GetResourceName()} 已加载,无需重复加载");return;}// 模拟加载重量级资源的耗时操作Debug.Log($"开始加载重量级资源: {resourcePath}");// 实际项目中可能是 Resources.Load() 或 Addressables.LoadAssetAsync()System.Threading.Thread.Sleep(1000); // 模拟加载延迟isLoaded = true;Debug.Log($"{GetResourceName()} 加载完成");}public void Unload(){if (!isLoaded){Debug.Log($"{GetResourceName()} 未加载,无需卸载");return;}// 模拟卸载资源Debug.Log($"卸载重量级资源: {resourcePath}");// 实际项目中可能是 Resources.UnloadAsset() 或 Addressables.Release()isLoaded = false;}public bool IsLoaded => isLoaded;public string GetResourceName(){return System.IO.Path.GetFileName(resourcePath);}
}/// <summary>/// 代理类(ResourceProxy):实现主题接口,内部持有真实资源的引用,提供以下额外功能:/// 延迟初始化:只有在需要时才创建真实资源对象/// 权限控制:检查访问者是否有权限操作资源/// 访问日志:记录所有对资源的操作/// 安全访问:即使真实对象未初始化也能安全处理请求/// </summary>
// 代理类:控制对真实资源的访问
public class ResourceProxy : IHeavyResource
{private HeavyResource realResource; // 真实资源对象(延迟初始化)private string resourcePath;private int accessCount = 0; // 记录访问次数private string userRole; // 访问者角色(用于权限控制)public ResourceProxy(string path, string role){resourcePath = path;userRole = role;}// 代理的加载方法:包含权限检查、延迟初始化和日志记录public void Load(){accessCount++;LogAccess("Load");// 权限检查if (!HasPermission()){Debug.LogError($"权限不足!{userRole} 无法加载 {GetResourceName()}");return;}// 延迟初始化:只有在真正需要时才创建真实对象if (realResource == null){realResource = new HeavyResource(resourcePath);}realResource.Load();}// 代理的卸载方法public void Unload(){accessCount++;LogAccess("Unload");if (!HasPermission()){Debug.LogError($"权限不足!{userRole} 无法卸载 {GetResourceName()}");return;}if (realResource != null){realResource.Unload();}else{Debug.Log($"{GetResourceName()} 尚未加载,无法卸载");}}public bool IsLoaded{get{// 即使真实对象未初始化,也能安全返回状态return realResource != null && realResource.IsLoaded;}}public string GetResourceName(){return System.IO.Path.GetFileName(resourcePath);}// 代理特有的功能:权限检查private bool HasPermission(){// 只有管理员和开发者可以加载特殊资源if (resourcePath.Contains("secret") && userRole != "Admin" && userRole != "Developer"){return false;}return true;}// 代理特有的功能:访问日志记录private void LogAccess(string operation){Debug.Log($"[{DateTime.Now:HH:mm:ss}] {userRole} 执行 {operation} 操作,资源: {GetResourceName()},累计访问: {accessCount}次");}
}// 客户端:只与代理交互,不直接访问真实对象
public class ResourceManager : MonoBehaviour
{private void Start(){// 创建代理(客户端不知道真实对象的存在)IHeavyResource normalResource = new ResourceProxy("models/character.prefab", "Player");IHeavyResource secretResource = new ResourceProxy("models/secret_boss.prefab", "Player");IHeavyResource adminResource = new ResourceProxy("models/secret_boss.prefab", "Admin");// 通过代理访问资源Debug.Log("=== 玩家访问普通资源 ===");normalResource.Load();normalResource.Load(); // 测试重复加载Debug.Log($"普通资源加载状态: {normalResource.IsLoaded}");normalResource.Unload();Debug.Log("\n=== 玩家访问机密资源(权限不足) ===");secretResource.Load(); // 会被拒绝Debug.Log("\n=== 管理员访问机密资源 ===");adminResource.Load();Debug.Log($"机密资源加载状态: {adminResource.IsLoaded}");adminResource.Unload();}
}
代理模式的优势:
控制对真实对象的访问,可实现权限管理、访问限制;实现延迟加载,提高系统启动速度和内存使用效率;可以在不修改真实对象的情况下添加额外功能(如日志、缓存);隔离了客户端与真实对象,降低了耦合度。在 Unity 中,代理模式适合用于资源管理、网络请求、权限控制等场景,尤其适合处理那些创建成本高、加载耗时的资源或服务。