返回

19.代理模式

提供一个代理者位置给一个对象,好让代理者可以控制存储这个对象。

代理模式

提供一个代理者位置给一个对象,好让代理者可以控制存储这个对象。

代理模式的定义

定义说明了两个角色之间的关系,“原始对象”及一个“代理者”,如果假设用总经理和秘书当作时这两个角色来解释定义就很好理接:提供一个秘书职位给总经理,好让秘书可以先过滤掉要接给总经理的电话。

代理模式让原始对象及代理者能同时运行,并让客户端使用相同的接口进行沟通,客户端无法分辨两者。

参与者说明如下:

  • Subject(操作接口):定义让客户端可以操作的接口。
  • RealSubject(功能执行):真正执行客户端预期功能的类。
  • Proxy(代理者)
    • 拥有一个RealSubject(功能执行)对象
    • 实现Subject定义的接口,所以可以用来取代 RealSubject 出现的地方,让原客户端来操作。
    • 实现 Subject 所定义的接口,但不重复实现 RealSubject 内的功能,仅就 Proxy 当时所代表的功能,做前置判断的工作,必要时才转为调用 RealSubject 的方法。
    • Proxy 所做的前置工作,会根据应用方式,而有不同的判断和操作

具体实现

    // 制訂RealSubject和Proxy所共用遵偱的介面
    public abstract class Subject
    {
        public abstract void Request();
    }

    // 定義Proxy所代表的真正物件
    public class RealSubject : Subject
    {
        public RealSubject()
        {
        }

        public override void Request()
        {
            Debug.Log("RealSubject.Request");
        }
    }

    // 持有指向RealSubject物件的reference以便存取真正的物件
    public class Proxy : Subject
    {
        RealSubject m_RealSubject = new RealSubject();

        // 權限控制
        public bool ConnectRemote { get; set; }

        public Proxy()
        {
            ConnectRemote = false;
        }

        public override void Request()
        {
            // 依目前狀態決定是否存取RealSubject
            if (ConnectRemote)
                m_RealSubject.Request();
            else
                Debug.Log("Proxy.Request");
        }
    }
    
 public class ProxyTest
{
    void UnitTest()
    {
        // 產生Proxy
        Proxy theProxy = new Proxy();

        // 透過Proxy存取
        theProxy.Request();
        theProxy.ConnectRemote = true;
        theProxy.Request();
    }
}

使用代理模式优化加载速度

参与者说明如下:

  • IAssetFactory:资源加载工厂
  • ResourceAssetFactory:从项目的Resource中,将Unity Asset实例化成 GameObject 的工厂类
  • ResourceAssetProxyFactory:ResourceAssetFactory的代理者内部包含了一个ResourceAssetFactory 对象及 Unity Asset 资源容器。代理者必须判断资源加载需求是否要通过原始类ResourceAssetFactory来执行,只有违背加载过的 Unity Asset 资源,才会放行 ResourceAssetFactory类去执行

具体实现

// 做為ResourceAssetFactory的Proxy代理者,會記錄已經載入過的資源
public class ResourceAssetProxyFactory : IAssetFactory
{
    private ResourceAssetFactory m_RealFactory = null; // 實際負責載入的AssetFactory
    private Dictionary<string, UnityEngine.Object> m_Soldiers = null;
    private Dictionary<string, UnityEngine.Object> m_Enemys = null;
    private Dictionary<string, UnityEngine.Object> m_Weapons = null;
    private Dictionary<string, UnityEngine.Object> m_Effects = null;
    private Dictionary<string, AudioClip> m_Audios = null;
    private Dictionary<string, Sprite> m_Sprites = null;

    public ResourceAssetProxyFactory()
    {
        m_RealFactory = new ResourceAssetFactory();
        m_Soldiers = new Dictionary<string, UnityEngine.Object>();
        m_Enemys = new Dictionary<string, UnityEngine.Object>();
        m_Weapons = new Dictionary<string, UnityEngine.Object>();
        m_Effects = new Dictionary<string, UnityEngine.Object>();
        m_Audios = new Dictionary<string, AudioClip>();
        m_Sprites = new Dictionary<string, Sprite>();
    }

