设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)

时间:2022-05-22 06:09:31

我们知道Google早就被墙了,所以FQ才能访问Google呢,这个“FQ”的过程就是一个代理的过程。“代理模式”在之前的博客中不止一次的提及过,之前的委托回调就是代理模式的具体应用。今天我们就从“FQ”中来认识一下代理模式。代理模式的定义如下:

代理模式:为另一个对象提供一个替身或占位符以控制对这个对象的访问。

首先说一下什么是“代理”吧,其实代理很好理解,你就把“代理”看成是二道贩子,说的好听点叫代理商。就是你买个东西,不从生产地直接买,而是通过二道贩子,三道贩子来进行购买,这些商贩就是代理商,也就是我们今天所说的代理。说的具体点,比如你要买棵萝卜,那么一般人不会去找菜农,然后给他们钱直接去地里薅萝卜。大部分人是通过商超来获取萝卜,这些商超就是所谓的萝卜代理商,也就是二道贩子。

那么在说一下什么是“FQ”吧,今天就拿Facebook为例。你是用户,Facebook网站就好比大萝卜,你直接去拔萝卜(直接访问FaceBook站点)不太现实,所以你得通过二道贩子(各种网络代理)来获取你想要的萝卜呢。可是你默认的代理商(GFW--长城防火墙)发现你购买萝卜不太单纯,所以就拒绝进行供货,这就是你访问的网站被墙了。可是你不甘心呢,家里兔子还等着吃萝卜呢。你得寻找新的代理商,此刻你就找到了*这个二道贩子。*没有什么估计的,他可以给你提供大萝卜,这就是FQ。*如何使用在此就不做过多的赘述了,自行Google。

言归正传,上面说这么多无非都是在解释什么是“代理”。今天我们就使用Swift代码来模拟上述的“FQ”过程,通过这个FQ过程来认识一下“保护代理模式”和“远程代理模式”,然后在结合着另一个实例来认识一下“虚拟代理模式”。进入今天的主题。

一、“FQ”的类图设计

其实在“FQ”这件事情上我们不关心如何去FQ,而是关心如何使用代理。在真正网络访问时,无论你是进行FQ访问,还是不FQ访问,都使用的“代理模式”。只不过FQ之前使用的是“保护代理模式”(GFW), 而FQ之后使用的是“远程代理模式”(因为今天的主题是“代理模式”,关于FQ我们先这么理解着,真正的网络代理要比这个复杂的多)。在该部分,基于我们今天这个FQ的场景,然后使用“代理模式”来设计GFW和*两种代理方式。下方就是我们所设计的FQ的类图,稍后会给出具体的代码实现。

在下方类图中绿框的部分是我们要访问的网站,其中有被墙的Facebook和Twitter,也有没被墙的Cnblogs。这三个Web站点都遵循一样的网络访问协议,此处我们定义了InternetAccessProtocol协议(接口)来模拟这些Web站点所遵循的网络协议。在InternetAccessProtocol网络访问协议中的response()方法是用来响应用户网络请求的,getId()方法用来返回该网站的唯一标示,也就是网站的域名。所有的Web站点都必须遵循该网络请求协议。

上面红框中是我们今天的核心部分,也就是网络代理部分。该部分声明了一个协议ProxyType,所有网络代理也都必须遵循该协议。因为网络代理是用来代理网络访问的,它作为用户与Web站点的中转者,对于用户来说该代理就如同真正的网站一样,随意ProxyType协议也必须的遵循上面定义的网络访问协议InternetAccessProtocol。在红框中有两个网络访问的代理,一个是*Proxy,一个是GreatFirewall。

*Proxy是远程代理,你在使用该远程代理时,你访问的网站不受限制,也就是说你可以访问Google、Twitter、Facebook这些网站,远程代理的态度是Open&Freedom的。远程代理只负责桥接,至于你访问的什么网站它不做关心,它只负责响应你的请求。而下方的GreatFirewall就不同了,GreatFirewall是一个保护代理,其中有一个blackList(黑名单),如下所示。blackList数组中记录的就是那些被墙的网站,只要是请求的网站在blackList中,你的请求是不会得到你请求网站的响应的。这也就是保护代理模式的功能,保护代理模式会添加一些权限的限制,会限制用户访问一些不安全的网站。

然后就是黄框中的Client了,在Client中依赖的是代理接口,也就是说Client只能依赖于代理进行网络访问。默认的代理就是GreatFirewall,GreatFirewall会屏蔽一些不能让你访问的网站。如果用户选择*Proxy远程代理进行网络访问,就不受GreatFirewall的限制了,这也就是所谓的FQ。在下方Client类中就有两种上网方式,一种是*一种是GreatFireFirewall。有一点还是需要说明的是,真正的FQ不是不经过GreatFirewall,而是你的代理地址在GreatFirewall的白名单中,就是你可以通过GreatFirewall来访问的你的代理,然后你的代理是不经过GreatFirewall来访问你想要访问的Web站点的。该实例只是我们了解代理模式来模拟出来的实例,我们的重点在代理模式。

设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)

二、代码实现

上述给出了结构的设计,接下来我们就需要进行具体的代码实现了,我们在实现时仍然使用Swift语言。有了上面的类图设计,然后在给出代码实现似乎简单了许多。下方会分部分给出上述类图的代码实现,下方是一些代码的截图,更完整的实例请参见本博客后方github分享地址。

