C# 中的代理模式 (Proxy Pattern) 详解

1. 什么是代理模式

代理模式是一种结构型设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,可以在不改变原始类代码的情况下,通过引入代理类来增加额外的功能。

核心思想:通过创建一个代理对象来控制对原始对象的访问,可以在访问前后添加额外的操作。

2. 代理模式的特点

  • 控制访问:控制客户端对真实对象的访问
  • 增强功能:在不修改原始对象的情况下增加额外功能
  • 延迟加载:可以延迟真实对象的创建和初始化
  • 远程代理:可以代表远程对象
  • 保护代理:可以控制对敏感对象的访问权限

3. 代理模式的结构

+-------------------+       +---------------------------+
|    ISubject       |       |      Proxy                |
+-------------------+       +---------------------------+
| +Request(): void  |<------| -realSubject: RealSubject |
+-------------------+       | +Request(): void          |
         ^                  +---------------------------+
         |                           ^
         |                           |
+-------------------+       +---------------------+
|   RealSubject     |       |       Client        |
+-------------------+       +---------------------+
| +Request(): void  |       |                     |
+-------------------+       +---------------------+

4. 代理模式的类型

  1. 虚拟代理:延迟创建开销大的对象
  2. 远程代理:代表远程对象
  3. 保护代理:控制对原始对象的访问权限
  4. 智能引用代理:在访问对象时执行额外操作(如引用计数)
  5. 缓存代理:为开销大的运算结果提供临时存储

5. 代码示例

基础示例:虚拟代理(延迟加载)

using System;

// 抽象主题接口
public interface IImage
{
    void Display();
}

// 真实主题 - 高分辨率图片
public class HighResolutionImage : IImage
{
    private string _filename;
  
    public HighResolutionImage(string filename)
    {
        _filename = filename;
        LoadImageFromDisk(); // 模拟加载大图片的耗时操作
    }
  
    private void LoadImageFromDisk()
    {
        Console.WriteLine($"正在从磁盘加载图片: {_filename}...");
        // 模拟耗时操作
        System.Threading.Thread.Sleep(2000);
        Console.WriteLine($"图片 {_filename} 加载完成!");
    }
  
    public void Display()
    {
        Console.WriteLine($"显示图片: {_filename}");
    }
}

// 代理类 - 图片代理
public class ImageProxy : IImage
{
    private string _filename;
    private HighResolutionImage _realImage;
  
    public ImageProxy(string filename)
    {
        _filename = filename;
    }
  
    public void Display()
    {
        Console.WriteLine("代理: 接收到显示图片请求");
    
        // 延迟加载真实图片对象
        if (_realImage == null)
        {
            _realImage = new HighResolutionImage(_filename);
        }
    
        // 调用真实对象的显示方法
        _realImage.Display();
    }
}

// 客户端代码
public class Program
{
    public static void Main()
    {
        Console.WriteLine("客户端: 需要显示一张图片");
    
        // 创建代理对象(此时不会加载真实图片)
        IImage image = new ImageProxy("test_image.jpg");
    
        Console.WriteLine("\n客户端: 代理对象已创建,但真实图片尚未加载");
        Console.WriteLine("客户端: 现在执行显示操作...\n");
    
        // 第一次调用Display会加载图片
        image.Display();
    
        Console.WriteLine("\n客户端: 再次调用显示操作...\n");
    
        // 第二次调用不会重新加载图片
        image.Display();
    }
}

保护代理示例(访问控制)

using System;

// 抽象主题接口
public interface IDatabase
{
    void Query(string sql);
}

// 真实主题 - 数据库操作
public class RealDatabase : IDatabase
{
    public void Query(string sql)
    {
        Console.WriteLine($"执行SQL查询: {sql}");
    }
}

// 代理类 - 数据库访问代理(添加权限控制)
public class DatabaseProxy : IDatabase
{
    private RealDatabase _realDatabase;
    private string _userRole;
  
    public DatabaseProxy(string userRole)
    {
        _userRole = userRole;
    }
  
