天天看點

Proxy 代理模式 對象結構型模式

1、意圖

        為其它對象提供一種代理以控制對這個對象的通路。

2、别名

        Surrogate

3、動機

        對一個對象進行通路控制的一個原因是為了隻有在我們确實需要這個對象時才對它進行建立和初始化。我們考慮一個可以在文檔中嵌入圖形對象的文檔編輯器。有些圖形對象(如大型光栅圖像)的建立開銷很大。但是打開文檔必須很迅速,因為我們在打開文檔時應避免一次性建立所有開銷很大的對象。因為并非所有這些對象在文檔中都同時可見,是以也沒有必要同僚建立這些對象。

        這一限制條件意味着,對于每一個開銷很大的對象,應該根據需要進行建立,當一個圖像變為可見時會産生這樣的需要。但是在文檔中我們用什麼來代替這個圖像呢?我們又如何才能隐藏需要建立圖像這一事實,進而不會使得剪輯器的實作複雜化呢?例如這種優化不應影響繪制和格式化的代碼。

        問題的解決方案是使用另一個對象,即圖像Proxy,替代那個真正的圖像。Proxy可以代替一個圖像對象,并且在需要時負責執行個體化這個圖像對象。

Proxy 代理模式 對象結構型模式

        隻有當文檔編輯器激活圖像代理的Draw操作以顯示這個圖像的時候,圖像Proxy才建立真正的圖像。Proxy直接将随後的請求轉發給這個圖像對象。是以在建立這個圖像以後,它必須有一個指向這個圖像的引用。

        我們假設圖像存儲在一個獨立的檔案中。這樣我們可以把檔案名作為對實際對象的引用。Proxy還存儲了圖像的尺寸(extent),即它的長和寬。有了圖像尺寸,Proxy無須真正執行個體化這個圖像就可以相應格式化程式對圖像尺寸的要求。

        以下的類圖更詳細地闡述了這個例子。

        文檔編輯器通過抽象的Graphic類定義的接口通路嵌入的圖像。ImageProxy是那些根據需要建立的圖像的類,ImageProxy儲存了檔案名作為指向磁盤上的圖像檔案的指針。該檔案名被作為一個參數傳遞給ImageProxy的構造器。

        ImageProxy還存儲了這個圖像的邊框以及對真正的Image執行個體的指引,直到代理執行個體化真正的圖像時,這個指引才有效。Draw操作必須保證在向這個圖像轉發請求之前,它已經被執行個體化了。GetExtent操作隻有在圖像被執行個體化後才向它傳遞請求,否則,ImageProxy傳回它存儲的圖像尺寸。

Proxy 代理模式 對象結構型模式

4、适用性

        在需要用比較通用和複雜的對象指針代替簡單的指針的時候,使用Proxy模式。下面是一些可以使用Proxy模式常見的情況:

        1)遠端代理(Remote Proxy)為一個對象在不同的位址空間提供局部代表。NEXTSTEP【Add94】使用NXProxy類實作了這一目的。Coplien[Cop92】稱這種代理為“大使”(Ambassador)。

        2)虛代理(Virtual Proxy)根據需要建立開銷很大的對象。在動機一節描述的ImageProxy就是這樣一種代理的例子。

        3)保護代理(Protection Proxy)控制對原始對象的通路。保護代理用于對象應該有不同的通路權限的時候。例如,在Choices作業系統【CIRM93】中KemelProxies為作業系統對象提供了通路保護。

        4)智能指引(Smart Reference)取代了簡單的指針,它在通路對象時執行一些附加操作。

            它的典型用途包括:

            · 對指向實際對象的引用計數,這樣當該對象沒有引用時,可以自動釋放它(也稱為Smart Pointers【Ede92】)。

            ·  當第一次引用一個持久對象時,将它裝入記憶體。

            · 在通路一個實際對象前,檢查是否已經鎖定了它,以確定其他對象不能改變它。

5、結構

Proxy 代理模式 對象結構型模式

        這是運作時刻一種可能的代理結構的對象圖。

Proxy 代理模式 對象結構型模式

6、參與者

        · Proxy(ImageProxy)

          -- 儲存一個引用使得代理可以通路實體。若RealSubject和Subject的接口相同,Proxy會引用Subject。

          -- 提供一個與Subject的接口相同的接口,這樣代理就可以用來替代實體。

          -- 控制對實體的存取,并可能負責建立和删除它。

          -- 其它功能依賴于代理的類型:

             · Pemote Proxy負責對請求及其參數進行編碼,并向不同位址空間中的實體發送已編碼的請求。

             · Virual Proxy可以緩存實體的附加資訊,以便延遲對它的通路。例如,動機一節中提到的ImageProxy緩存了圖像實體的尺寸。

             · Protection Proxy檢查調用者是否具有實作一個請求所必需的通路權限。

        ·Subject(Graphic)

          -- 定義RealSubject和Proxy的共用接口,這樣就在任何使用RealSubject的地方都可以使用Proxy。

        · RealSubject(Image)

          -- 定義Proxy所代表的實體。

