1. 程式人生 > >Proxy模式(代理[延遲]模式)

Proxy模式(代理[延遲]模式)

Proxy??

Proxy是“代理人”的意思,它指的是代替別人進行工作的人。代理實際上就是使用委託的機制,在代理的過程中你可以做點其他的事情,然後再來執行被代理物件的程式碼。

  • 知識儲備

1.什麼時候使用:

GoF書(請參見附錄E[GoF])在講解Proxy模式時,使用了一個可以在文字中嵌入圖形物件(例如圖片等)的文字編輯器作為例子。為了生成這些圖形物件,需要讀取圖片檔案,這很耗費時間。因此如果在開啟文件時就生成有所的圖形物件,就會導致文件開啟時間過長。所以,最好是當用戶瀏覽至文字中各個圖形物件時,再去生成它們的例項。這時,Proxy模式就有了用武之地。

2.有那些代理:

  • Virtual Proxy(虛擬代理)Virtual Proxy就是本章中學習的Proxy模式。只有當真正需要例項時,它才生成和初始化例項。
  • Remote Proxy(遠端代理)Remote Proxy可以讓我們完全不必在意RealSubject角色是否在遠端網路上,可以如同它在自己身邊一樣(透明性地)呼叫它的 方法。Java的RMI(RemoteMethodInvocation:遠端方法呼叫)就相當於Remote Proxy。
  • Access Proxy Access Proxy 用於在呼叫RealSubject角色的功能時設定訪問限制。例如,這種代理可以只允許指定的使用者呼叫方法,而當其他使用者呼叫方法時則報錯。

  • 靜態代理
    1.使用委託機制代理人只代理他能解決的問題。當遇到他不能解決的問題時,還是會“轉交”給本人去解決。
    這裡的“轉交”就是在本書中多次提到過的“委託”。從PrinterProxy類的print方法中呼叫real.print方法正是這種“委託”的體現。

  1. 為了實現PrinterProxy類可以從printer類中分離出來作為獨立的元件使用,而且只要是實現了Printable介面的類都可以扮演Proxy的角色。需要使用反射例項

private synchronized void realize(String classname) {
    if(real==null){
        try {
            real = ((Printable) Class.forName(classname).newInstance());
        }catch (ClassNotFoundException e){
            System.out.println("沒有發現"+classname);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

  • 動態代理

在java的動態代理機制中,有兩個重要的類或介面,一個是 InvocationHandler(Interface)、另一個則是 Proxy(Class),這一個類和介面是實現我們動態代理所必須用到的。首先我們先來看看java的API幫助文件是怎麼樣對這兩個類進行描述的:
InvocationHandler:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance. 

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

每一個動態代理類都必須要實現InvocationHandler這個介面,並且每個代理類的例項都關聯到了一個handler,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行呼叫。我們來看看
InvocationHandler這個介面的唯一一個方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable

我們看到這個方法一共接受三個引數,那麼這三個引數分別代表什麼呢?


Object invoke(Object proxy, Method method, Object[] args) throws Throwable

proxy:  指代我們所代理的那個真實物件
method:  指代的是我們所要呼叫真實物件的某個方法的Method物件
args:  指代的是呼叫真實物件某個方法時接受的引數

理清職責

  • 實現一個帶名字的印表機
    名字=====》》說明
    Printer || 表示帶名字的印表機的類(本人)
    Printable || Printer和PrinterProxy的共同介面
    PrinterProxy || 表示帶名字的印表機的類(代理人)
    Main ||測試程式行為的類

UML

類圖:

時序圖:

Code

  • Printable
public interface Printable {

    // 設定列印名字
    void setPrinterName(String name);

    // 獲取列印名字
    String getPrinterName();

    // 顯示文字
    void print(String string);
}

  • Printer

···
public class Printer implements Printable{
private String name;

public Printer() {
    heavyjob("正在生成Printer例項");
}

public Printer(String name) {
    this.name = name;
    heavyjob("正在生成Printer例項("+name+")");
}

/**
 * 模擬一個高負載任務
 * @param string
 */
private void heavyjob(String string) {
    System.out.println(string);
    for (int i = 0; i < 5; i++) {
        try {
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.print(".");
    }
}

@Override
public void setPrinterName(String name) {
    this.name=name;
}

@Override
public String getPrinterName() {
    return name;
}

@Override
public void print(String string) {
    System.out.println("==="+name+"===");
    System.out.println(string);
}

}

···

  • PrinterProxy

public class PrinterProxy implements Printable{

    private String name;

    private Printer real;

    /**
     * 不論 setPrinterName 方法和getPrinterName 方法被呼叫多少次,
     * 都不會生成Printer類的例項。只有當真正需要本人時,
     * 才會生成printer類的例項(printerProxy類的呼叫者完全不知道是否生成了本人,也不用在意是否生成了本人)。
     * @param name
     */


    public PrinterProxy(String name) {
        this.name = name;
    }

    @Override
    public synchronized void setPrinterName(String name) {
        if(real!=null){
            real.setPrinterName(name);
        }
        this.name=name;
    }

    @Override
    public String getPrinterName() {
        return name;
    }

    @Override
    public void print(String string) {
            realize();
            real.print(string);
    }

    private synchronized void realize() {
        if(real==null) real=new Printer(name);
    }
}

public class MainT {
    public static void main(String[] args) {

        Printable p=new PrinterProxy("Tom");
        System.out.println("現在是"+p.getPrinterName());
        p.setPrinterName("Cat");
        System.out.println("現在是"+p.getPrinterName());
        p.print("我是 Tomcat");

    }
}
  • 結果:

現在是Tom
現在是Cat
正在生成Printer例項(Cat)
.....
===Cat===
我是 Tomcat

動態代理Code


public class Client {

    public static void main(String[] args) {

        Printable tom = new Printer("Tom");

        DynamicProxy proxy = new DynamicProxy(tom);

        Printable o = (Printable) Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
                tom.getClass().getInterfaces(), proxy);

        System.out.println(o.getClass().getName());

        System.out.println("現在是"+o.getPrinterName());
        o.print("Tomcat");
    }
}




public class DynamicProxy implements InvocationHandler {

    private Object object;

    public DynamicProxy(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("準備執行");

        System.out.println("Method:" + method);

        Object o = method.invoke(object, args);

        System.out.println("執行完畢");
        return o;
    }
}




public interface Printable {

    // 設定列印名字
    void setPrinterName(String name);

    // 獲取列印名字
    String getPrinterName();

    // 顯示文字
    void print(String string);
}



public class Printer implements Printable{
    private String name;

    public Printer() {
        heavyjob("正在生成Printer例項");
    }

    public Printer(String name) {
        this.name = name;
        heavyjob("正在生成Printer例項("+name+")");
    }

    /**
     * 模擬一個高負載任務
     * @param string
     */
    private void heavyjob(String string) {
        System.out.println(string);
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.print(".");
        }
    }

    @Override
    public void setPrinterName(String name) {
        this.name=name;
    }

    @Override
    public String getPrinterName() {
        return name;
    }

    @Override
    public void print(String string) {
        System.out.println("==="+name+"===");
        System.out.println(string);
    }
}