    public void Query(string sql)
    {
        // 延迟初始化真实数据库对象
        if (_realDatabase == null)
        {
            _realDatabase = new RealDatabase();
        }
    
        // 检查权限
        if (CheckAccess())
        {
            Console.WriteLine($"代理: 用户({_userRole})有权限执行查询");
            _realDatabase.Query(sql);
        }
        else
        {
            Console.WriteLine($"代理: 用户({_userRole})没有执行查询的权限!");
        }
    }
  
    private bool CheckAccess()
    {
        // 只有管理员可以执行查询
        return _userRole == "Admin";
    }
}

// 客户端代码
public class Program
{
    public static void Main()
    {
        // 创建管理员代理
        IDatabase adminDb = new DatabaseProxy("Admin");
        adminDb.Query("SELECT * FROM Users");
    
        Console.WriteLine();
    
        // 创建普通用户代理
        IDatabase userDb = new DatabaseProxy("User");
        userDb.Query("SELECT * FROM Orders");
    }
}

远程代理示例(模拟)

using System;

// 远程服务接口
public interface IRemoteService
{
    string GetData(int id);
}

// 真实远程服务(模拟)
public class RemoteService : IRemoteService
{
    public string GetData(int id)
    {
        // 模拟远程调用
        Console.WriteLine($"远程服务: 获取ID为 {id} 的数据...");
        System.Threading.Thread.Sleep(1000); // 模拟网络延迟
        return $"数据 {id}: 这是来自远程服务的数据";
    }
}

// 远程代理
public class RemoteServiceProxy : IRemoteService
{
    private RemoteService _remoteService;
    private Dictionary<int, string> _cache = new Dictionary<int, string>();
  
    public string GetData(int id)
    {
        Console.WriteLine($"代理: 接收到获取数据 {id} 的请求");
    
        // 延迟初始化远程服务
        if (_remoteService == null)
        {
            _remoteService = new RemoteService();
        }
    
        // 检查缓存
        if (_cache.ContainsKey(id))
        {
            Console.WriteLine($"代理: 从缓存返回数据 {id}");
            return _cache[id];
        }
    
        // 调用远程服务
        Console.WriteLine($"代理: 从远程服务获取数据 {id}");
        string data = _remoteService.GetData(id);
    
        // 缓存结果
        _cache[id] = data;
    
        return data;
    }
}

// 客户端代码
public class Program
{
    public static void Main()
    {
        // 创建远程服务代理
        IRemoteService service = new RemoteServiceProxy();
    
        // 第一次请求(会调用远程服务)
        Console.WriteLine("\n第一次请求数据1:");
        Console.WriteLine(service.GetData(1));
    
        // 第二次请求相同数据(从缓存获取)
        Console.WriteLine("\n第二次请求数据1:");
        Console.WriteLine(service.GetData(1));
    
        // 请求新数据
        Console.WriteLine("\n第一次请求数据2:");
        Console.WriteLine(service.GetData(2));
    }
}

6. 代理模式的使用场景

代理模式适用于以下情况:

  1. 延迟初始化(虚拟代理) :当对象创建开销很大时,可以延迟对象的创建
  2. 访问控制(保护代理) :当需要控制对原始对象的访问权限时
  3. 远程访问(远程代理) :当对象位于远程地址空间时,可以本地代表远程对象
  4. 日志记录:需要在访问对象时记录日志
  5. 缓存结果(缓存代理) :当需要缓存请求结果时
  6. 智能引用:当需要额外的操作(如引用计数、对象锁定等)时

具体应用场景示例:

  • 图片加载:在图像查看器中延迟加载大图片
  • 数据库访问:添加权限控制和连接管理
  • Web服务客户端:缓存远程服务调用结果
  • ORM框架:实现延迟加载关联对象
  • API网关:作为微服务的代理
  • 防火墙代理:控制网络资源的访问

7. 代理模式的优缺点

优点:

  • 开闭原则:可以在不修改原始对象的情况下增加新功能
  • 控制访问:可以控制对原始对象的访问
  • 职责分离:代理可以处理额外功能,原始对象专注于核心功能
  • 性能优化:通过缓存、延迟加载等方式优化性能

缺点:

  • 增加复杂性:需要引入额外的类和接口
  • 间接访问:可能增加响应时间(特别是远程代理)
  • 维护成本:需要维护代理类和原始类之间的一致性

