设计模式 - 代理模式

代理模式,PROXY。这是一个比较常用的结构型模式。个人比较喜欢这个模式,代理模式一个比较出名的应用就是智能指针。

 

意图

为其他对象提供一个代理以控制对这个对象的访问。

 

结构图

 

一眼看去是不是和Decorator(装饰模式)很像?是啊,从结构图看,确实很像,或者说几乎一样。虽说它们的结构图很像,但是它们的意图完全不一样:装饰模式是为了给对象增加功能而代理模式是为了控制对象的访问。

根据GOF设计模式里面的说法,代理模式可以应用于:

1. 远程代理, 为一个对象在不同的地址空间提供局部代表。

2. 虚代理,根据需要创建开销很大的对象。

3. 保护代理,控制对原始对象的访问。保护对象用于对象应该有不同的访问权限的时候。

4. 智能指引,取代了简单的指针,它在访问对象时执行一些附加操作,比如智能指针。

 

对于第一种“远程代理”,我工作当中还没有碰到过,所以也没有什么经验。我碰到过#2,#3和#4。智能指引的经典应用就是智能指针,这个我相信大多数C++程序员都用到过。保护代理在大多数拥有账户的系统里面还是挺常见的,用来控制各种权限。虚代理应用于大对象系统里面。下面我们以前面用过的图形例子来讲解一下代理模式的虚代理。现在,我们假如有这么几个类,CPicture,CRichText。CRichText里面可以存放CPicture,CPicture包含一个需要1M内存的图片,并且初始化会花2秒钟,而现在的情况是这个图片会被放在CRichText的第二页里面,也就是说用户一打开这个CRichText,只会看到第一页。那么如果我们在用户一打开CRichText的时候就去装载CPicture对象,显然没什么意义,比如用户只看了第一页就关闭了,我们装载了这个CPicture有什么用呢?花了1M 内存,还有2秒延时。而用户根本就没有去看第二页。

我们就可以通过代理模式来延迟CPicture的创建。换句话说就是等有需要的时候才来创建真实的CPicture对象。先来看看类图:

CPicProxy是CPicture的一个代理,同时CPicProxy和CPicture一样也是继承于CGraphic接口,也就是说CPicProxy和CPicture具有一样的接口函数。这也就是说在任何使用CPicture的地方,都可以使用CPicProxy来代替。先看看具体的代码:

class CGraphic
{
public:
	virtual void Draw() = 0;
};

class CPicture: public CGraphic
{
public:
	CPicture(const std::string& strPicName):_PicName(strPicName)
	{
		std::cout << "CPicture constructor, it's a big object, it will take about 2 seconds and occupy 1 M memory.\n";
	}

	virtual void Draw()
	{
		std::cout << "Draw a picture:"<<_PicName << "\n";
	}

protected:
	std::string _PicName;

};

class CPicProxy: public CGraphic
{
public:
	CPicProxy(const std::string& strPicName):_PicName(strPicName), _Picture(NULL){}

	~CPicProxy()
	{
		if (_Picture)
		{
			delete _Picture;
			_Picture = NULL;
		}
	}

	virtual void Draw()
	{
		LoadPicture()->Draw();
	}

	CPicture* LoadPicture()
	{
		if (_Picture == NULL)
		{
			_Picture = new CPicture(_PicName);
		}
		return _Picture;
	}

protected:
	std::string _PicName;
	CPicture* _Picture;
};

我们假设CPicture是一个比较浩内存的对象,因为它要装载大图片。那么CPicProxy是CPicture的一个代理,我们可以看到CPicProxy的构造函数里面初始化了一个图片路径,然后把CPicture的对象指针设置为0。注意CPicProxy::LoadPicture()这个函数,这个函数会创建一个CPicture对象,只创建一次。然后在CPicProxy::Draw()里面就调用CPicture对象的Draw函数。再看看CPicture的调用者CRichText:

class CRichText
{
public:
	CRichText():_g(NULL){}

	~CRichText()
	{
		if(_g)
		{
			delete _g;
			_g = NULL;
		}
	}

	void Insert(CGraphic* g){_g = g;}
	void Draw()
	{
		//当显示第二页的时候才画图片
		std::cout << "draw picture when user switches to 2nd page\n";
		_g->Draw();
	}
	
protected:
	CGraphic* _g;
};

我们假设CRichText::Draw()函数只有当有需要显示图片的时候才被调用,例子假设图片是放在第二页的,也就是说当用户查看第二页的时候CRichText::Draw()才被调用。那么我们客户端就可以这么调用:

CPicProxy* proxy = new CPicProxy("c:\\test.jpg");
	CRichText text;
	text.Insert(proxy);//这个函数是很快的,因为还没有真正创建CPicture对象。

//假设只有当用户切到第二页的时候,下面的函数才被调用
	text.Draw();//这个时候才创建CPicture,这个会花2秒种并且占用1M内存。

我们可以创建一个CPicture的代理对象CPicProxy,然后把代理对象传给CRichText。注意:CPicProxy和CPicture一样,继承于同一个接口,所以我们可以用CPicProxy对象代替CPicture。
 这么做有什么好处呢?通过虚代理,我们成功地将CPicture对象的创建延迟到了需要的时候才创建。考虑这种情况,用户打开CRichText,然后看了第一页就关闭了,并没有看第二页。那么CPicProxy并不会创建CPicture对象,也就是没有1M内存的消耗和2秒延迟了。那么有人会问:

1. 不如我们直接在CPicture的Draw函数里面才创建呢?是的,没错,这样做也行,其实这个就相当于CPicture已经支持“将对象延迟到需要的时候才创建了”。那么万一CPicture类已经存在了,而且在CPicture的构造函数里面会分配1M内存呢?我们要尽量避免修改已有的类库(开闭原则),这个时候我们就可以使用代理模式来控制CPicture了。

2. 那要不然就不传CPicture对象给CRichText了,等到用户切换到第二页的时候才传。没错,这样也达到了效果。但是这就意味着我们需要修改CRichText的逻辑,这增加的程序的复杂度。使用代理模式就可以避免这种特殊处理。

总体来讲,虚代理就是为大对象先提供一个虚的代理,然后等有需要的时候才创建真正的大对象。

虚代理对开销很大的对象才有意义,如果是小对象,根本就没必要使用虚代理,不然就是杀鸡用牛刀。


OK, 虚代理基本介绍完毕了。还是那句话,不要硬套设计模式,只有当有需要的时候,才考虑用合适的设计模式来打造一个清晰而且强壮的系统架构。

 

有关保护代理和智能指引,这里就不举例了。总之,当我们想对一个对象进行一些控制的时候,就可以考虑代理模式。

 

相关模式:

1. Adapter:适配器为它所适配的对象提供了一个不同的接口。相反,代理提供了和它的实体相同的接口。然而,保护代理可能会拒绝执行实体对象的操作,因此,代理对象的接口实际上可能是实体接口的一个子集。

2. Decorator: 一开始就讲了Proxy和Decorator看上去很像。是啊,尽管结构和实现部分很类似,但是目的完全不一样。Decorator用于给对象增加功能,而Proxy用于控制对一个对象的访问。很多时候设计模式的不同是从意图来区分的,可能结构包括实现很类似,但是意图完全不一样。

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值