结构型模式——代理(Proxy)
问题背景
当需要监控外界对对象的访问时,考虑使用代理。举个大家都干过的例子哈,在咱这一代小时候,互联网这东西还没这么牛X。你要约女朋友出来,却没有QQ微信电话,只能硬着头皮去她家找。然而开门的不是你女朋友,而是你老丈母娘。老丈母娘问你,叫她宝贝女儿出去干嘛。这时候,要是你说:我们约好要一起写作业,那老丈母娘肯定开开心心地把女儿交给你;但你要是说:我们约好要去开房233……
下面,我们就用面向对象程序模拟以上场景,来介绍代理。
解决方案
首先,在上面的场景中,你扮演的就是“用户”,女朋友是被调用的“真实对象”。但是,对于你一个外部人员来说,系统(女朋友家)不允许你直接访问真实对象,想访问真实对象,必须通过代理(老丈母娘)。于是,我们得到了如下的类图:
Date方法是约会,Boy要约Girl,只能调用MotherInLaw的Date方法。传入附加参数,MotherInLaw根据参数决定是否允许Boy访问Girl。同时,MotherInLaw还可以通过“引用计数”来观察自己的女儿在外边搞了多少个男人…这样,对于系统内部来说,就达到了监控访问的目的。
效果
- 为真实对象的访问提供了间接性,这种间接性可以实现如安全、日志、延迟加载、引用计数等附加功能。
缺陷
在上面的类图中,我们不难发现,代理模式是面向实现编程而非面向接口编程,这使程序的可扩展性降低。
为了解决这一缺陷,可以再增加两个接口IProxy和IReal分别表示代理和真实对象的抽象。这样一来,就变成了面向接口编程。然而问题又来了,具体的Proxy和具体的Real是绑定的,也就是说每当扩展Real时,就要同时扩展Proxy,这又容易造成类爆炸。
实际上,受限于传统OO的静态性质,纯OO是无法解决类爆炸问题的。在第二期中会介绍混合了反射机制的“动态代理”,这种代理能完美解决类爆炸问题。
相关模式
- 适配器:适配器和代理易混淆,适配器的作用是转换接口,代理的作用是监控访问。识别方式就是看目标对象的接口是否发生了变化。
- 装饰器:装饰器和代理易混淆,装饰器的作用是动态增强,代理的作用是监控访问。识别方式就是看用户是否能直接访问真实对象。
实现
using System;
namespace Proxy
{
class Boy
{
public interface IGirlFamily
{
void Date(params object[] parameters);
}
public class Girl : IGirlFamily
{
public void Date(params object[] parameters)
{
Console.WriteLine("你和女朋友开心地玩了一天");
}
}
public class MotherInLaw : IGirlFamily
{
private Girl girl = new Girl();
public void Date(params object[] parameters)
{
if (parameters[0] as string == "我们约好要一起写作业")
{
Console.WriteLine("你:我们约好要一起写作业");
Console.WriteLine("老丈母娘:好孩子,去吧");
girl.Date();
} else if (parameters[0] as string == "我们约好要去开房")
{
Console.WriteLine("你:我们约好要去开房");
Console.WriteLine("老丈母娘:滚出去!");
}
}
}
static void Main(string[] args)
{
var mother = new MotherInLaw();
Console.WriteLine("场景一:你来到女朋友家...");
Console.WriteLine("老丈母娘:你们要去做什么?");
mother.Date("我们约好要一起写作业");
Console.WriteLine();
Console.WriteLine("场景二:你来到女朋友家...");
Console.WriteLine("老丈母娘:你们要去做什么?");
mother.Date("我们约好要去开房");
}
}
}