8. 与其他模式的关系

  • 与装饰器模式:两者都基于组合,但目的不同。装饰器模式用于动态添加功能,代理模式用于控制访问。
  • 与适配器模式:适配器改变接口,代理保持相同接口。
  • 与外观模式:外观模式简化接口,代理模式控制访问。

9. 实际应用建议

  1. 明确代理目的:清楚知道为什么要使用代理(延迟加载、访问控制、缓存等)
  2. 保持接口一致:代理对象和被代理对象应实现相同接口
  3. 考虑性能影响:特别是远程代理和缓存代理的性能影响
  4. 线程安全:多线程环境下需要考虑代理对象的线程安全性

10. 在Unity开发中的应用

1. 资源加载代理(Asset Loading Proxy)

资源加载代理是Unity中最常见的代理模式应用之一,主要用于优化资源加载性能,实现异步加载和缓存机制。

// 原始资源加载接口
public interface IResourceLoader
{
    Texture2D LoadTexture(string path);
}

// 真实资源加载器
public class RealResourceLoader : IResourceLoader
{
    public Texture2D LoadTexture(string path)
    {
        // 使用Unity Resources同步加载纹理
        return Resources.Load<Texture2D>(path);
    }
}

// 资源加载代理,负责异步加载并缓存资源
public class AssetLoadingProxy : IResourceLoader
{
    private Dictionary<string, IEnumerator> _loadingTasks; // 正在加载的任务字典
    private RealResourceLoader _realLoader; // 真实加载器
    private Dictionary<string, Texture2D> _cache; // 资源缓存

    public AssetLoadingProxy()
    {
        _loadingTasks = new Dictionary<string, IEnumerator>();
        _realLoader = new RealResourceLoader();
        _cache = new Dictionary<string, Texture2D>();
    }

    // 同步加载接口实现
    public Texture2D LoadTexture(string path)
    {
        // 如果资源已缓存,直接返回
        if (_cache.ContainsKey(path))
        {
            return _cache[path];
        }
    
        // 否则使用真实加载器加载并缓存
        var texture = _realLoader.LoadTexture(path);
        _cache[path] = texture;
        return texture;
    }

    // 异步加载方法
    public IEnumerator LoadTextureAsync(string path, Action<Texture2D> onLoaded)
    {
        // 如果资源已缓存,直接回调
        if (_cache.ContainsKey(path))
        {
            onLoaded?.Invoke(_cache[path]);
            yield break;
        }

        // 如果资源正在加载中,等待加载完成
        if (_loadingTasks.ContainsKey(path))
        {
            while (_loadingTasks[path] != null)
            {
                yield return null;
            }
            onLoaded?.Invoke(_cache[path]);
            yield break;
        }

        // 开始异步加载
        var www = UnityWebRequestTexture.GetTexture(path);
        _loadingTasks[path] = www.SendWebRequest();
        yield return _loadingTasks[path];

        if (www.result == UnityWebRequest.Result.Success)
        {
            var texture = DownloadHandlerTexture.GetContent(www);
            _cache[path] = texture; // 缓存加载结果
            _loadingTasks[path] = null; // 标记任务完成
            onLoaded?.Invoke(texture);
        }
        else
        {
            Debug.LogError($"加载纹理失败: {www.error}");
            _loadingTasks.Remove(path);
        }
    }
}

应用场景

  • 游戏场景中大量纹理、模型等资源的加载
  • UI系统中的图片动态加载
  • 需要异步加载避免卡顿的场景

2. 网络请求代理(Network Request Proxy)

网络请求代理可以管理网络请求队列、重试机制等,提高网络通信的可靠性。

// 网络请求接口
public interface INetworkService
{
    void SendRequest(string url, Action<string> onResponse);
}

// 真实网络服务类
public class RealNetworkService : INetworkService
{
    public void SendRequest(string url, Action<string> onResponse)
    {
        // 启动协程发送请求
        CoroutineRunner.Instance.StartCoroutine(SendRequestCoroutine(url, onResponse));
    }

