1. 程式人生 > >設計模式-代理模式(和裝飾模式的真正區別)

設計模式-代理模式(和裝飾模式的真正區別)

最近有時間,學習了一下設計模式,發現了一個問題,代理模式(靜態代理)在寫法和結構上,基本和裝飾器是一樣的。

由此引發了對這兩者的真正區別的思考,網上搜索了許許多多的答案(雖然都有一定的道理,個人覺得都沒有說到真正的重點) :

  1 . 有的人說是結構上不同,代理和真實物件之間的的關係通常在編譯時就已經確定了,而裝飾器能夠在執行時遞迴地被構造(我個人完全反對這種說法);

  2 . 裝飾器模式為了增強功能;而代理模式是為了加以控制,代理類對被代理的物件有控制權,決定其執行或者不執行。(大名鼎鼎的菜鳥教程這樣解釋);

  3 . 甚至還有人說裝飾器模式用於新增新方法;而代理模式則用於增強原方法(那為什麼叫代理?)。

代理模式(靜態)與裝飾者雖然在結構上基本上一模一樣,但兩者卻有真正區別,我認為是 : 目的不一樣關注的重心不一樣。

  代理模式目的 : 讓原有物件被代理,我們的目的是讓使用者儘可能的感受不到原有物件,原有物件的行為或額外的動作交由代理物件完成。(完成代理模式的真正意義)

  裝飾器模式目的 : 讓原有物件被增強,我們的目的通常是得到由原有物件被增強後的裝飾器物件行為。(完成裝飾器模式的真正意義)

  代理模式關注重心 : 主要功能不變,代理物件只是幫忙代理或稍加擴充套件原有物件的行為,功能上主要關心原有物件所具有的行為。(最終主要功能仍然由原有物件決定)

  裝飾器模式關注重心 : 主要功能增強,使用裝飾器目的就是為了增強,功能上更關心裝飾增加後的行為。(最終主要功能由裝飾物件決定)

靜態代理

靜態代理的角色分為 : 抽象行為角色,委託人,代理人。基本寫法如下 : 

  抽象行為角色 : 是委託人和代理人的共同介面。這裡我們叫它抽象主題(Subject) : 

package name.ealen.proxy.designPattern.staticProxy;

/**
 * Created by EalenXie on 2018/11/2 10:16.
 */
public interface Subject {

    public void operation();

}

  委託人 : 也就是我上面一直說的原有物件,真正被代理的物件,也叫做代理元。這裡我們叫它真實主題 (RealSubject): 

package name.ealen.proxy.designPattern.staticProxy;

/**
 * Created by EalenXie on 2018/11/2 10:17.
 */
public class RealSubject implements Subject {

    @Override
    public void operation() {
        System.out.println("真實物件 : 重要操作");
    }

}

  代理人 : 代理角色,由它去代理原有物件。它包含被代理物件的引用,這裡叫它代理主題(ProxySubject):  

package name.ealen.proxy.designPattern.staticProxy;

/**
 * Created by EalenXie on 2018/11/2 10:18.
 */
public class ProxySubject implements Subject {
    private Subject subject;

    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    /**
     * 目的 : 代理真實物件完成方法呼叫,代理可以進行相對不重要的行為擴充套件
     */
    @Override
    public void operation() {
        before();
        subject.operation();
        after();
    }

    private void after() {
        System.out.println("代理人 : 真實物件的操作完成了");
    }

    private void before() {
        System.out.println("代理人 : 開始完成真實物件的操作");
    }
}

測試程式碼 : 

 /**
     * 靜態代理
     */
    @Test
    public void staticProxy() {

        Subject realSubject = new RealSubject();              //一個真實物件

        Subject proxy = new ProxySubject(realSubject);        //一個代理人,指定要代理的真實物件,型別只能是Subject及其子類

        proxy.operation();                                    //整個操作由代理人幫真實物件完成,代理人還做了操作說明
    }

  結果如下 : 

  

動態代理

  動態代理主要依賴Java反射機制實現,基本寫法如下 : 

  抽象行為角色 : 是委託人和代理人的共同介面。這裡我們叫它抽象主題(DynamicSubject) : 

package name.ealen.proxy.designPattern.dynamicProxy;

/**
 * Created by EalenXie on 2018/11/2 15:35.
 */
public interface DynamicSubject {
    public void operation() ;
}

  一個被代理物件 ,這裡叫他真實物件(DynamicRealSubject) : 

package name.ealen.proxy.designPattern.dynamicProxy;

/**
 * Created by EalenXie on 2018/11/2 12:56.
 */
public class DynamicRealSubject implements DynamicSubject {
    @Override
    public void operation() {
        System.out.println("真實物件 : 重要操作");
    }
}

  代理的呼叫處理類(ProxyHandler) ,主要實現反射的介面 InvocationHandler ,這裡可以看出該處理類設計上和被代理物件並沒有任何的直接聯絡

package name.ealen.proxy.designPattern.dynamicProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Created by EalenXie on 2018/11/2 12:57.
 * 代理的呼叫處理類
 */
public class ProxyHandler implements InvocationHandler {

    private Object realSubject;     //指定被代理的真實物件

    public ProxyHandler(Object realSubject) {
        this.realSubject = realSubject;
    }

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

        before();                                           //自定義邏輯

        Object result = method.invoke(realSubject, args);   //呼叫真實物件的操作

        after();                                            //自定義邏輯

        return result;

    }

    private void after() {
        System.out.println("代理人 : 真實物件的操作完成了");
    }

    private void before() {
        System.out.println("代理人 : 開始完成真實物件的操作");
    }
}

  那麼如何得到我們的代理物件呢?答案是基於反射類Proxy,既然是動態代理,那麼代理的呼叫處理類,是可以代理任何型別的物件。請看如下測試類 : 

/**
     * 動態代理,基於反射類實現
     */
    @Test
    public void dynamicProxy() {

        //一個真實物件
        DynamicSubject subject = new DynamicRealSubject();

        //動態代理處理邏輯 ,基於反射類InvocationHandler實現,指定要代理的真實物件,可以是任何型別
        InvocationHandler proxyHandler = new ProxyHandler(subject);

        //一個代理人,例項化基於反射類Proxy實現。
        DynamicSubject proxy = (DynamicSubject) Proxy.newProxyInstance(DynamicSubject.class.getClassLoader(), subject.getClass().getInterfaces(), proxyHandler);

        //整個操作由代理人幫真實物件完成,代理人還做了操作說明
        proxy.operation();

    }

  只需要在ProxyHandler中動態的傳入任何我們需要代理的物件,然後Proxy呼叫newProxyInstance,強轉即可得到我們的代理物件,結果如下 :