前面写了代理模式的一种使用方式——句柄,接下来要介绍伪代理。
我们在浏览比较大的word文档或PDF文档是,常常会遇到这种情形:前面看过和当前看的部分显示正常,当我们将鼠标按住滚动条迅速往下翻页的时候,会发现后面的文档还在加载,或者文字已经出来了,但图像还在处理,当然,这是在电脑配置比较低的情况下才能见证的。那么这是为什么?难道不是打开文件的时候全部内容都解析出来吗?或者将文字解析出来的部分也把图像解析出来?我们可以考虑:当文档较大时,读者很可能不会全部阅读,至少不太可能这么迅速地阅读全部,而阅读器的响应时间需要限定,否则这么慢的阅读器谁用?所以,很可能的实现是打开文档只解析一部分后将文档显示出来,后面的部分,只需要有一些预加载就可以了,毕竟解析几页的速度还是很快的。
那么,这里我们考虑一个实例:阅读器解析文档时,先将后面的几页的文字解析出来,毕竟文字处理很快,而图像就处理得较慢了,我们可以先用一个空白的框代替,当快阅读到这个图像时再将其解析显示出来,以提高响应速度。这里假定文档和图像不同文件,文档只保存了在某个位置有一副多大的存储在哪里的图像这些基本信息,即位置、大小、路径。那么我们会怎样设计呢?
一种比较好的实现方式是使用伪代理:解析文档时先操作伪代理,有伪代理负责绘制出空白的框,但实际需要解析图像时,再将图像对象生成出来,之后操作转为对图像对象的操作。如下图所示:
文档操作代理类ImageProxy的对象,该对象保存了一个Image对象的指针。在解析文档时,先不生成Image对象,调用ImageProxy的GetExtent接口绘制空白区域。当读者阅读到该位置时,调用Draw函数,这时必须将Image对象生成出来,之后的操作则转向Image对象。
proxy.h
#ifndef __Proxy_H_
#define __Proxy_H_
struct Size
{
int Width,Height;
Size(int x = 0 , int y = 0) : Width(x) , Height(y) {}
};
class Graphic
{
public:
virtual ~Graphic(){}
virtual const Size& GetExtent() = 0;
virtual void Draw() = 0;
virtual void Load() = 0;
virtual void Save() = 0;
};
class Image : public Graphic
{
public:
Image(Size size , const char* FileName);
~Image();
virtual const Size& GetExtent();
virtual void Draw();
virtual void Load();
virtual void Save();
private:
Size m_Size;
char* m_FileName;
};
class ImageProxy : public Graphic
{
public:
ImageProxy(Size size , const char* FileName);
~ImageProxy();
virtual const Size& GetExtent();
virtual void Draw();
virtual void Load();
virtual void Save();
private:
Image* GetImage();
private:
Size m_Size;
char* m_FileName;
Image* m_pImage;
};
#endif
proxy.cpp
#include "stdafx.h"
#include "proxy.h"
#include <string>
#include <iostream>
#include <assert.h>
using namespace std;
Image::Image(Size size, const char *FileName)
{
m_Size.Width = size.Width;
m_Size.Height = size.Height;
m_FileName = _strdup(FileName);
Load();
}
Image::~Image()
{
Save();
}
const Size& Image::GetExtent()
{
cout<<"GetExtern in Image"<<endl;
return m_Size;
}
void Image::Draw()
{
//do draw
cout<<"Draw Image"<<endl;
}
void Image::Save()
{
//do save
cout<<"Save Image"<<endl;
}
void Image::Load()
{
//do load
cout<<"Load Image"<<endl;
}
ImageProxy::ImageProxy(Size size, const char *FileName)
{
m_Size.Height = size.Height;
m_Size.Width = size.Width;
m_FileName = _strdup(FileName);
m_pImage = NULL;
}
ImageProxy::~ImageProxy()
{
if(m_pImage)
delete m_pImage;
}
const Size& ImageProxy::GetExtent()
{
if(m_pImage)
return m_pImage->GetExtent();
else
{
cout<<"GetExtent in ImageProxy"<<endl;
return m_Size;
}
}
void ImageProxy::Draw()
{
if(NULL == m_pImage)
m_pImage = GetImage();
m_pImage->Draw();
}
void ImageProxy::Load()
{
if(NULL == m_pImage)
m_pImage = GetImage();
m_pImage->Load();
}
void ImageProxy::Save()
{
if(NULL == m_pImage)
m_pImage = GetImage();
m_pImage->Save();
}
Image* ImageProxy::GetImage()
{
Image* pImage = new Image(m_Size , m_FileName);
assert(pImage);
return pImage;
}
测试
#include "stdafx.h"
#include "proxy.h"
int _tmain(int argc, _TCHAR* argv[])
{
Graphic* pGraph = new ImageProxy(Size(10,20) , "image.bmp");
pGraph->GetExtent();
pGraph->Draw();
pGraph->GetExtent();
delete pGraph;
return 0;
}
输出
GetExtent in ImageProxy
Load Image
Draw Image
GetExtern in Image
Save Image
我们可以看到,在初始绘制的时候,调用GetExtent函数是用代理ImageProxy自己的数据,在调用Draw实际生成Image对象之后,在调用GetExtent则是用的Image对象的。伪代理比较特别的地方,就是控制所代理对象的生成时机,并且一前一后操作有些不同。
个人原创,如有不对之处请指正,转载请注明出处。