    private IEnumerator SendRequestCoroutine(string url, Action<string> onResponse)
    {
        using (var request = UnityWebRequest.Get(url))
        {
            yield return request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.Success)
            {
                onResponse?.Invoke(request.downloadHandler.text);
            }
            else
            {
                Debug.LogError($"网络请求错误: {request.error}");
                onResponse?.Invoke(null);
            }
        }
    }
}

// 网络请求代理,添加了请求队列和错误重试机制
public class NetworkRequestProxy : INetworkService
{
    private INetworkService _realService; // 真实网络服务
    private Queue<Action> _requestQueue; // 请求队列
    private int _maxRetryCount; // 最大重试次数
    private bool _isProcessing; // 是否正在处理请求

    public NetworkRequestProxy(INetworkService realService, int maxRetries = 3)
    {
        _realService = realService;
        _requestQueue = new Queue<Action>();
        _maxRetryCount = maxRetries;
    }

    public void SendRequest(string url, Action<string> onResponse)
    {
        // 将请求加入队列
        _requestQueue.Enqueue(() => {
            int retryCount = _maxRetryCount;
            Action<string> wrappedCallback = response => {
                if (!string.IsNullOrEmpty(response) || --retryCount < 0)
                {
                    onResponse?.Invoke(response); // 成功或达到最大重试次数
                }
                else
                {
                    // 重试请求
                    _realService.SendRequest(url, wrappedCallback);
                }
            };
            _realService.SendRequest(url, wrappedCallback);
        });

        // 处理队列中的请求
        if (!_isProcessing)
        {
            CoroutineRunner.Instance.StartCoroutine(ProcessQueue());
        }
    }

    private IEnumerator ProcessQueue()
    {
        _isProcessing = true;
        while (_requestQueue.Count > 0)
        {
            var nextAction = _requestQueue.Dequeue();
            nextAction();
            yield return new WaitForSeconds(0.1f); // 请求间隔防止过于频繁
        }
        _isProcessing = false;
    }
}

// 协程运行器单例
public class CoroutineRunner : MonoBehaviour
{
    private static CoroutineRunner _instance;
    public static CoroutineRunner Instance
    {
        get
        {
            if (_instance == null)
            {
                var go = new GameObject("CoroutineRunner");
                DontDestroyOnLoad(go);
                _instance = go.AddComponent<CoroutineRunner>();
            }
            return _instance;
        }
    }
}

应用场景

  • 游戏服务器通信
  • 排行榜数据获取
  • 用户登录验证
  • 需要重试机制的敏感请求

3. 性能优化代理(Performance Optimization Proxy)

性能优化代理可以根据设备性能动态调整渲染质量或其他计算密集型操作。

// 高级图形渲染组件接口
public interface IAdvancedRenderer
{
    void RenderHighQualityMesh(Mesh mesh);
    void RenderLowQualityMesh(Mesh mesh);
}

// 真实高级渲染组件
public class RealAdvancedRenderer : IAdvancedRenderer
{
    public void RenderHighQualityMesh(Mesh mesh)
    {
        // 执行高质量、高消耗的渲染操作
        Debug.Log($"高质量渲染网格: {mesh.name}");
        // 实际渲染代码...
    }

    public void RenderLowQualityMesh(Mesh mesh)
    {
        // 执行低质量、低消耗的渲染操作
        Debug.Log($"低质量渲染网格: {mesh.name}");
        // 实际渲染代码...
    }
}

// 性能优化代理,根据设备性能动态调整渲染质量
public class PerformanceOptimizationProxy : IAdvancedRenderer
{
    private IAdvancedRenderer _realRenderer;
    private bool _useHighQuality;

    public PerformanceOptimizationProxy(IAdvancedRenderer realRenderer)
    {
        _realRenderer = realRenderer;
        // 根据设备性能决定使用高质量还是低质量渲染
        _useHighQuality = SystemInfo.graphicsMemorySize > 2048; // 显存大于2GB使用高质量
    }

    public void RenderHighQualityMesh(Mesh mesh)
    {
        if (_useHighQuality)
        {
            _realRenderer.RenderHighQualityMesh(mesh);
        }
        else
        {
            _realRenderer.RenderLowQualityMesh(mesh);
        }
    }

