代理模式 并不是日常开发工作中常常用到的一种设计模式,也是一种不易被理解的一种设计模式。但是它会广泛的应用在系统框架、业务框架中。

定义

它的 定义 就如其它同大部分 设计模式 的定义类似,即不通俗也不易懂,而且随便百度一下就能找到 : 为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。

每个字都认识,连在一起就看不懂了 by. 某个攻城狮

我们一个词一个词看就明白了。

其他对象

所谓的 其它,其实就是你系统中 任意 一个类型,可以是 UserServiceOrderRepositoryDataDeletedEventListener、等等。

控制对这个对象的访问

访问 其实就是调用这个对象上的方法、访问它的属性、设置它的属性等等,比如

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 来丰富代理类功能。

但缺点是,当你无法为所有的类型都创建 ProxyInterruption

动态代理类

之前的方法中,我们在编写阶段就已经建立了代理类,被称为静态代理类。

这种方法无法将代理类运用在系统中任何一个我们可能需要的类型上。

于是,动态代理类 就来了。

动态代理类 依靠编程语言本身的特征,让程序在 运行时 创建类型,实现接口,添加功能等等。

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 有两个步骤:

  1. 编写 Interceptor
  2. 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 的细节,会在以后的文章中进一步介绍。


相关链接

代理模式是什么?如何在 C# 中实现代理模式的更多相关文章

  1. 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 用户 ...

  2. Oracle 12c 如何在 PDB 中添加 SCOTT 模式(手工方式)

    Oracle 12c 建库后,没有 scott 模式,本篇使用手工脚本方式,在12c版本中创建 scott 模式及相关表. 目录 1. PDB中创建用户 2. PDB中用户授权 3. PDB中创建表空 ...

  3. iOS开发之swift与OC混编出现的坑,oc中不能对swift的代理进行调用,不能访问swift中的代理,swift中的回调方法

    1. Swift与oc混编译具体怎么实现,这儿我就不重复讲出了,网上有大把的人讲解. 2. 在swift与OC混编的编译环境下, oc类不能访问swift创建类中的代理? 解决方法如下: 在代理的头部 ...

  4. 面试官:你说你懂动态代理,那你知道为什么JDK中的代理类都要继承Proxy吗?

    之前我已经写过了关于动态代理的两篇文章,本来以为这块应该没啥问题,没想到今天又被难住了- 太难了!!! 之前文章的链接: 动态代理学习(一)自己动手模拟JDK动态代理. 动态代理学习(二)JDK动态代 ...

  5. 如何在ThinkPHP中开启调试模式

    1.为什么使用调试模式? 因为调试会在我们的模板页的最后增加一些trace信息. 2.什么是trace信息? 是ThinkPHP为我们提供好的一些包含了系统运行时间.占用内存.加载时间.请求的协议.. ...

  6. Oracle 12c 如何在 PDB 中添加 SCOTT 模式(数据泵方式)

    Oracle 12c 建库后,没有 scott 模式,本篇使用数据泵方式,在12c版本之前数据库中 expdp 导出 scott 模式,并连接 12c 的 pdb 进行 impdp 导入. 目录 1. ...

  7. 如何在IE11中设置兼容模式?设置的具体方法

    IE11浏览器软件版本:简体中文正式版 For Win7网络工具立即查看 1.同样进入需要兼容性模式的网站,点击菜单栏位工具--F12开发者人员工具!如下图所示. 2.在开发者选项左下侧菜单栏位,点击 ...

  8. JAVA中的代理技术(静态代理和动态代理)

    最近看书,有两个地方提到了动态代理,一是在Head First中的代理模式,二是Spring AOP中的AOP.所以有必要补充一下动态代理的相关知识. Spring采用JDK动态代理和CGLib动态代 ...

  9. iOS开发之OC与swift开发混编教程,代理的相互调用,block的实现。OC调用Swift中的代理, OC调用Swift中的Block 闭包

    本文章将从两个方向分别介绍 OC 与 swift 混编 1. 第一个方向从 swift工程 中引入 oc类 1. 1 如何在swift的类中使用oc类    1.2  如何在swift中实现oc的代理 ...

随机推荐

  1. 蓝桥杯——一步之遥,扩展gcd的应用

    1. 一步之遥 [问题描述]从昏迷中醒来,小明发现自己被关在X星球的废矿车里.矿车停在平直的废弃的轨道上.他的面前是两个按钮,分别写着“F”和“B”. 小明突然记起来,这两个按钮可以控制矿车在轨道上前 ...

  2. 主从校验工具pt-table-checksum和pt-table-sync工作原理

    pt-table-checksum和pt-table-sync是常用来做MySQL主从数据一致性校验的工具,pt-table-checksum只校验数据,不能对数据进行同步:pt-table-sync ...

  3. postman设置全局变量及参数化

    笔者第一次记录使用过程,仅供参考 测试过程中接口的前缀都是一样的,所以我们可以将这个前缀作为全局变量来使用 首先,打开postman点击这里的小齿轮设置 在这里就可以进行变量的一个添加,添加好之后记住 ...

  4. linux 中的页缓存和文件 IO

    本文所述是针对 linux 引入了虚拟内存管理机制以后所涉及的知识点.linux 中页缓存的本质就是对于磁盘中的部分数据在内存中保留一定的副本,使得应用程序能够快速的读取到磁盘中相应的数据,并实现不同 ...

  5. java 中的字符串处理--正则表达式

    最近在做一些支付报文处理工作,需要从各种各样的报文中提取需要的信息比如(金额,订单号...),每个渠道报文各式各样,想要写一个通用的提取逻辑,于是就回顾java正则表达式的用法.当然我们可以自己写一些 ...

  6. 403 Invalid CORS request 跨域问题解决

    这里使用springMVC自带的CORS解决跨域问题 什么是跨域问题 1.请求地址与当前地址不相同 2.端口号不相同 技术有限端口号不同还未发现 3.二级域名不相同 出现这种问题如何解决有很多种方法, ...

  7. vue3.0+axios 跨域+封装

    封装: 目录结构:src/utils/request.js, 没有就自己建一个 //src/utils/request.jsimport axios from 'axios' import { Mes ...

  8. POI2014 FAR-FarmCraft 树形DP+贪心

    题目链接 https://www.luogu.org/problem/P3574 题意 翻译其实已经很明确了 分析 这题一眼就是贪心啊,但贪心的方法要思索一下,首先是考虑先走时间多的子树,但不太现实, ...

  9. P1130 红牌(动态规划)

    P1130 红牌 思路如下 这一题很像数字金字塔,我们可以正着求最小时间,当然也可以逆着求最小时间, 如果正着求:那么我们怎么求状态转移方程呢?,在这里我们假定状态转移方程为:dp[ i ][ j ] ...

  10. Flutter 实现虎牙/斗鱼 弹幕效果

    老孟导读:用Flutter实现弹幕功能,轻松实现虎牙.斗鱼的弹幕效果. 先来一张效果图: 实现原理 弹幕的实现原理非常简单,即将一条弹幕从左侧平移到右侧,当然我们要计算弹幕垂直方向上的偏移,不然所有的 ...