设计模式——结构型模式(下)

外观模式(Facade):为复杂子系统提供 "统一的简化接口",降低调用者复杂度。

外观模式相关具体代码如下所示:

cs 复制代码
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):复用大量 "相同 / 相似属性" 的对象,减少内存消耗

享元模式相关具体代码如下所示:

cs 复制代码
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):为对象提供 "代理类",控制对原对象的访问(如权限、缓存、远程调用)。

代理模式的相关具体代码如下所示:

cs 复制代码
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 中,代理模式适合用于资源管理、网络请求、权限控制等场景,尤其适合处理那些创建成本高、加载耗时的资源或服务。

相关推荐
new_daimond4 小时前
设计模式-迭代器模式详解
设计模式·迭代器模式
天將明°4 小时前
单例模式指南:全局资源的安全访问
c语言·单例模式·设计模式
yujkss4 小时前
23种设计模式之【单例模式模式】-核心原理与 Java实践
java·单例模式·设计模式
Chan164 小时前
【 设计模式 | 行为型模式 观察者模式 】
java·spring boot·后端·spring·观察者模式·设计模式·idea
yujkss5 小时前
23种设计模式之【策略模式】-核心原理与 Java 实践
java·设计模式·策略模式
new_daimond6 小时前
设计模式-建造者模式详解
java·设计模式·建造者模式
ytadpole7 小时前
揭秘设计模式:状态设计模式 优雅地管理对象状态
java·后端·设计模式
new_daimond8 小时前
设计模式-中介者模式详解
设计模式·中介者模式
bkspiderx8 小时前
C++设计模式之创建型模式:建造者模式(Builder)
c++·设计模式·建造者模式