一、概述
大家都用過代理服務器,代理服務器是從出發點到目的地之間的中間層。而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:引用計數
大衛的Design Patterns學習筆記12:Proxy
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章
大衛的Design Patterns學習筆記20:State
一、概述State(狀態)模式用於把一個對象的內部狀態從對象中分離出來,形成單獨的狀態對象,所有與該狀態相關的行爲都放入該狀態對象中。一個對象可能處在
billdavid
2020-06-29 08:29:14
大衛的Design Patterns學習筆記22:Template Method
一、概述Template Method(模板方法)模式定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。Template Method使得子類可以
billdavid
2020-06-29 08:29:13
大衛的Design Patterns學習筆記15:Interpreter
一、概述Interpreter(解釋器)模式描述瞭如何爲簡單的語言定義一個文法,如何在該語言中表示一個句子,以及如何解釋這些句子。在這裏使用語言這個詞
billdavid
2020-06-29 08:29:12
大衛的Design Patterns學習筆記13:Chain of Responsibility
一、概述Chain of Responsibility(職責鏈,以下簡稱CoR)模式通過將多個對象串接成一條鏈(Chain),並沿着這條鏈傳遞上層應用
billdavid
2020-06-29 08:29:11
一文搞懂什麼是面向對象編程思想與設計原則(深度)
谭小先
2020-04-24 21:18:43
大衛的Design Patterns學習筆記10:Flyweight
billdavid
2020-02-24 21:30:48
大衛的Design Patterns學習筆記05:Singleton
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記07:Bridge
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記24:後記
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記14:Command
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記23:Vistor
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記17:Mediator
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記18:Memento
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記06:Adapter
billdavid
2020-02-24 21:30:37
大衛的Design Patterns學習筆記11:Decorator
billdavid
2020-02-24 21:30:37