跳到主要內容

代理人模式 (Proxy Pattern)

        代理人模式如同字面上的意思,就是做事情時(如取得某些資料),是透過代理人,而不是直接跟提供資料的物件構通。先來看看此模式的正式定義及類別圖:

代理人模式讓某個物件具有一個替身,藉以控制外界對此物件的影響。
Provide a surrogate or placeholder for another object to control access to it.


        從類別圖可以看到, Subject 是共用的介面,可以讓客戶將 Proxy 物件視為 RealSubject 物件來處理。RealSubject 是真正做事的物件,被 Proxy 代理, Proxy 可以控制 RealSubject 的存取。Proxy 持有 RealSubject 的參考,客戶和 RealSubject 的互動都要透過 Proxy,在某些情形下這樣的限制是必要的,如 RealSubject 是遠端物件, RealSubject 建立成本高等等。

        此模式有許多種變形,都是依據上面的原則而發展出來的。以下介紹 深入淺出設計模式(Head First Design Patterns) 裡面提到的一些應用方式:

        1. 遠端代理人(Remote):算是最常使用到的應用方式。在網路中或是跨 Process 的各種程式,不可能直接存取(assess) 不同程式間的物件,因此就需要遠端代理人。 Java RMI 和 Android AIDL 都算是遠端代理人的實踐。

        2. 虛擬代理人(Virtual):代理的對象是建立很花費資源的物件。當物件建立前和建立中,由虛擬代理人扮演代理的角色。物件建立完成後,代理人就會將讓求直接轉給物件。以下為簡單範例程式:

// Proxy 實作跟 RealSubject 共同的介面 Icon
public class ImageProxy implements Icon {

    // 這是我們的 RealSubject,
    // 也就是真正要顯示圖的物件
    private ImageIcon mImageIcon;
    private URL mUrl;
    private Thread mRetrievalThread;
    private boolean mRetrieving = false;

    // 此範例是從網路上取得圖
    public ImageProxy(URL url)
    {
        mUrl = url;
    }

    @Override
    public int getIconWidth()
    {
        // 在圖下載完成前, 先傳回預設的值
        if(mImageIcon != null)
        {
            return mImageIcon.getIconWidth();
        }
        else
        {
            return 0;
        }
    }

    @Override
    public int getIconHeight()
    {
        // 在圖下載完成前, 先傳回預設的值
        if(mImageIcon != null)
        {
            return mImageIcon.getIconHeight();
        }
        else
        {
            return 0;
        }
    }

    @Override
    public int paintIcon(...)
    {
        // 圖下載好了, 畫圖的事就轉給
        // RealSubject 做
        if(mImageIcon != null)
        {
            mImageIcon.paintIcon(...);
        }
        else
        {
            // 第一次下載圖
            drawString("Loading icon, please wait...");
            if(!mRetrieving)
            {
                mRetrieving = true;

                mRetrievalThread = new Thread(new Runnable() {

                    @Override
                    public void run()
                    {
                        mImageIcon = new ImageIcon(mUrl, "icon");
                    }
                });
                mRetrievalThread.start();
            }
        }
    }
}
        3. 保護代理人(Protection):根據客戶的權限決定是否能存取物件。如果需要,可以針對不同客提供不同權限。

// Proxy 實作跟 RealSubject 共同的介面 CarBase
public class CarProxy implements CarBase {

    // 這是我們的 RealSubject
    private CarBase mCar;
    private Driver mDriver;

    public CarProxy()
    {
        mCar = new Car();
    }