7、協作

        代理根據其種類,在适當的時候向RealSubject轉發請求。

8、效果

        Proxy模式在通路對象時引入了一定程度的間接性。根據代理的類型,附加的間接性有多種用途:

        1)Remote Proxy 可以隐藏一個對象存在于不同位址空間的事實。

        2)Virtual Proxy可以進行最優化,例如根據要求建立對象。

        3)Protection Proxies和Smart Reference都允許在通路一個對象時有一些附加的内務處理(Houskeeping task)。

        Proxy模式還可以對使用者隐藏另一種稱之為copy-on-write的優化方式,該優化與根據需要建立對象有關。拷貝一個龐大而複雜的對象是一種開銷很大的操作,如果這個拷貝根本沒有被修改,那麼這些開銷就沒有必要。用代理延遲這一拷貝過程,我們可以保證隻有當這個對象被修改的時候才對它進行拷貝。

        在實作copy-on-write時必須對實體進行引用計數。拷貝代理僅會增加引用計數。隻有當使用者請求一個修改該實體的操作時,代理才會真正的拷貝它。在這種情況下,代理還必須減少實體的引用計數。當引用的數目為零時,這個實體将被删除。

        Copy-on-write可以大幅度的降低拷貝龐大實體時的開銷。

9、實作

        Proxy模式可以利用以下一些語言特征:

        1)重載C++中的存取運算符    C++支援重載運算符。重載這一運算符使你可以在撤銷對一個對象的引用時,執行一些附加的操作。這一點可以用于實作某些種類的代理;代理的作用就像一個指針。

              下面的例子說明怎樣使用這一技術實作一個稱為ImagePtr的虛代理。

Proxy 代理模式 對象結構型模式

            重載的->和*運算符使用LoadImage和_image傳回給它的調用者(如果必要的話裝入它)。

Proxy 代理模式 對象結構型模式

              該方法使你能夠通過ImagePtr對象調用Image操作,而省去了把這些操作作為ImagePtr接口的一部分的麻煩。

Proxy 代理模式 對象結構型模式

          請注意這裡的image代理起到一個指針的作用,但并沒有将它定義為一個指向Image的指針。這意味着你不能把它當做一個真正的指向Image的指針來使用。是以在使用此方法時使用者應差別對待Image對象和Imageptr對象。

         重載成員通路運算符并非對每一種代理來說都是好辦法。有些代理需要清楚地知道調用了哪個操作,重載運算符的方法在這種情況下行不通。

         考慮在目的一節提到的虛代理的例子,圖像應該在一個特定的時刻被裝載----也就是在Draw操作被調用時----而不是在隻要引用這個圖像就裝載它。重載通路操作符不能作出這種區分。在這種情況下我們隻能人工實作每一個代理操作,向實體轉發請求。

         正如示例代碼中所示的那樣,這些操作之間非常相似。一般來說,所有的操作在向實體轉發請求之前,都要檢驗喝個要求是否合法,原始對象是否存在等。但重複寫這些代碼很麻煩,是以我們一般用一個預處理程式自動生成它。

         2)使用Smalltalk中的doesNotUnderstand    Smalltalk提供一個hook方法可以用來自動轉發請求。當使用者向接受者發送一個消息,但是這個接受者沒有相關方法的時候,Smalltalk調用方法doesNotUnderstand:amessage。Proxy類可以重定義doesNotUnderstand以便向它的實體轉發這個消息。

        為了保證一個請求真正被轉發給實體,而不是無聲無息的被代理所吸收,我們可以定義一個不了解任何資訊的Proxy類。Smalltalk定義了一個沒有任何超類的Proxy類,實作了這個目的。

        doesNotUnderstand:的主要缺點在于:大多數Smalltalk系統都有一些由虛拟機直接控制的特殊消息。而這些消息并不因其通常的方法查找。唯一一個通常用Object實作(因而可以影響代理)的符号是恒等運算符==。

        如果你準備使用doesNotUnderstand:來實作Proxy的話,你必須圍繞這一問題進行設計。對代理的辨別并不意味着對真正實體的辨別。doesNotUnderstand:另一個缺點是,它主要用作錯誤處理,而不是建立代理,是以一般來說它的速度不是很快。

         3)Proxy并不總是需要知道實體的類型    若Proxy類能夠完全通過一個抽象接口處理它的實體,則無須為每一個RealSubject類都生成一個Proxy類;Proxy可以統一處理所有的RealSubject類。但是如果Proxy要執行個體化RealSubjects(例如在virtual Proxy中),那麼它們必須知道具體的類。

        另一個實作方面的問題涉及到在執行個體化實體以前怎樣引用它。有些代理必須引用它們的實體,無論它在硬碟上還是在記憶體中。這意味着它們必須使用某種獨立于位址空間的對象辨別符。在目的一節中,我們采用一個檔案名來實作這種對象辨別符。

10、代碼執行個體

        網絡上java的設計模式的代碼示例很多,可搜尋随意參考。

繼續閱讀