大衛的Design Patterns學習筆記12:Proxy

一、概述
大家都用過代理服務器,代理服務器是從出發點到目的地之間的中間層。而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:引用計數
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章