    @Override
    public int driveCar(Driver driver)
    {
        // 確認客戶是否達到開車年齡
        if(driver.getAge() < 18)
        {
            System.out.println("Driver is too young to drive.");
        }
        else
        {
            mCar.driveCar();
        }
    }
}

        4. 防火牆代理人( Firewall):控制網路資源的存取,保護內部免於受到外面的侵害。常用於公司的防火牆系統。

        5. 聰明參考代理人(Smart Reference):當物件被參考到時,進行額外的動作,例如計算此物件被參考的次數。

        6. 備用代理人(Caching):將耗資源的運算結果暫存起來,供後續使用,以減少成本。常用於 Web Proxy。

        7. 同步化代理人(Synchronization):提供安全的存取,讓多個執行緖存取同一個物件時不會出錯。

// Proxy 實作跟 RealSubject 共同的介面 TableBase
public class RowLockTableProxy extends TableBase {

    private TableBase mTable;
    private Object[] mLocks;

    public RowLockTableProxy(TableBase table)
    {
        mTable = table;

        // 針對不同 row 建立不同 lock
        mLocks = new Object[mTable.numOfRows()];
        for(int i = 0; i < mTable.numOfRows(), i++)
        {
            mLocks[i] = new Object();
        }
    }

    @Override
    public Object getElementAt(int row, int column)
    {
        synchronized (locks[row])
        {
            return realTable.getElementAt(row, column);
        }
    }

    @Override
    public int numOfRows()
    {
        return mTable.numOfRows();
    }
}

        8. 隱藏複雜代理人(Complexity Hiding):用來對一群複雜的類別,進行複雜度隱藏以及存取控制。有時也被稱為表象代理人(Facade Proxy)。要注意的是這和表象模式是不一樣的,因為代理人控制存取,而表象模式只提供另一組介面。

        9. 寫入時複製代理人(Copy-On-Write):用來控制物件的複製,將複製的動作拖延到客戶真的需要為止,這是虛擬代理人的變形。可以參考 Java 或 Android 的 CopyOnWriteArrayList 實作。

        看到這邊我們可以發現,上面提到的代理人變形都跟「控制存取」有關。有人可能有疑問,覺得代理人模式跟裝飾者模式一樣,都是把物件包裝起來,再把調用傳遞過去,這樣有什麼區別?注意裝飾者是增加物件的行為,而代理人是控制物件的存取。而用虛擬代理人的例子來看,客戶如果直接存取 ImageIcon,就必須等圖下載完,且等的時候不能做其他事(不能顯示訊息)。在這裡代理人就是控制 ImageIcon 的存取,要等圖真的下載完成才允許客戶存取 ImageIcon。

        也有人可能會覺得代理人模式跟轉接器模式很像。雖然他們兩個都是客戶和物件之間的中間層,負責將請求轉給真正的物件,但轉接器會改變物件的介面,而代理人是實作相同的介面

參考資料:

        深入淺出設計模式(Head First Design Patterns)

留言

這個網誌中的熱門文章

整理設計模式

        依據 GOF 的書,可以將經典的設計模式分為以下三類:生成、行為、結構。 生成模式 :牽涉到 將物件實體化 。這類模式都提供一個方法,將客戶從所需要實體化的物件中鬆綁出來。 獨體模式 (Singleton Pattern) 工廠方法模式 (Factory Method Pattern) 抽象工廠模式 (Abstract Factory Pattern) 建立者模式 (Builder Pattern) 原型模式 (Prototype Pattern) 結構模式 :讓你 合成類別或物件到大型的結構 。 裝飾者模式 (Decorator Pattern) 轉接器模式 (Adapter Pattern) 表象模式 (Facade Pattern) 合成模式 (Composite Pattern) 代理人模式 (Proxy Pattern) 橋接模式 (Bridge Pattern) 享元模式 (Flyweight Pattern) 行為模式 :模述 類別和物件如何互動 ,以及 各自的責任 。 策略模式 (Strategy Pattern) 觀察者模式 (Observer Pattern) 命令模式 (Command Pattern) 樣板方法模式 (Template Method Pattern) 反覆器模式 (Iterator Pattern) 狀態模式 (State Pattern) 責任鏈模式 (Chain of Responsibility Pattern) 解譯器模式 (Interpreter Pattern) 中介者模式 (Mediator Pattern) 備忘錄模式 (Memento Pattern) 訪問者模式 (Visitor Pattern)         有人可能會覺得裝飾者模式明明有替物件增加行為,為什麼不算是行為模式呢?我們可以從上面的結構模式得知, 結構模式用來描述類別或物件如何被合成,以建立新的結構或功能 。裝飾者模式允許你透過「 將某物件包裝進另一個物件的方式 」,將物件合成以提供新功能,因此焦點應該放在「 動態合成物件,以取得某功能 」,而不是物件之間的溝通。         設入淺出設計模式也有提到一些使用設計模式的