    // 產生Soldier
    public override GameObject LoadSoldier(string AssetName)
    {
        // 還沒載入時
        if (m_Soldiers.ContainsKey(AssetName) == false)
        {
            UnityEngine.Object res =
                m_RealFactory.LoadGameObjectFromResourcePath(ResourceAssetFactory.SoldierPath + AssetName);
            m_Soldiers.Add(AssetName, res);
        }

        return UnityEngine.Object.Instantiate(m_Soldiers[AssetName]) as GameObject;
    }

    // 產生Enemy
    public override GameObject LoadEnemy(string AssetName)
    {
        if (m_Enemys.ContainsKey(AssetName) == false)
        {
            UnityEngine.Object res =
                m_RealFactory.LoadGameObjectFromResourcePath(ResourceAssetFactory.EnemyPath + AssetName);
            m_Enemys.Add(AssetName, res);
        }

        return UnityEngine.Object.Instantiate(m_Enemys[AssetName]) as GameObject;
    }

    // 產生Weapon
    public override GameObject LoadWeapon(string AssetName)
    {
        if (m_Weapons.ContainsKey(AssetName) == false)
        {
            UnityEngine.Object res =
                m_RealFactory.LoadGameObjectFromResourcePath(ResourceAssetFactory.WeaponPath + AssetName);
            m_Weapons.Add(AssetName, res);
        }

        return UnityEngine.Object.Instantiate(m_Weapons[AssetName]) as GameObject;
    }

    // 產生特效
    public override GameObject LoadEffect(string AssetName)
    {
        if (m_Effects.ContainsKey(AssetName) == false)
        {
            UnityEngine.Object res =
                m_RealFactory.LoadGameObjectFromResourcePath(ResourceAssetFactory.EffectPath + AssetName);
            m_Effects.Add(AssetName, res);
        }

        return UnityEngine.Object.Instantiate(m_Effects[AssetName]) as GameObject;
    }

    // 產生AudioClip
    public override AudioClip LoadAudioClip(string ClipName)
    {
        if (m_Audios.ContainsKey(ClipName) == false)
        {
            UnityEngine.Object res =
                m_RealFactory.LoadGameObjectFromResourcePath(ResourceAssetFactory.AudioPath + ClipName);
            m_Audios.Add(ClipName, res as AudioClip);
        }

        return m_Audios[ClipName];
    }

    // 產生Sprite
    public override Sprite LoadSprite(string SpriteName)
    {
        if (m_Sprites.ContainsKey(SpriteName) == false)
        {
            Sprite res = m_RealFactory.LoadSprite(SpriteName);
            m_Sprites.Add(SpriteName, res);
        }

        return m_Sprites[SpriteName];
    }
}

使用代理模式时的注意事项

装饰模式与代理模式的差别

Rroxy会知道代理的对象是哪个子类,并拥有该子类的对象,而Decorator则是拥有父类对象(被装饰对象)的引用。Proxy会按“职权”来决定是不是需要将需求转给原始类,所以Proxy有“选择”要不要执行原有功能的权力。但Decorator是一个“增加”的操作,必须在原始类被调用的之前或之后,再按照自己的职权“增加”原始类没有的功能。

适配器模式与代理模式的差异

Proxy 类与原始类同属于一个父类,所以客户端不需要做任何的变动,只需要决定是否要采取代理者。而Adapter中的Adaptee类及Target类则分属不同的类群组,着重在于“不同实现的转换”。

代理模式的应用

今年来,大型多人游戏在线橘色扮演游戏(MMORPG)在客户端多使用无缝地图的实现,用以提升玩家对游戏的体验感。但在游戏服务器的实现上,还是会将整个游戏划分为数个区块,而每个区块必须交有一个“地图服务器”来管理。当玩家在跨越两个地图服务器之间移动或进行打怪战斗时,就必须在邻近地图服务器上建立一个“代理人”。地图服务器就利用这个“代理人”来同步与其他地图服务器之间的信息传送。

在网页游戏的应用上,由于网络资源下载的速度不一致,为了要让玩家体验更好的游戏顺感,对于画面上还没有被下载成功的“游戏资源”,如建筑角色、NPC角色、角色装备道具……,大多会使用一个“资源代理人”先呈现在画面上,让玩家知道当前有个游戏资源还在下载。如果游戏资源是个3D角色的话,那么多半会使用通用的角色模式来代表一个3D角色正在加载中。待游戏资源可以重新呈现时,就会直接使用原本的游戏资源类来显示。