前言
结束了前一阶段的创建型模式,接下来我们来学习结构型模式。结构型模式就是通过多个类和对象进行组合得到复杂结构的类,一般使用继承或者成员变量引用的形式来实现。结构型模式总共有七种,包括代理模式、桥接模式、装饰器模式、适配器模式、组合模式、享元模式、外观(门面)模式,七种代理模式是比较常用结构型模式之一,我们来看一下项目中的代码目录截图:
上图中会看到有好多Proxy,这就是用到了代理模式,下面我们就详细学习一下这个代理模式。
基本介绍
代理模式(Proxy Design Pattern),为其它对象提供一种代理以控制这个对象的访问和扩展。换言之就是给某个对象提供一个代理,并由代理控制原对象的引用。
一些典型的代理应用场景
- Windows里面的快捷方式
- 西游记中,猪八戒去找高翠兰,结果是孙悟空变的,可以这样理解:高翠兰的外貌是一个基类,高翠兰继承自这个基类,孙悟空也继承自这个外貌的基类,但猪八戒并不知道他接触的高翠兰是孙悟空,此时孙悟空就是高翠兰的代理
- 我们买火车票不一定要去车站,可以去代售点,而代售点就是代理,也能够售票
- 一张支票或者银行存单是银行账户的资金代理,我们可以凭借存单或者支票也能够在市场中进行买卖,这里典型的一个应用就是通承兑
- 我们点外卖,外卖员就是我们的代理,我们不需要自己去店取餐,外卖员替我们取餐
代理模式的结构
通过上图我们会发现代理模式有三个角色:
- 抽象主题角色(Subject):声明了真实主题和代理主题的公共接口,这样一来凡是使用真实主题的任何地方都可以使用代理
- 真实主题角色(RealSubject):定义乐乐角色所代表的真实对象
- 代理主题角色(Proxy):代理主题角色内部包含了真实主题角色的引用,从而可以操作真实主题对象,代理主题角色在需要用到的时候创建真实的主题对象。
代理模式的原理与实现
在不改变原始类的情况下,通过引入代理类来给原始类附加功能。一般情况下,我们让代理类继承实现原始类同样的接口。但如果原始类并没有定义接口,而且原始类代码并不是我们开发维护的,这种情况下,我们可以让代理类继承原始类的方法来实现代理类。
为什么要用代理模式
我们在考虑为什么的时候,主要就是从它的优劣势来分析,也就是说我们用了会有什么好处,如果不用会有什么弊端。
我们从三个案例触发,首先举个搜索的例子,我们常用的搜索引擎百度、谷歌,我们知道要被搜索的内容是存在硬盘上的,这是存档,大多数就是数据库上,我们会发现搜索两次相同的东西,第二次会明显比第一次要快很多,这是为什么呢?因为服务器给我们做了缓存,对于服务器而言,AB两个人搜索相同的东西,第一次搜索,百度服务器要从数据库中查找结果,然后通过缓存代理CacheProxy做了数据缓存,这里的Cacheproxy可以理解为Redis或者Memcache服务器,我们的搜索需求都是通过代理去完成的,如果代理对这次搜索做了缓存那就直接从缓存处理,就不需要从数据库中搜索了,这样提高了查询效率,对应游戏开发中,我们要加载一个磁盘上的图片,我们不需要每次要用这个图片的时候都进行加载,可以通过代理来做一个图片的缓存,这样第二次访问就快很多。
第二个例子,我们一个游戏项目,一开始要初始化各种Manager,每个Manager或许都做了好多初始化的工作,开辟了好多内存,占用了好多资源,但有的是否是需要立马创建呢?是否可以在需要的时候进行创建呢,这样我们做一个代理,在程序启动的时候我们启动各种代理,然后操作都是通过代理去操作,当真正需要用到某个Manager的时候,这个代理发现这个Manager比没有实例化,那么就进行实例化然后进行访问,这样是懒加载的模式,这种方式在程序启动的时候节省了资源,可能这里举例不太恰当,但想要表达的思想就是这个么意思,代理也有这样的功能。通过以上两个例子就可以知道一些代理模式的好处,当然代理模式还有其他各种好处,根据不同的好处也会细分不同功能的代理。
第三个例子,如果我们用了一个第三方库,我们的需求这个库不能满足,我们就需要对他进行扩展,或者说有的库的方法不希望被调用,那我们就通过创建代理类,将不希望被调用的方法隐藏,不对调用者开放,然后扩展原本不满足需求的功能,这也是代理类的作用之一,扩展原功能。
关于扩展我有话说:
如果我们不是调用的第三方库,如果是中台引擎组开发的一个通用类库,我们可以让引擎组把这个类库改成部分类partial class,然后我们本项目进行扩展,如果是某个对象需要扩展,我们可以通过扩展方法来扩展它的功能。这两种方式都是不改变原本代码而进行的功能扩展常用方式。
常用代理类型
防火墙代理
控制网络资源的访问,保护主题免于”坏客户”的侵害。智能引用代理
当主题被引用时,进行额外的动作,例如计算一个对象的被引用次数。缓存代理
为开销大的运算结果提供暂时存储,它也允许多个客户共享结果,以减少计算或网络延迟。同步代理
在多线程的情况下为主题提供安全的访问。复杂隐藏代理
用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也称外观代理,复杂隐藏代理和外观模式是不一样的,因为代理需要实际控制访问,而外观模式只提供另一组接口。写入时复制代理
用来控制对象的复制,方法是延时对象的复制,直到客户真的需要为止。这是虚拟代理的变体。
代理模式优劣
优点
- 降低耦合度,代理模式能够将调用者与被调用者做一层对象隔离,在一定程度上降低复杂度
- 扩展性好,不需要修改被代理类即可实现新增功能,符合开闭原则
- 职责清晰,我们可以细分代理权限,比如安全代理、缓存代理等等
- 保护被代理类,调用者不可直接操作被代理类
缺点
- 在被代理类之间增加了一个代理对象,会造成请求处理的速度变慢
- 实现代理类也需要额外的工作,从而增加系统的真实复杂度
Unity游戏开发中代理模式的应用示例
我们拿一个AnimationProxy举例,我们在操作角色的时候,肯定会跟角色的动画打交道,动画肯定会有对应的状态State的设计,我们定义一个IAnimationState和IAnimation的接口
IAnimation
1 | public interface IAnimation |
关注其中的某些接口方法Blend,AddClip,IsPlaying,Stop等,我们在关注一下Unity内置的Animation方法
看上图,Unity内置的Animation方法也有我们接口定义的方法,这是不是有点符合上文讲的代理模式的意味,只不过Unity这里Animation没有将这些方法抽成一个我们自定义的API,我们自己抽象了一个这样的API,其实Animation也是可以继承自这个IAnimation接口的,上文有提到如果第三方库没有继承定义的接口,我们可以继承这个类来实现代理或者扩展,还有这种方式就是定义个第三方库api的通用接口,其实主要还是为了实现跟第三方库一样的api方法,然后代理去调用它的api。同理我们看一下Animation和IAnimation是不是也是类似的api方法
AnimationStateProxy
1 | private class AnimationStateProxy: IAnimationState |
上面AnimationStateProxy就是AnimationState的代理,通过构造函数传入AnimationState对象,并且实现了Animation对应的api方法,各个方法里面调用的是AnimationState的属性和方法,这就是代理模式的最典型的应用,跟代理模型一模一样。至于AnimationProxy也是同理,只不过代码更多一点而已1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188using UnityEngine;
[RequireComponent(typeof(Animation))]
public class AnimationProxy : MonoBehaviour, IAnimation
{
private Animation m_Animation;
new private Animation animation
{
get
{
if (m_Animation == null)
{
m_Animation = GetComponent<Animation>();
}
return m_Animation;
}
}
public bool animatePhysics
{
get { return animation.animatePhysics; }
set { animation.animatePhysics = value; }
}
public AnimatorCullingMode cullingMode
{
get
{
AnimatorCullingMode mode;
switch (animation.cullingType)
{
case AnimationCullingType.AlwaysAnimate:
mode = AnimatorCullingMode.AlwaysAnimate;
break;
case AnimationCullingType.BasedOnRenderers:
mode = AnimatorCullingMode.CullCompletely;
break;
default:
mode = AnimatorCullingMode.CullUpdateTransforms;
break;
}
return mode;
}
set
{
AnimationCullingType type;
switch(value)
{
case AnimatorCullingMode.AlwaysAnimate:
type = AnimationCullingType.AlwaysAnimate;
break;
default:
type = AnimationCullingType.BasedOnRenderers;
break;
}
animation.cullingType = type;
}
}
public bool isPlaying
{
get { return animation.isPlaying; }
}
public bool playAutomatically
{
get { return animation.playAutomatically; }
set { animation.playAutomatically = value; }
}
public WrapMode wrapMode
{
get { return animation.wrapMode; }
set { animation.wrapMode = value; }
}
public AnimationClip clip
{
get { return animation.clip; }
set { animation.clip = value; }
}
public bool usesLegacy
{
get { return true; }
}
new public GameObject gameObject
{
get { return animation.gameObject; }
}
public void AddClip(AnimationClip clip, string newName)
{
animation.AddClip(clip, newName);
}
public void Blend(string state, float targetWeight, float fadeLength)
{
animation.Blend(state, targetWeight, fadeLength);
}
public void CrossFade(string state, float fadeLength)
{
animation.CrossFade(state, fadeLength);
}
public void CrossFadeQueued(string state, float fadeLength, QueueMode queueMode)
{
animation.CrossFadeQueued(state, fadeLength, queueMode);
}
public int GetClipCount()
{
return animation.GetClipCount();
}
public bool IsPlaying(string stateName)
{
return animation.IsPlaying(stateName);
}
public void Stop()
{
animation.Stop();
}
public void Stop(string stateName)
{
animation.Stop(stateName);
}
public void Sample()
{
animation.Sample();
}
public bool Play()
{
return animation.Play();
}
public bool Play(string stateName)
{
return animation.Play(stateName);
}
public void PlayQueued(string stateName, QueueMode queueMode)
{
animation.PlayQueued(stateName, queueMode);
}
public void RemoveClip(AnimationClip clip)
{
animation.RemoveClip(clip);
}
public void RemoveClip(string stateName)
{
animation.RemoveClip(stateName);
}
public void Rewind()
{
animation.Rewind();
}
public void Rewind(string stateName)
{
animation.Rewind(stateName);
}
public IAnimationState GetState(string stateName)
{
AnimationState state = animation[stateName];
if (state != null)
return new AnimationStateProxy(state);
return null;
}
public IAnimationState this[string name]
{
get { return GetState(name); }
}
}
以上就是代理模式在Unity中的最典型的应用。
动态代理
上面将的是静态代理,有静态就有动态代理,动态代理比较复杂,可以看下面的链接自信学习,下面提供一个简单的动态代理的例子。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49using System;
using System.Reflection;
using System.Reflection.Emit;
namespace EmitDemo
{
public interface IConsole
{
void Say();
}
class Program
{
static void Main(string[] args)
{
//定义一个程序集名称
AssemblyName aName = new AssemblyName("DynamicAssemblyExample");
//创建一个程序集构建器
AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
//使用程序及构建创建一个模块构建器
ModuleBuilder mb = ab.DefineDynamicModule(aName.Name + ".dll");
//使用模块构建起创建一个类型构建器
TypeBuilder tb = mb.DefineType("DynamicConsole");
//使用类型实现IConsole接口
tb.AddInterfaceImplementation(typeof(IConsole));
var attrs = MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.NewSlot | MethodAttributes.HideBySig | MethodAttributes.Final;
// 使用类型构建器创建一个方法构建器
MethodBuilder methodBuilder = tb.DefineMethod("Say", attrs, typeof(void), Type.EmptyTypes);
//通过方法构建起获取一个MSIL生成器
var IL = methodBuilder.GetILGenerator();
//开始编写方法的执行逻辑
//将一个字符串压入栈顶
IL.Emit(OpCodes.Ldstr, "I'm here.");
//调用Console.WriteLine函数
IL.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }));
//退出函数
IL.Emit(OpCodes.Ret);
//方法结束
//从类型构建起中创建出类型
var dymicType = tb.CreateType();
//通过反射创建出动态类型的实例
var console = Activator.CreateInstance(dymicType) as IConsole;
console.Say();
Console.Read();
}
}
}
上述代码会打印输出”I’m here”的字符串。
设计模式系列教程汇总
http://dingxiaowei.cn/tags/设计模式/
更多相关教程
- https://www.cnblogs.com/daniels/p/8242592.html
- https://www.runoob.com/design-pattern/proxy-pattern.html
- http://www.xuanyusong.com/archives/1895
- https://www.runoob.com/design-pattern/proxy-pattern.html
- https://blog.csdn.net/q932104843/article/details/90677245 C#动态代理
- https://www.zhihu.com/question/39850773/answer/896398201