    public void RenderLowQualityMesh(Mesh mesh)
    {
        _realRenderer.RenderLowQualityMesh(mesh);
    }

    // 动态调整渲染质量
    public void SetQuality(bool highQuality)
    {
        _useHighQuality = highQuality;
        Debug.Log($"切换渲染质量为: {(highQuality ? "高" : "低")}");
    }
}

应用场景

  • 根据设备性能动态调整图形质量
  • 复杂场景下的LOD(Level of Detail)管理
  • 移动设备上的性能优化

4. Android与Unity通信代理

在Unity与Android原生代码交互时,代理模式可以简化通信过程。

using UnityEngine;
using UnityEngine.UI;

public class AndroidCommunicationProxy : MonoBehaviour
{
    public Text resultText;
    public Button callNativeButton;

    private AndroidJavaObject _nativePlugin;
    private AndroidCallbackProxy _callbackProxy;

    void Start()
    {
        // 创建Android原生插件实例
        _nativePlugin = new AndroidJavaObject("com.example.unityplugin.NativePlugin");
    
        // 创建回调代理
        _callbackProxy = new AndroidCallbackProxy(this);
    
        // 设置按钮点击事件
        callNativeButton.onClick.AddListener(() => {
            // 调用原生方法并设置回调
            _nativePlugin.Call("performNativeOperation", _callbackProxy);
        });
    }

    // 回调代理类
    public class AndroidCallbackProxy : AndroidJavaProxy
    {
        private AndroidCommunicationProxy _parent;

        public AndroidCallbackProxy(AndroidCommunicationProxy parent) 
            : base("com.example.unityplugin.NativeCallback")
        {
            _parent = parent;
        }

        // 原生代码成功回调
        public void onSuccess(string result)
        {
            UnityMainThreadDispatcher.Instance.Enqueue(() => {
                _parent.resultText.text = $"成功: {result}";
            });
        }

        // 原生代码失败回调
        public void onFailure(string error)
        {
            UnityMainThreadDispatcher.Instance.Enqueue(() => {
                _parent.resultText.text = $"失败: {error}";
            });
        }
    }
}

// 主线程调度器
public class UnityMainThreadDispatcher : MonoBehaviour
{
    private static UnityMainThreadDispatcher _instance;
    private readonly Queue<Action> _actions = new Queue<Action>();

    public static UnityMainThreadDispatcher Instance
    {
        get
        {
            if (_instance == null)
            {
                var go = new GameObject("MainThreadDispatcher");
                DontDestroyOnLoad(go);
                _instance = go.AddComponent<UnityMainThreadDispatcher>();
            }
            return _instance;
        }
    }

    public void Enqueue(Action action)
    {
        lock (_actions)
        {
            _actions.Enqueue(action);
        }
    }

    void Update()
    {
        lock (_actions)
        {
            while (_actions.Count > 0)
            {
                _actions.Dequeue().Invoke();
            }
        }
    }
}

应用场景

  • Unity调用Android原生功能(如相机、GPS等)
  • 处理Android原生插件回调
  • 跨平台通信

5. 导航代理(Navigation Proxy)

在AI导航系统中,代理模式可以封装复杂的导航逻辑。

using UnityEngine;
using UnityEngine.AI;

// 导航接口
public interface INavigator
{
    void SetDestination(Vector3 destination);
    void Stop();
    bool IsPathValid();
}

// 真实导航组件
public class RealNavigator : INavigator
{
    private NavMeshAgent _agent;

    public RealNavigator(NavMeshAgent agent)
    {
        _agent = agent;
    }

    public void SetDestination(Vector3 destination)
    {
        _agent.SetDestination(destination);
    }

    public void Stop()
    {
        _agent.isStopped = true;
    }

    public bool IsPathValid()
    {
        return _agent.hasPath && _agent.pathStatus == NavMeshPathStatus.PathComplete;
    }
}

// 导航代理,添加额外功能
public class NavigationProxy : INavigator
{
    private INavigator _realNavigator;
    private Vector3 _lastDestination;
    private float _repathInterval = 1f;
    private float _lastRepathTime;

    public NavigationProxy(INavigator realNavigator)
    {
        _realNavigator = realNavigator;
    }

