閱讀565 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Design Pattern: Proxy 模式

學習是分享和合作式的!

轉載請注明出處:https://blog.csdn.net/wdzxl198/article/details/10472999

文章摘自: https://www.riabook.cn/doc/designpattern/

Gof 的書中對Proxy模式的目的給定為:為其它的物件提供一種代理,以控製對這個物件的訪問。由這句話所延伸出來的意思是,根據您的目的不同,您的代理物件將負有不同的責任,因為產生多種不同的代理情況。

根據不同的代理目的,而有不同的代理情況,在Gof中所舉的一個例子是Virtual Proxy,當中舉一個文檔中內嵌圖片的例子,假設您的圖片是在文檔分頁的後麵,一開始您並不用直接載入圖片,而使用一個虛代理物件,代替圖片被載入,以 求開啟一個文檔的時候,速度能夠加快。當您卷動文檔至該顯示圖片的頁數時,這時再載入圖片。
Proxy

如上圖所示,當文檔被開啟時,ImageProxy物件代理Image物件被載入,在還沒卷動至圖片顯示處時,也就是還沒有調用 ImageProxy的draw()時,圖片並不會被載入,因而可以加速文檔的開啟與節省記憶體的使用;如果需要顯示圖片了,ImageProxy的 draw()會被調用,而這時才真正創建Image物件,以從硬碟中載入圖片。

Proxy模式的 UML 結構圖如下所示:
Proxy

在調用RealSubject的request()之前,Proxy物件也許會有一些預先處理的操作,就假設我們組織為preOperation()與 postOperation()好了,當客戶對Proxy發出request()請求後,一個可能的時序圖如下所示:
Proxy


您的preOperation()與postOperation()正決定了Proxy模式使用於何種情況,例如一個Remote Proxy的情況,可以為一個遠端真實物件提供一個局部代表;Protection Proxy控製對物件的訪問,您可以使用它來作不同級別、權限的存取控製;Cache Proxy為一個物件提供臨時的儲存,使得許多客戶端都能直接存取它,而不用對真實物件直接要求,隻有在必要的時候更新這個臨時物件,或是讓客戶直接存取 真實物件。


現在來看看實現代理的兩種方式:Static Proxy與Dynamic Proxy。嚴格來說這是屬於模式的實現方式,不過藉由實例可以更了解Proxy模式的應用。

先來看個例子,這個例子是記錄(log)動作,程式中很常需要為某些動作或事件作下記錄,以便在事後檢視或是作為除錯時的資訊,一個最簡單的例子如下:

  • HelloSpeaker.java
import java.util.logging.*; 

public class HelloSpeaker { 
    private Logger logger = 
               Logger.getLogger(this.getClass().getName()); 

    public void hello(String name) { 
        logger.log(Level.INFO, "hello method starts....");

        System.out.println("Hello, " + name); 

        logger.log(Level.INFO, "hello method ends...."); 
    } 
} 

HelloSpeaker在執行hello()方法時,您希望能記錄該方法已經執行及結束,最簡單的作法就是如上在執行的前後加上記錄動作,然而 Logger介入了HelloSpeaker中,記錄這個動作並不屬於HelloSpeaker,這使得HelloSpeaker增加了非業務上需要的邏 輯在當中。

想想如果程式中這種記錄的動作到處都有需求,上麵這種寫法勢必造成必須複製記錄動作的程式碼,使得維護記錄動作的困難度加大。如果不隻有記錄動作,有一些 非物件本身職責的相關動作也混入了物件之中(例如權限檢查、事務管理等等),會使得物件的負擔更形加重,甚至混淆了物件的職責,物件本身的職責所占的程式 碼,或許遠小於這些與物件職責不相關動作的程式碼。

怎麼辦,用下麵的方法或許好一些,先定義一個介麵,然後實作該介麵:
  • IHello.java
public interface IHello { 
    public void hello(String name); 
} 

  • HelloSpeaker.java
public class HelloSpeaker implements IHello { 
    public void hello(String name) { 
        System.out.println("Hello, " + name); 
    } 
} 

接下來實作一個代理物件HelloProxy:
  • HelloProxy.java
import java.util.logging.*; 

public class HelloProxy implements IHello { 
    private Logger logger = 
              Logger.getLogger(this.getClass().getName()); 
    private IHello helloObject; 

    public HelloProxy(IHello helloObject) { 
        this.helloObject = helloObject; 
    } 

    public void hello(String name) { 
        logger.log(Level.INFO, "hello method starts...."); 

        helloObject.hello(name); 

        logger.log(Level.INFO, "hello method ends...."); 
    } 
}

執行時可以如此:
IHello helloProxy = new HelloProxy(new HelloSpeaker());
helloProxy.hello("Justin");

代理物件HelloProxy將代理真正的HelloSpeaker來執行hello(),並在其前後加上記錄的動作,這使得 HelloSpeaker在撰寫時不必介入記錄動作,HelloSpeaker可以專心於它的職責。

這是Static Proxy的基本範例,然而如您所看到的,代理物件的一個介麵隻服務於一種類型的物件,而且如果要代理的方法很多,勢必要為每個方法進行代理, Static Proxy在程式規模稍大時就必定無法勝任。

Java在JDK 1.3之後加入協助開發Dynamic Proxy功能的類別,我們不必為特定物件與方法撰寫特定的代理,使用Dynamic Proxy,可以使得一個handler服務於各個物件,首先,一個handler必須實現 java.lang.reflect.InvocationHandler:
  • LogHandler.java
import java.util.logging.*; 
import java.lang.reflect.*; 

public class LogHandler implements InvocationHandler { 
    private Logger logger = 
               Logger.getLogger(this.getClass().getName()); 
    private Object delegate; 

    public Object bind(Object delegate) { 
        this.delegate = delegate; 
        return Proxy.newProxyInstance(
                 delegate.getClass().getClassLoader(), 
                 delegate.getClass().getInterfaces(), 
                 this); 
    }
 
    public Object invoke(Object proxy, 
                         Method method, 
                         Object[] args) throws Throwable {
        Object result = null; 
        try { 
            logger.log(Level.INFO, 
                         "method starts..." + method); 
            result = method.invoke(delegate, args); 
            logger.log(Level.INFO, 
                         "method ends..." + method); 
        } catch (Exception e){ 
            logger.log(Level.INFO, e.toString()); 
        } 
        return result; 
    } 
} 

InvocationHandler的invoke()方法會傳入被代理物件的方法名稱與執行參數實際上要執行的方法交由method.invoke (),並在其前後加上記錄動作,method.invoke()傳回的物件是實際方法執行過後的回傳結果。

Dynamic Proxy必須宣告介麵,實作該介麵,例如:
  • IHello.java
public interface IHello { 
    public void hello(String name); 
} 

  • HelloSpeaker.java
public class HelloSpeaker implements IHello { 
    public void hello(String name) { 
        System.out.println("Hello, " + name); 
    } 
} 

java.lang.reflect.Proxy的newProxyInstance()依要代理的物件、介麵與handler產生一個代理物件,我們可 以使用下麵的方法來執行程式:
LogHandler logHandler = new LogHandler();
IHello helloProxy = (IHello) logHandler.bind(
new HelloSpeaker());
helloProxy.hello("Justin");

LogHandler不在服務於特定物件與介麵,而HelloSpeaker也不用插入任何有關於記錄的動作,它不用意識到記錄動作的存在。

最後更新:2017-04-03 16:49:02

  上一篇:go linux socket編程初認識
  下一篇:go Design Pattern: Flyweight 模式