1.网络访问协议与Web站点的实现

下方就是网络访问协议与Web站点的具体实现。在InternetAccessProtocol网络访问协议中的response()方法用来响应用户的请求,getId()方法用来返回网站的域名。在InternetAccessProtocol协议的下方分别实现了三个web站点:Facebook、Twitter、Cnblogs。众所周知,前两个已经被墙了,所以如果你要想访问的话,你得FQ呢。web站点以及网络访问协议代码如下:

设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)

2. 两种代理的实现

首先我们先实现一个比较简单的,也就是远程代理。使用远程代理访问Web站点时没有一些不必要的限制,就是你访问什么网站,该远程代理就会请求什么网站并返回相应的信息。下方代码片段就是代理协议和远程代理*Proxy的具体实现。在ProxyType代理协议中,setDelegate(delegate)方法用来设置代理,也就是用来设置访问的Web站点,ProxyType协议也同样遵循InternetAccessProtocol协议。在*Proxy中的delegate成员变量就是用户要请求的Web站点,你访问的是A站点,那么此处的代理就是A站点的对象。在*Proxy中的response()方法会请求delegate的response()方法,而代理中的getId()方法中则会返回当前远程代理的域名或者IP, 这一点很关键呢。具体代码实现如下:

设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)

接下来我们来实现我们的长城防火墙,也就是GreatFirewall。下方的代码实现就是GreatFirewall的实现,其中比上述的远程代理的实现多了一些东西,其中多了一项权限的控制。当你使用GreatFirewall来访问Web站点时,GreatFirewall首先会判断你所访问的Web站点在不在自己的黑名单中,也就是下方的blackList。如果你访问的Web站点在blackList中,就说明该站点被墙了,GreatFirewall就不会调用该Web站点的response(),所以用户就不会受到该网站的相应。相反,如果不在黑名单中,那么就会设置代理,然后就可以调用该Web站点的response()方法做出相应的响应,这也就是所说的“保护代理模式”。其实说白了,保护代理模式就是在远程代理上添加了一些权限的控制。具体代码实现如下。

设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)

三、Client与测试用例

上面实现完了Web站点,与Web站点的两个访问代理,下方我们就该实现Client用户的代码。然后在此基础上给出测试用例。下方的代码段就是我们的Client的代码实现。在Client中你可选择*也可以选择使用greatFirewall。Client代码如下:

设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)

下方是我们的测试用例,以及该测试用例的输出结果。我们先使用远程代理访问Facebook是可以访问的,因为我们的远程代理没有添加任何限制。如果你通过GFW来访问Facebook,就访问不了,因为Facebook在GFW的黑名单中。但是你通过GFW访问远程代理服务器,然后在通过远程代理服务器去访问FaceBook是可行的。因为我们的远程代理服务器不在GFW的黑名单中,所以我们可以访问远程代理服务器,而我们的远程代理服务器是可以访问Facebook,所以我们可以访问Facebook。这也是测试用例中的第三段。代码以及输出结果如下所示。

设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)

四、虚拟代理

虚拟代理的作用就是为占用存储空间比较大的对象提供替身。当我们实例化大对象需要一定时间时(比如从网请求较大的图片),我们就可以使用虚拟代理提供一个对象的替身,等对象加载完毕后在使用我们这个真正的对象。因为虚拟代理较为简单,在此就不给出类图了,就直接给出代码实现吧。其实虚拟代理说白了也是代理模式,就是在大对象的使用者与大对象间添加了一层,这一层就是虚拟代理。

下方我们就以加载大图片为例,当我们加载比较大的图片时,为了不让用户等待,我可以先通过虚拟代理模式添加一个小的图片。然后在虚拟代理中将大图片加载完毕后我们在换回大的图片即可。下方我们创建了一个图片协议ImageType,然后创建了一个大的图片BigImage和一个小的图片SmallImage。SmallImage就作为BigImage未加载时的替身,当SmallImage加载完毕后我们就不使用SmallImage,而使用BigImage。而这一系列的替换的过程交给我们的虚拟代理来处理。

下方代码段中的BigImageProxy就是我们的虚拟代理。BigImageProxy也遵循于ImageType协议,对于用户来说,BigImageProxy的用法与普通的图片是一样的。在BigImageProxy中的loadImage()方法是我们虚拟代理的核心。在调用虚拟代理中的loadImage()方法时,如果BigImage已被实例化,就会调用loadImage()方法,如果BigImage没有实例化,那么就会调用SmallImage中的loadImage()方法,并且对BigImage进行实例化,并将虚拟代理中bigImage的状态设置为正在初始化状态。具体实现代码如下所示:

设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)

下方代码段就是上述虚拟代码的测试用例。ImageClient依赖的是ImageType协议,所以其中的image成员变量可以是真正的图片,也可以是我们的虚拟代理。虚拟代理对象的使用方式与普通图片的使用方式一致,测试用例如下所示:

设计模式(十三):从“FQ”中来认识代理模式(Proxy Pattern)

今天我们的博客中就介绍了三种代理模式:远程代理模式、保护代理模式、虚拟代理模式。远程代理访问是控制访问远程对象,保护代理是基于权限的资源访问,虚拟代理是控制访问创建开销大的资源。

上述代码示例仍然会在github上进行分享,分享地址为:https://github.com/lizelu/DesignPatterns-Swift