    public void SetDestination(Vector3 destination)
    {
        _lastDestination = destination;
        _realNavigator.SetDestination(destination);
        _lastRepathTime = Time.time;
    }

    public void Stop()
    {
        _realNavigator.Stop();
    }

    public bool IsPathValid()
    {
        // 定期重新计算路径
        if (Time.time - _lastRepathTime > _repathInterval)
        {
            _realNavigator.SetDestination(_lastDestination);
            _lastRepathTime = Time.time;
        }
        return _realNavigator.IsPathValid();
    }

    // 代理特有方法:计算路径距离
    public float CalculatePathLength(Vector3 destination)
    {
        var path = new NavMeshPath();
        if (NavMesh.CalculatePath(_realNavigator.transform.position, destination, NavMesh.AllAreas, path))
        {
            float length = 0f;
            for (int i = 0; i < path.corners.Length - 1; i++)
            {
                length += Vector3.Distance(path.corners[i], path.corners[i + 1]);
            }
            return length;
        }
        return -1f;
    }
}

应用场景

  • NPC路径规划
  • 敌人AI追逐玩家
  • 动态避障

6. 视频解码代理

代理模式可以封装不同格式的视频解码器,提供统一的接口。

// 视频解码接口
public interface IVideoDecoder
{
    void Initialize(string filePath);
    Texture2D GetNextFrame();
    void Release();
}

// 视频解码代理
public class VideoDecoderProxy : IVideoDecoder
{
    private IVideoDecoder _realDecoder;
    private string _filePath;
    private Dictionary<string, Type> _decoderTypes = new Dictionary<string, Type>()
    {
        { ".mp4", typeof(MP4Decoder) },
        { ".avi", typeof(AVIDecoder) },
        { ".mov", typeof(MOVDecoder) }
    };

    public void Initialize(string filePath)
    {
        _filePath = filePath;
        string extension = Path.GetExtension(filePath).ToLower();
    
        if (_decoderTypes.ContainsKey(extension))
        {
            _realDecoder = (IVideoDecoder)Activator.CreateInstance(_decoderTypes[extension]);
            _realDecoder.Initialize(filePath);
        }
        else
        {
            throw new NotSupportedException($"不支持的视频格式: {extension}");
        }
    }

    public Texture2D GetNextFrame()
    {
        if (_realDecoder == null)
        {
            throw new InvalidOperationException("解码器未初始化");
        }
        return _realDecoder.GetNextFrame();
    }

    public void Release()
    {
        if (_realDecoder != null)
        {
            _realDecoder.Release();
            _realDecoder = null;
        }
    }

    // 注册新的解码器类型
    public void RegisterDecoder(string extension, Type decoderType)
    {
        if (!typeof(IVideoDecoder).IsAssignableFrom(decoderType))
        {
            throw new ArgumentException("解码器类型必须实现IVideoDecoder接口");
        }
        _decoderTypes[extension.ToLower()] = decoderType;
    }
}

// MP4解码器实现
public class MP4Decoder : IVideoDecoder
{
    public void Initialize(string filePath)
    {
        Debug.Log($"初始化MP4解码器,文件: {filePath}");
        // 实际初始化代码...
    }

    public Texture2D GetNextFrame()
    {
        // 实际解码代码...
        Debug.Log("解码MP4下一帧");
        return new Texture2D(1, 1);
    }

    public void Release()
    {
        Debug.Log("释放MP4解码器资源");
        // 实际释放代码...
    }
}

应用场景

  • 游戏过场动画播放
  • 视频播放器
  • 支持多种视频格式的游戏引擎

总结

代理模式在Unity开发中的应用非常广泛,主要包括:

  1. 资源管理:通过代理实现资源的异步加载和缓存,优化性能
  2. 网络通信:管理网络请求队列、重试机制等,提高通信可靠性
  3. 性能优化:根据设备性能动态调整渲染质量或其他计算密集型操作
  4. 平台交互:简化Unity与原生平台(如Android)的通信
  5. AI导航:封装复杂的导航逻辑,提供更简单的接口
  6. 多媒体处理:统一不同格式的多媒体处理接口
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