代理模式是什么?如何在 C# 中实现代理模式
代理模式 并不是日常开发工作中常常用到的一种设计模式,也是一种不易被理解的一种设计模式。但是它会广泛的应用在系统框架、业务框架中。
定义
它的 定义 就如其它同大部分 设计模式 的定义类似,即不通俗也不易懂,而且随便百度一下就能找到 : 为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。
每个字都认识,连在一起就看不懂了 by. 某个攻城狮
我们一个词一个词看就明白了。
其他对象
所谓的 其它,其实就是你系统中 任意 一个类型,可以是 UserService、OrderRepository、DataDeletedEventListener、等等。
控制对这个对象的访问
访问 其实就是调用这个对象上的方法、访问它的属性、设置它的属性等等,比如
User user = UserService.GetUserById(1); // 访问了 GetUserById 方法
int id = user.Id; // 访问了 Id 属性
Order order = OrderRepository.SelectByUserId(id); // 访问了 SelectByUserId 方法
控制访问 ,控制 的本质是包装,外部不再直接使用 其他对象 ,而是使用 代理 ,再由代理来访问 其它对象。我们可以使用一个已有的 List<T> 实现一个 IList<T> ,并在使用 Add 方法时,打印一行日志。
public class LogList<T> : IList<T>
{
// other code here..
private IList<T> raw; // 这个就 "其它对象"
public EventList(IList<T> raw)
{
this.raw = raw; // 通过构造函数,这可以让 EventList 控制对 IList<T> 的访问。
}
public void Add(T value)
{
this.raw.Add(value);
Console.WriteLine(value);
}
}
上面就是一个简单的代理模式的例子:
为 IList<T> 提供一种 LogList<T> ,以控制对 IList<T> 的访问。
实现
简单实现
上面 LogList<T> 就是一种简单的实现。
但是你无法对这个类做外部扩展,所有的逻辑都在类型的内部被固定了。
于是我们可以使用下面的方法创建一个可以灵活扩展的 ListProxy<T>
public interface IListInterruption<T>
{
// other codes
// 执行 IList.Add 时会进入的方法
void OnAdding(IList<T> list, T addingValue);
// 执行完 IList.Add 时会进入的方法
void OnAdded(IList<T> list, T addedValue);
// other codes
}
// 列表代理类
// 允许外部提供 IListInterruption 来丰富 ListProxy<T> 的逻辑。
public class ListProxy<T> : IList<T>
{
private readonly IList<T> raw;
private readonly List<IListInterruption> interruptions;
public ListProxy(IList<T> raw)
{
this.interruptions = new List<IListInterruption>();
this.raw = raw;
}
public void AddInterruption(IListInterruption interruption)
{
this.interruptions.Add(interruption);
}
public void Add(T value)
{
foreach(var item in this.interruptions)
item.OnAdding(this.raw, value);
this.raw.Add(value);
foreach(var item in this.interruptions)
item.OnAdded(this.raw, value);
}
}
上面的代码实现一个较为灵活的 ProxyList<T> 。
首先看看 IListInterruption。通过实现 IListInterruption 接口,可以向 ProxyList 提供各种各样的功能。
我们可以看一个简单的功能
public class LogListInterruption<T> : IListInterruption<T>
{
// other codes
public void OnAdding(IList<T> list, T addingValue)
{
Console.WriteLie("Adding : {0}", addingValue);
}
// other codes
}
向 ProxyList 添加上述组件,就可以实现在 Add 前打印待添加的值的功能。
List<int> myList = new List<int>();
ProxyList<int> proxy = new ProxyList<int>(myList);
proxy.AddInterruption(new LogListInterruption<int>());
proxy.Add(1);
// >> Adding : 1
这种实现方式可以创建出针对某个类型的代理类,并通过外部给予的 IListInterruption 来丰富代理类功能。
但缺点是,当你无法为所有的类型都创建 Proxy 和 Interruption 。
动态代理类
之前的方法中,我们在编写阶段就已经建立了代理类,被称为静态代理类。
这种方法无法将代理类运用在系统中任何一个我们可能需要的类型上。
于是,动态代理类 就来了。
动态代理类 依靠编程语言本身的特征,让程序在 运行时 创建类型,实现接口,添加功能等等。
在 C# 中可以通过两种方式实现运行时创建类型的功能
- CodeDom + 运行时编译
- Emit
CodeDom 可以用来生成 C# 代码,再利运行时编译,会将C#代码编译为内存中的程序集,最后通过反射访问程序集中的成员。
这种方法的特点就是慢。。。。因为要生成语句,还要编译,生成程序集,最后还要反射,都是大开销,所以慢是可想而知的。
Emit 提供了利用 IL 命令在运行时创建类型、方法,并填充方法内的功能。
毕竟 C# 最终就是编译成 IL 的,所以直接使用 IL 性能当然快无敌了。
这个方式的缺点只有一个 : 学习 IL 。这玩意可不是每个人都愿意去学的。。。
于是,选择一些已经利用 Emit 做好了动态代理类功能的第三方功能库,成为了一个很好的选择。
C# 大环境下,可以用来生成动态代理类的库一般有两个选择 :
- PostSharp
- Caslte.DynamicProxy
其中 PostSharp 使用了更复杂的技术,不是使用 Emit,而且在编译时,就将代理类所附加的功能放进了目标类型中,你可以通过 Attribute 向任意的方法添加额外的功能。
PostSharp 会在程序编译时,把这些 Attribute 的功能直接编译到方法内部。
这种在编译时就把一切准备好的方法,让 PostSharp 有着最高的运行性能。
但是又傻瓜、又好用的 PostSharp 只有一个缺点 ———— 收费。
Castle.DynamicProxy 是免费的,他是利用 Emit 在程序运行时创建代理类的。
使用 Castle.DynamicProxy 有两个步骤:
- 编写 Interceptor
- 将 Interceptor 附加到某个类型或接口上,并得到一个会调用你的 Interceptor 的代理类实例
开发流程很像之前的 LogList 的例子。
相比较 PostSharp 那种一个 Attribute 就搞定一切的模式, Caslte.DynamicProxy 就没有那么方便了。
那么一个显而易见的问题就来了 :
能不能利用 Caslte.DynamicProxy 实现像 PostSharp 那样利用 Attribute 创建代理类的功能呢?
Reface.AppStarter.Proxy
这是基于 Reface.AppStarter 开发的一个功能模块,
使用它,可以利用 Attribute 的方式轻松的创建代理类,并实现 AOP 的功能。
你所要做的,就是创建一个继承于 ProxyAttribute 的特征。
ProxyAttribute 中有三个方法需要重写
- OnExecuting ,被标记的方法执行时
- OnExecuted ,被标记的方法执行后
- OnExecuteError , 被标记的方法执行出现异常后
你可以编写你的逻辑在这三个方法内,并将你的 Attribute 挂载到你需要的类型的方法上即可。
剩下的事情只有两件
- 向你的 AppModule 添加 ProxyAppModule
- 为你需要创建代理的类型加上 [Component] 特征
你已经完成了所有工作,
当你利用 Reface.AppStarter 的框架的 IOC / DI 容器创建你的类型时,实际得到的就是代理类,这些代理类会调试你给予的 ProxyAttribute 。
关于 Reface.AppStarter.Proxy 的细节,会在以后的文章中进一步介绍。
相关链接
- Reface.AppStarter.Proxy @ Nuget
- Reface.AppStarter.Proxy @ Github
- Reface.AppStarter.Proxy @ Gitee
- Reface.AppStarter 框架初探
代理模式是什么?如何在 C# 中实现代理模式的更多相关文章
- Oracle 12c 多租户家族(12c 18c 19c)如何在 PDB 中添加 HR 模式
Oracle 12c 多租户家族(12c [12.2.0.1].18c [12.2.0.2].19c [12.2.0.3])如何在 PDB 中添加模式:19c (19.3) 手工添加示例 HR 用户 ...
- Oracle 12c 如何在 PDB 中添加 SCOTT 模式(手工方式)
Oracle 12c 建库后,没有 scott 模式,本篇使用手工脚本方式,在12c版本中创建 scott 模式及相关表. 目录 1. PDB中创建用户 2. PDB中用户授权 3. PDB中创建表空 ...
- iOS开发之swift与OC混编出现的坑,oc中不能对swift的代理进行调用,不能访问swift中的代理,swift中的回调方法
1. Swift与oc混编译具体怎么实现,这儿我就不重复讲出了,网上有大把的人讲解. 2. 在swift与OC混编的编译环境下, oc类不能访问swift创建类中的代理? 解决方法如下: 在代理的头部 ...
- 面试官:你说你懂动态代理,那你知道为什么JDK中的代理类都要继承Proxy吗?
之前我已经写过了关于动态代理的两篇文章,本来以为这块应该没啥问题,没想到今天又被难住了- 太难了!!! 之前文章的链接: 动态代理学习(一)自己动手模拟JDK动态代理. 动态代理学习(二)JDK动态代 ...
- 如何在ThinkPHP中开启调试模式
1.为什么使用调试模式? 因为调试会在我们的模板页的最后增加一些trace信息. 2.什么是trace信息? 是ThinkPHP为我们提供好的一些包含了系统运行时间.占用内存.加载时间.请求的协议.. ...
- Oracle 12c 如何在 PDB 中添加 SCOTT 模式(数据泵方式)
Oracle 12c 建库后,没有 scott 模式,本篇使用数据泵方式,在12c版本之前数据库中 expdp 导出 scott 模式,并连接 12c 的 pdb 进行 impdp 导入. 目录 1. ...
- 如何在IE11中设置兼容模式?设置的具体方法
IE11浏览器软件版本:简体中文正式版 For Win7网络工具立即查看 1.同样进入需要兼容性模式的网站,点击菜单栏位工具--F12开发者人员工具!如下图所示. 2.在开发者选项左下侧菜单栏位,点击 ...
- JAVA中的代理技术(静态代理和动态代理)
最近看书,有两个地方提到了动态代理,一是在Head First中的代理模式,二是Spring AOP中的AOP.所以有必要补充一下动态代理的相关知识. Spring采用JDK动态代理和CGLib动态代 ...
- iOS开发之OC与swift开发混编教程,代理的相互调用,block的实现。OC调用Swift中的代理, OC调用Swift中的Block 闭包
本文章将从两个方向分别介绍 OC 与 swift 混编 1. 第一个方向从 swift工程 中引入 oc类 1. 1 如何在swift的类中使用oc类 1.2 如何在swift中实现oc的代理 ...
随机推荐
- mysql两表合并,对一列数据进行处理
加班一时爽,一直加班~一直爽~ 欢迎收看http://www.996.icu/ 今天弄了下MySQL中两表合并的并且要处理一列数据,这列数据原来都是小写字母,处理时将这列数据改成驼峰命名的~~ 基本 ...
- Javascript之封装运动函数
@ 目录 阶段一.仅适用单位带px属性的匀速运动 阶段二.可适用单位不带px属性(如opacity)的匀速运动 阶段三.适用于多元素单一属性的匀速运动 阶段四.适用于多元素单一属性的匀速或缓冲运动 阶 ...
- 医学图像 | 使用深度学习实现乳腺癌分类(附python演练)
乳腺癌是全球第二常见的女性癌症.2012年,它占所有新癌症病例的12%,占所有女性癌症病例的25%. 当乳腺细胞生长失控时,乳腺癌就开始了.这些细胞通常形成一个肿瘤,通常可以在x光片上直接看到或感觉到 ...
- TensorFlow系列专题(三):深度学习简介
一.深度学习的发展历程 深度学习的起源阶段 深度学习的发展阶段 深度学习的爆发阶段 二.深度学习的应用 自然语言处理 语音识别与合成 图像领域 三.参考文献 一.深度学习的发展历程 作为机器学习最 ...
- 机器学习算法系列:FM分解机
在线性回归中,是假设每个特征之间独立的,也即是线性回归模型是无法捕获特征之间的关系.为了捕捉特征之间的关系,便有了FM分解机的出现了.FM分解机是在线性回归的基础上加上了交叉特征,通过学习交叉特征的权 ...
- JVM 常见参数配置
-XX:+PrintGC 每次触发GC的时候打印相关日志 -XX:+PrintGCDetails 每次触发GC的时候更详细的相关日志 -XX:+UseSerialGC 串行回收 -Xms 堆初始值( ...
- mongodb服务器启动
以配置文件启动服务器: mongod --config /usr/local/mongodata/config/mongodb.conf(配置文件路径) 客户端启动: mango 关闭mongodb的 ...
- Codeforces 1322C - Instant Noodles(数学)
题目链接 题意 给出一个二分图, 两边各 n 个点, 共 m 条边, n, m ≤ 5e5. 右边的点具有权值 \(c_i\), 对于一个只包含左边的点的点集 S, 定义 N(S) 为所有与这个点集相 ...
- LLVM 编码规范 - 中文翻译
LLVM 编码规范 导论 语言.库和标准 C++ 标准版本 C++ 标准库 Go 代码准则 机械的代码问题 代码格式化 注释 头文件 类概述 method information 注释格式化 使用Do ...
- [vijos1162]波浪数
题目链接:https://www.vijos.org/p/1162 这题的解法我觉得可能是模拟吧,但是题的分类又是构造QAQ..... 不是很懂,所以我们把这个方法叫做奇技淫巧吧 这题的暴力思路就是针 ...