一、概述
大家都用过代理服务器,代理服务器是从出发点到目的地之间的中间层。而Proxy模式中的Proxy功能上与此类似,是对象的访问者与对象之间的中间层。
Proxy(代理)模式可用于解决在直接访问对象不方便或不符合要求时,为这个对象提供一种代理,以控制对该对象的访问。
二、结构
Proxy模式的类图结构如下图所示:
图1:Proxy模式类图示意
在上面的类图中,Proxy类是Subject类的子类,但个人认为,单纯从Proxy的意图上讲,这一约束不是必须的。
与前面讨论的Decorator模式不同,在应用Proxy模式时,我们可能知道目标,也可能不知道目标(此时,被代理的对象由Proxy类创建),而Decorator模式下我们往往按照访问目标的方式去访问Decorator,即我们总是知道目标,或者说我们更注意的是目标,而不是Decorator。并且,我们Proxy类可能提供与被访问者不同的接口,而Decorator模式下应保证接口的一致性,以便用户可以用与访问Decoratee一样的方式来访问Decorator。
单纯从结构上讲,Adapter模式与Proxy模式比较相似,但二者的区别在于意图的不同:Adapter的意图在于接口的转换,而Proxy的意图在于代理(或控制),因此,有人形象地将Proxy模式称为“票贩子模式”,而将Adapter模式称为“外汇买卖模式”。
三、应用
Proxy这个模式的目的比较笼统,引入Proxy的目的是在Subject和Client之间构建一个中间层,它适用的情况很多(以至于JDK1.3开始,特别添加了对Proxy的支持,详见java.lang.reflect.Proxy说明文档),可以简单分成以下几类:
1、远程访问代理(可能为了简化客户代码,也可能为了集中管理等);
2、重要对象(可能是共享对象或大的,耗资源的对象)访问代理;
3、访问控制代理。
在实际应用中,可能出现同时属于以上多种类别的情况。下面是一些可以应用Proxy模式的常见情况:
1、远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中,远程代理又叫做大使(Ambassador)。常见的应用如CORBA、DCOM等。
2、虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。如某个Word文档中包含很多较大的图片,需要花费很长时间才能显示出来,那么使用编辑器或浏览器打开这个文档,不能等待大图片处理完成,这时需要做个图片Proxy来代替真正的图片,先在图片的位置显示一个框,然后随着图片逐步被打开,再对显示区域以及内容进行调整,直至显示所需内容。再比如说数据库的显示,客户现在只需要显示1-10条,你放在内存中10000条对客户没有任何意义,这时可以用一个代理,只是取出前10条就可以了,当客户选择显示下10条时再取出接下来的10条数据显示给客户。
3、Copy-on-Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。这在访问大的对象和防止频繁拷贝造成过多消耗时经常被用到,现代操作系统往往使用这种技术来防止频繁写磁盘,对于我们的普通应用而言,这种技术也被经常使用,当对象未发生修改时,先使用已有的对象,任何一方发生修改时才执行拷贝动作,创建新的对象,以避免在创建对象上过多的消耗(因为我们有可能在整个对象存在期间不会被修改),从而提高处理效率。
4、保护(Protect or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。对于硬件等系统资源而言,OS即是一层保护代理。
5、Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
6、防火墙(Firewall)代理:保护目标,不让恶意用户接近。
7、同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
8、智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。
总之,当需要对Client访问目标对象的行为进行控制时,请尽可能不要通过继承来对目标进行深入规范,而是采用Proxy模式在Client和Subject之间建立一个中间层,这将为我们的系统提供更大的可扩展性。
四、优缺点
五、举例
由于Proxy模式的目的是在Subject和Client之间构建一个中间层,以控制Client对Subject的访问,其应用十分广泛,很难举出一个面面俱到的例子,以下用两个典型应用来简单演示Proxy模式的应用。如果你感兴趣,也可以研究一下STL的auto_ptr和string的实现,其中也用到了Proxy模式,其中,std::string使用了Copy-on-Write技术。
下面是一个采用Proxy模式进行图片加载的例子(Virtual Proxy, Java Code):
注:运行该示例前请在程序运行目录下放置一个图片文件,并将其命名为test.jpg
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
// This class tests a virtual proxy, which is a proxy that
// delays loading an expensive resource (an icon) until that
// resource is needed.
public class VirtualProxyTest extends JFrame {
private static String IMAGE_NAME = "test.jpg";
private static int IMAGE_X = 256, IMAGE_Y = 256;
private JPanel normPanel, proxyPanel;
public VirtualProxyTest() {
super("Virtual Proxy Test");
normPanel = new NormPanel(IMAGE_NAME);
proxyPanel = new ImageProxy(IMAGE_NAME);
normPanel.setPreferredSize(new Dimension(IMAGE_X, IMAGE_Y));
proxyPanel.setPreferredSize(new Dimension(IMAGE_X, IMAGE_Y));
Container c = getContentPane();
c.setLayout(new GridLayout(1, 2));
c.add(normPanel);
c.add(proxyPanel);
// Set the bounds of the frame, and the frame's default
// close operation.
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
static public void main(String args[]) {
VirtualProxyTest app = new VirtualProxyTest();
app.show();
app.pack();
}
}
class NormPanel extends JPanel {
private String imageName = null;
private Image img = null;
public NormPanel(String imageName) {
this.imageName = imageName;
img = Toolkit.getDefaultToolkit().getImage(imageName);
}
public void paint(Graphics g) {
g.drawImage(img, 0, 0, getWidth(), getHeight(), this); //draw image
}
}
// ImageProxy is a proxy (or surrogate) for an image.
// The proxy delays loading the image until the first time the
// image is drawn. While the icon is loading its image, the
// proxy draws a border and the message "Loading image..."
class ImageProxy extends JPanel implements Runnable {
boolean isIconCreated = false;
private String imageName = null;
private Image img = null;
Thread imageLoadThread = null;
public ImageProxy(String imageName) {
this.imageName = imageName;
imageLoadThread = new Thread(this);
imageLoadThread.start();
}
// The proxy's paint() method is overloaded to draw a border
// and a message ("Loading image...") while the image
// loads. After the image has loaded, it is drawn. Notice
// that the proxy does not load the image until it is
// actually needed.
public void paint(Graphics g) {
if(isIconCreated) {
g.drawImage(img, 0, 0, getWidth(), getHeight(), this); //draw image
}
else {
g.drawRect(1, 1, getWidth() - 2, getHeight() - 2);
g.drawString("Loading image...", 20, 20);
}
}
public void run() {
try {
// Slow down the image-loading process.
Thread.currentThread().sleep(2000);
// create the image
img = Toolkit.getDefaultToolkit().getImage(imageName);
isIconCreated = true;
} catch(InterruptedException ex) {
ex.printStackTrace();
}
repaint();
}
}
以下示例取自Thinking in C++ 2nd,演示了Copy-on-Write技术的实现方法,耐心的Bruce Eckel用软件工程领域经典的Dog&DogHouse的例子清晰地告诉了我们应该如何实现Copy-on-Write(除了两处注释外,所有代码出自Bruce Eckel)。此外,关于string类中如何运用Copy-on-Write的问题,见参考1:
//: C12:ReferenceCounting.cpp
// From Thinking in C++, 2nd Edition
// Available at http://www.BruceEckel.com
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
// Reference count, copy-on-write
#include <string>
#include <iostream>
using namespace std;
// assistant function
inline void require(bool requirement,
const std::string& msg = "Requirement failed"){
using namespace std;
if (!requirement) {
fputs(msg.c_str(), stderr);
fputs("\n", stderr);
exit(1);
}
}
// Subject class
class Dog {
string nm;
int refcount;
Dog(const string& name)
: nm(name), refcount(1) {
cout << "Creating Dog: " << *this << endl;
}
// Prevent assignment:
Dog& operator=(const Dog& rv);
public:
// Dogs can only be created on the heap:
static Dog* make(const string& name) {
return new Dog(name);
}
Dog(const Dog& d)
: nm(d.nm + " copy"), refcount(1) {
cout << "Dog copy-constructor: "
<< *this << endl;
}
~Dog() {
cout << "Deleting Dog: " << *this << endl;
}
void attach() {
++refcount;
cout << "Attached Dog: " << *this << endl;
}
void detach() {
require(refcount != 0);
cout << "Detaching Dog: " << *this << endl;
// Destroy object if no one is using it:
if(--refcount == 0) delete this;
}
// Conditionally copy this Dog.
// Call before modifying the Dog, assign
// resulting pointer to your Dog*.
Dog* unalias() {
cout << "Unaliasing Dog: " << *this << endl;
// Don't duplicate if not aliased:
if(refcount == 1) return this;
--refcount;
// Use copy-constructor to duplicate:
return new Dog(*this);
}
void rename(const string& newName) {
nm = newName;
cout << "Dog renamed to: " << *this << endl;
}
friend ostream&
operator<<(ostream& os, const Dog& d) {
return os << "[" << d.nm << "], rc = "
<< d.refcount;
}
};
// Proxy class
class DogHouse {
Dog* p;
string houseName;
public:
DogHouse(Dog* dog, const string& house)
: p(dog), houseName(house) {
cout << "Created DogHouse: "<< *this << endl;
}
DogHouse(const DogHouse& dh)
: p(dh.p),
houseName("copy-constructed " +
dh.houseName) {
p->attach();
cout << "DogHouse copy-constructor: "
<< *this << endl;
}
DogHouse& operator=(const DogHouse& dh) {
// Check for self-assignment:
if(&dh != this) {
houseName = dh.houseName + " assigned";
// Clean up what you're using first:
p->detach();
p = dh.p; // Like copy-constructor
p->attach();
}
cout << "DogHouse operator= : "
<< *this << endl;
return *this;
}
// Decrement refcount, conditionally destroy
~DogHouse() {
cout << "DogHouse destructor: "
<< *this << endl;
p->detach();
}
void renameHouse(const string& newName) {
houseName = newName;
}
void unalias() { p = p->unalias(); }
// Copy-on-write. Anytime you modify the
// contents of the pointer you must
// first unalias it:
void renameDog(const string& newName) {
unalias();
p->rename(newName);
}
// ... or when you allow someone else access:
Dog* getDog() {
unalias();
return p;
}
friend ostream&
operator<<(ostream& os, const DogHouse& dh) {
return os << "[" << dh.houseName
<< "] contains " << *dh.p;
}
};
int main() {
DogHouse
fidos(Dog::make("Fido"), "FidoHouse"),
spots(Dog::make("Spot"), "SpotHouse");
cout << "Entering copy-construction" << endl;
DogHouse bobs(fidos);
cout << "After copy-constructing bobs" << endl;
cout << "fidos:" << fidos << endl;
cout << "spots:" << spots << endl;
cout << "bobs:" << bobs << endl;
cout << "Entering spots = fidos" << endl;
spots = fidos;
cout << "After spots = fidos" << endl;
cout << "spots:" << spots << endl;
cout << "Entering self-assignment" << endl;
bobs = bobs;
cout << "After self-assignment" << endl;
cout << "bobs:" << bobs << endl;
// Comment out the following lines:
cout << "Entering rename(\"Bob\")" << endl;
bobs.getDog()->rename("Bob");
cout << "After rename(\"Bob\")" << endl;
return 0;
} ///:~
参考:
1、More Effective C++ Item M29:引用计数