代理模式
提供一个代理者位置给一个对象,好让代理者可以控制存储这个对象。
代理模式的定义
定义说明了两个角色之间的关系,“原始对象”及一个“代理者”,如果假设用总经理和秘书当作时这两个角色来解释定义就很好理接:提供一个秘书职位给总经理,好让秘书可以先过滤掉要接给总经理的电话。
代理模式让原始对象及代理者能同时运行,并让客户端使用相同的接口进行沟通,客户端无法分辨两者。
参与者说明如下:
- 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角色正在加载中。待游戏资源可以重新呈现时,就会直接使用原本的游戏资源类来显示。