訪問者模式 (Visitor Pattern)

        假設你設計一個系統,其中會有一些相似類別,類別中都有某些方法內容相似,但還是需要判斷目前要做事的是哪個類別才能呼叫對應的適當類別。通常遇到這種情情,在 Java 中最直接的做法就是使用 instanceof 關鍵字來判斷,如以下的簡單範例: public interface CarComponent { public void printMessage(); } public class Wheel implements CarComponent { @Override public void printMessage() { System.out.println("This is a wheel"); } // 這是 Wheel 跟 Engine 不同的方法 public void doWheel() { System.out.println("Checking wheel..."); } } public class Engine implements CarComponent { @Override public void printMessage() { System.out.println("This is a engine"); } // 這是 Wheel 跟 Engine 不同的方法 public void doEngine() { System.out.println("Testing this engine..."); } } public class Car { private List mComponents; public Car() { mComponents = new ArrayList<carcomponent>(); } // 有些時候我們還是需要針對不同類別去做不同的事情 public void setComponent(CarCompon

裝飾者模式 (Decorator Pattern)

        假如你有一間飲料店, 目前只有賣幾種咖啡。因為生意很好, 因此想更換菜單…         以下是目前菜單的類別圖:         簡單說明此類別圖, cost() 是抽象的, 子類別要實作自己的 cost() 來告知飲料的價格。         買咖啡時, 也可要求要加料, 例如牛奶(Milk)、摩卡(Mocha,就是巧克力口味)。這樣的新類別要如何設計呢 ? 看起來是不能直接新增所需的子類別, 例如 EspressoWithMilk, EspressoWithMilkAndMocha, DarkRoastWithMilk, DarkRoastWithMilkAndMocha… 這樣加下去, 日後飲料跟配料越來越多時, 類別也就越多, 這實在不是個好設計。         換個方式設計呢, 在 Beverage 裡面加入所有的配料如何 ? 這樣好像也不太好, 未來要是配料有更動, Beverage 程式碼就要重寫, 而未來要是有新口味的飲料時, 有些配料就不太合理 ( 薑茶加摩卡 ? ), 更麻煩的是, 無法應付機車的客人 (例如要加 3 份牛奶)。這時候裝飾者模式就能上場啦。在介紹裝飾者模式前, 先說明其設計守則: 類別應該開放, 以便擴充 ; 應該關閉, 禁止修改。         我們的目標是允許類別容易擴充, 在不修改現有程式碼的情形就能搭配新的行為。這樣的設計具有彈性, 可以接受新功能以達到改變需求的目的。這看起來好像有點矛盾, 但是的確有一些技術可以在不直接修改程式碼的情形下進行擴充, 如裝飾者模式。         這時候應該有人會問: 那是不是以後我的專案架構設計都遵循這個守則就是好設計了 ? 答案是不太可能, 也沒這必要, 就算做得到, 也可能是浪費, 容易導致程式碼複雜且難以理解。只需小心選擇哪些部分未來會擴充, 這些部份遵循這個設計守則即可。         接下來正式介紹裝飾者模式的定義:  裝飾者模式動態地將責任加諸於物件上。若要擴充功能,裝飾者模提供了比繼承更有彈性的選擇 Attach additional responsibilities to an object dynamically. Decorators provide a flexible alt