代理模式(Proxy)
为了深刻点理解代理模式,我们先来看一个 Demo ,
首先这个 Demo 是用来测试 QQ 号码是否在线,
这里涉及到的内容是 Web 服务的使用,
这个 Web 服务所在地址为:
http://webservice.webxml.com.cn/webservices/qqOnlineWebService.asmx
如果有不懂 Web 服务的,还可以查看笔者一篇涉及 Web 服务的文章,
http://www.cnblogs.com/QinBaoBei/archive/2010/03/30/1700898.html
然后,我便将上面的验证 QQ 是否在线的这个 Web 服务引入到我的项目中,
并且给这个 Web 服务命名为 WebService.TestQQ
然后就是来编写类了,
主要是一个 TestResult 类,这个类的作用就是来完成访问 Web 服务,返回验证结果,
其具体代码如下:
namespace ProxyQQ
{
public static class TestResult
{
public static string GetResult(string qqNum)
{
//调用引用的 Web 服务(自命名为 WebService.TestQQ)
WebService.TestQQ.qqOnlineWebService testQQ =
new WebService.TestQQ.qqOnlineWebService();
return testQQ.qqCheckOnline(qqNum);
}
}
}
然后就是要编写客户端了,我这里使用的是一个 WinForm 应用程序来实现的,
这个项目中唯一一个窗体 TestQQ 的代码如下
using System;
using System.Windows.Forms;
namespace ProxyQQTest
{
public partial class TestQQ : Form
{
public TestQQ()
{
InitializeComponent();
}
private void btnQuery_Click(object sender, EventArgs e)
{
//调用 ProxyQQ 命名空间下的静态类 TestResult 的
//静态方法 GetResult()来判断 QQ 状态
string result =
ProxyQQ.TestResult.GetResult(txtQQNum.Text.Trim());
string msg = String.Empty;
switch (result)
{
case "Y":
msg = "该 QQ 在线";
break;
case "N":
msg = "该 QQ 离线";
break;
case "E":
msg = "该 QQ 号码不存在";
break;
case "A":
msg = "商业用户验证失败";
break;
case "V":
msg = "免费用户超过数量";
break;
default:
break;
}
MessageBox.Show(msg, "提示",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
}
下面就来查询一下 QQ 号码咯
然后我让我的 QQ 上线再来查看
好,说了这么多了,感觉是在介绍 Web 服务似的,
但是请不要误会,本篇博文是来介绍代理模式的,而非 Web 服务,
下面我们就从上面的这个 Demo 来谈谈代理模式,
其实上面谈到的 Demo 就是代理模式的一种最典型的应用--远程代理,
先来看上面的 Demo 中,我添加的那个 Web 服务,
当我在我的项目中添加了 Web 服务后呢,
大家可以看到会在项目中自动生成一个 WebReference 的文件夹以及一些文件,
其实这些就是代理了,
您看我在应用程序中只是使用了下面一丁点代码,
//调用引用的 Web 服务(自命名为 WebService.TestQQ)
WebService.TestQQ.qqOnlineWebService testQQ =
new WebService.TestQQ.qqOnlineWebService();
return testQQ.qqCheckOnline(qqNum);
我们在客户端只需要通过这一丁点代码就可以完成访问 Web 服务并且返回结果,
也可以说成是客户端通过调用代理来完成远程访问并返回结果。
下面就来看看什么是远程代理?
远程代理可以看做另一个服务器上的对象在本地服务器上的代表,
调用代理的方法就会被代理通过网络来转发到远程执行,并且结果会通过网络返回给代理,
然后再由代理将结果转发给客户。
其实上面对远程代理的定义还是不明确的,就拿 Web 服务来说的话,您也可以调用你自己的项目中的 Web 服务的,
所以完整的远程代理的定义是,为一个不同地址空间中的对象提供一个局部代表对象,
不过这个不同的地址空间呢可以在您的本地机也可以在远程机器上,
所以在上面的 Demo 中,
就可以把 WebService.TestQQ.qqOnlineWebService (代理对象)看做是
验证 QQ 号码这个对象(这里看做一个对象,它是被代理的对象)在本地机器(也就是我的项目中)上的代理对象,
而后,我在客户端中便只需要对这个代理对象进行访问和操作就 OK 了,其他的内容就交给代理对象来完成了,
这是典型的代理模式。
上面呢,介绍的只是代理模式的一种最典型的应用,也就是远程代理,而下面就要来介绍代理模式的一些具体内容了,
什么是代理模式?
代理模式为另一个对象提供一种代理(换句话说就是替身)以控制对这个对象的访问,使用代理模式创建代理对象,
让这个代理对象控制某个对象的访问。
下面就来看看代理模式的结构类图
从上面的结构类图可以看出,代理类和具体主题类都继承子抽象主题类,
何为要这样实现继承呢?
很简单的,由于 Proxy 和 RealSubject 均继承自 Subject 这个抽象主题类,
并且在 Proxy 和 RealSubject 中均实现了 Request 方法,
所以在需要使用 RealSubject 的地方便可以使用 Proxy 来替代
(换句话说的话,可以认为 Proxy 和 RealSubject 是同一类型)
由于使用了 Proxy 来替代 RealSubject ,所以便实现了通过 Proxy 来代理 RealSubject ,
同时在 Proxy 这个代理类中保存和维护了一个 RealSubject 对象,
当然就在一定程度上实现了通过 Proxy 来控制 RealSubject 。
下面还是先来看看这个代理模式的最基本的结构实现吧
Subject 类
namespace Proxy
{
public abstract class Subject
{
public abstract void Request();
}
}
Proxy 类
using System;
namespace Proxy
{
public class Proxy:Subject
{
private RealSubject realSubject;
public override void Request()
{
Console.WriteLine(this.GetType().ToString());
if (realSubject == null)
{
realSubject = new RealSubject();
}
realSubject.Request();
}
}
}
RealSubject 类
using System;
namespace Proxy
{
public class RealSubject:Subject
{
public override void Request()
{
Console.WriteLine(this.GetType().ToString());
}
}
}
客户端代码
using System;
namespace ProxyTest
{
class Program
{
static void Main(string[] args)
{
Proxy.Subject proxy = new Proxy.Proxy();
proxy.Request();
Console.ReadKey();
}
}
}
光看上面的代码和效果,我想大家很难明白是怎么一回事,那么还请看下面的解释,然后再回过头结合代码来看,
那么大家应该就可以明白代理模式到底是咋一回事了,
首先,可以看到 Proxy 和 RealSubject 这两个类都继承自抽象类 Subject ,
同时均实现了在抽象类 Subject 类中定义的 Request 方法,
这样的话,在某一层意义上来说,Proxy 就是 RealSubject 的替身了,
因为他们的抽象类型都是一样的,所以,便可以在 RealSubject 出现的地方使用 Proxy 来替代了,
比如在上面的例子中,我本来的意义是要在客户端中调用 RealSubject 的 Request 方法的,
所以本来在客户端代码中应该如下写的:
Proxy.Subject realSubject= new Proxy.RealSubject();realSubject.Request();
而事实上并非这样,我是通过实例化一个 Proxy 类来完成操作的,因为我在 Proxy 中保存了一个 RealSubject 对象,
并且在 Proxy 类的 Request 方法中调用了 RealSubject 的 Request 方法,
所以便是代理实现了调用 RealSubject 的 Request 方法,
在这里,我想就有疑问了,那就是我为什么一定要在客户端来通过 Proxy 代理完成这个操作,
而不是直接实例化 RealSubject 对象来完成 Request 操作了,
其实,这里涉及到的便是代理模式的具体应用了,前面看了一种应用也就是远程代理,
下面我们来看另外一种代理模式的应用,也就是虚拟代理,
比如,我要加载一个很大的视频文件或者是图片文件(比如 800 像素拍下的照片 2M),
此时由于网络速度慢的缘故,在加载的时候费个什么几秒钟那是非常常见的,但是就是在这几秒钟中,
难道您就让用户干巴巴的等着吗?要我是用户的话,看着半天没反应的浏览器或者是窗体,
我想,我还是关闭算了吧 ! ! !
那么我们有什么办法来解决上面的问题呢?
其实有一种比较好的办法,就是当图片在下载时,可以发布一个提示消息,比如“正在下载中,请等待”,
做的好一点的话,还可以显示一张本地的小图片,这样会更炫,
然后等下载完成后便将这些提示消息去掉,然后显示图片就 OK 了,这样可以极大的提高用户体验,
其实这种效果在很多地方都常见了,而这就是通过虚拟代理来解决的,
我们先来看一下腾讯主页上这种效果吧
下面就来看如何通过虚拟代理来完成上面的这种效果
我们就来看类了,
首先是 Subject 这个抽象类,在这里是 LoadImage 来充当
namespace ProxyVirtual
{
public abstract class LoadImage
{
public abstract void LoadImageRequest();
}
}
然后是 Proxy 这个代理类,这里由 ProxyLoadImage 来充当,
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
namespace ProxyVirtual
{
public class ProxyLoadImage : LoadImage
{
//这里要保存一个具体主题类的引用
//这样才能够实现控制具体主题了 RealLoadImage
private RealLoadImage realLoadImage;
private PictureBox pcBox;
public ProxyLoadImage(PictureBox pcBox, RealLoadImage realLoadImage)
{
this.pcBox = pcBox;
this.realLoadImage = realLoadImage;
}
public override void LoadImageRequest()
{
//首先是要实例化一个线程
Thread thread = new Thread(ThreadMethod);
//然后就是要启动这个线程了
thread.Start();
}
private void ThreadMethod()
{
//将整个提示用户还在下载的图片(小图片)
//和正在下载的图片(大图片)放在同一个线程中
//可以防止其他线程对整个应用程序进行干扰
pcBox.Image = Image.FromFile("SquareCircle.gif");
realLoadImage.LoadImageRequest();
}
}
}
还有一个就是具体主题类 RealLoadImage 类了
using System.Windows.Forms;
using System.Drawing;
using System.Net;
namespace ProxyVirtual
{
public class RealLoadImage : LoadImage
{
private string strUrl;
private PictureBox pcBox;
public RealLoadImage(string strUrl, PictureBox pcBox)
{
this.strUrl = strUrl;
this.pcBox = pcBox;
}
public override void LoadImageRequest()
{
//发出对指定 URL 路径的请求(这里是请求一张图片)
WebRequest request = WebRequest.Create(strUrl);
//返回对请求的响应结果
WebResponse response = request.GetResponse();
//从网络资源中返回数据流,然后给图片框显示
pcBox.Image = Image.FromStream(response.GetResponseStream());
}
}
}
然后就是客户端代码了(这里是一个 Form 的 Code-Behind)
using System;
using System.Windows.Forms;
namespace ProxyVirtualTest
{
public partial class Test : Form
{
public Test()
{
InitializeComponent();
}
private void Test_Load(object sender, EventArgs e)
{
ProxyVirtual.LoadImage loadImage =
new ProxyVirtual.ProxyLoadImage(this.pcBox,
new ProxyVirtual.RealLoadImage(
"http://images.cnblogs.com/cnblogs_com/qinbaobei/6.jpg",
this.pcBox));
//调用代理类的 LoadImageRequest
loadImage.LoadImageRequest();
}
}
}
下面也把效果给出来,刚开始加载的时候效果是
当下载完成以后,便会出现
上面的这张图片呢,是我的博客中的一张图片,我通过 URL 来下载到我的应用程序当中。
下面我再给出上面这个 Demo 的类图,大家可以拿这个类图和最开始的代理模式的结构类图比较比较,
我们不谈别的内容,光从上面的这幅类图中便可以看出上面的 Demo 使用的就是代理模式,
下面我们还再来具体的谈一下何为虚拟代理?
虚拟代理是作为创建大的对象的代理类,一般来说,创建一个对象需要消耗比较大的资源时,
可以通过虚拟代理来实现这个对象只有在需要的时候才会被真正创建,
可以通过使用代理(这个代理不怎么耗资源)来在必要的时候将被代理的对象加载,
也就是通过不怎么耗资源的代理来控制什么时候需要加载比较耗资源的被代理对象,
在上面的 Demo 中,大的图片就是比较耗资源的,所以通过一个不怎么耗资源的小图片先来代理它,
在真正需要这个大图片的时候再通过代理来加载。
再看一个虚拟代理的应用,那就是当您打开一个有很多图片的网页时,
一般来说,文字是全部可以看到的(会在加载网页的时候全部下载过来),
而图片却必须要一张一张的下载才能看得
(比如您将滑轮往下滚会看到一些图片并没有显示出来,只是在那里留出一个方框,
而真实的图片就需要等待才能显示),
那些未打开的图片框就是通过虚拟代理来替代了真实的图片,
此时,代理存储了真实图片的路径和大小,所以在也没上才会出现图片的方框,而并没有真实的图像。
在上面浏览器中使用代理模式的好处是什么呢?很明显,通过使用虚拟代理优化了下载。
上面呢,重点介绍了代理模式的两种应用,即远程代理和虚拟代理,其实呢,代理模式还有很多变种的
既然是变种的话,基本上就差不太多远啦,
比如安全代理,智能引用代理,缓存代理,写入时复制代理,防火墙代理等等,
在这里就不做一一介绍了,如果有想了解的,可以 Google 或者是 Bing 一下,就会出来一大堆了 ! ! !
还有就是 TerryLee 的博客上也有一点点介绍的,感兴趣的可以去看看 ! ! !