一步一步學設計模式——代理模式

代理模式是一個我們在編程中經常用到的設計模式,它的目的是爲其它對象提供一種代理以控制對這個對象的訪問

1. 生活實例

每年到快要過年的時候,搶票都是一個十分艱辛的任務。在互聯網技術與網絡購票還沒有特別成熟時,大家都需要在售票點排隊去買票。很多學校上學的學生都沒有時間在剛售票的時候就去排隊等票,因此出現了代排隊的黃牛,他們每次多收學生25%的錢然後替學生排隊買票。

再回想一下我們代理的模式的概念,在上面的例子中就存在一個典型的代理模式。代理模式目的是爲其它對象(學生)提供一種代理(黃牛)以控制對這個對象(火車票)的訪問。

下面我們就將上面這個生活實例轉變爲代碼。

2. 生活實例代碼

  • 首先我們建立火車票類:
package com.wxueyuan.DesignPettern.StaticProxy;

/**
 * Author: Jesmin
 * Description: 火車票實體類
 *
 */
public class Ticket {
    private double price;

    public Ticket(double price) {
        this.price = price;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

}
  • 由於由於黃牛和學生都需要買票,我們抽象出一個Operation接口,提供一個公共方法buyTicket用來表示買票操作
package com.wxueyuan.DesignPettern.StaticProxy;

/**
 * Author: Jesmin
 * Description: 由於黃牛和學生都需要買票,我們抽象出一個Operation接口,提供一個公共方法buyTicket
 *              用來表示買票操作
 *
 */
public interface Operation {
    void buyTicket(Ticket t);
}
  • 建立黃牛實體類Scalper
package com.wxueyuan.DesignPettern.StaticProxy;


/**
 * Author: Jesmin
 * Description: “黃牛”實體類,在黃牛的購票操作中,他實際上分成了4步,先收錢,然後排隊,然後購票,最後將票交給學生
 *
 */
public class Scalper implements Operation{

    private Student  realConsumer;

    public Scalper(Student s) {
        realConsumer = s;
    }

    @Override
    public void buyTicket(Ticket t) {
        // TODO Auto-generated method stub
        System.out.println("黃牛收取購票者的錢");
        System.out.println("黃牛連夜排隊");
        realConsumer.buyTicket(t);
        System.out.println("黃牛將票交給學生");
    }

}
  • 建立學生實體類Student
package com.wxueyuan.DesignPettern.StaticProxy;

public class TicketConsumer implements Operation{

    @Override
    public void Student(Ticket t) {
        // TODO Auto-generated method stub
        System.out.println("學生買到一張票,票價爲"+t.getPrice());
    }

}
  • 最後進行測試
package com.wxueyuan.DesignPettern.StaticProxy;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //學生需要購買的ticket實例
        Ticket t = new Ticket(200);
        //學生黃牛代理實例
        Scalper scalper = new Scalper(new Student());
        //黃牛爲學生執行買票操作
        scalper.buyTicket(t);
    }
}

執行結果爲:
黃牛收取購票者的錢
黃牛連夜排隊
購票者買到一張票,票價爲200.0
黃牛將票交給學生

分析一下上面的代碼,黃牛實體類中獲得了購票者的實例,因此他能夠在他的購票操作中執行購票者的購票操作,以及一些額外的操作。

下面我們一起來分析一下在代理模式中有哪些角色:

  • 抽象角色:通常是根據代理對象與實際對象相同的行爲所抽象出的接口,由於代理對象與實際對象都實現了這個接口,代理對象就可以在任何實際對象調用方法的地方替換它。

  • 代理角色:代理對象內部含有實際對象的引用,因此可以執行實際對象的操作。代理對象可以在執行實際對象操作時,附加其他的操作,相當於對實際對象的操作進行封裝。

  • 委託角色:定義了代理對象所代表的目標對象。代理角色所代表的委託對象,是我們最終要引用的對象。

3.代理模式的優缺點

  • 優點:能夠在不修改方法源碼的情況下,對方法進行附加操作;可以通過代理模式將核心業務代碼與非核心業務代碼解耦。
  • 缺點:1.由於代理類和委託類實現了相同的接口,如果接口增加一個方法,除了所有實現類需要實現這個方法外,所有代理類也需要實現此方法,增加了代碼維護的複雜度。 2.代理對象只服務於一種類型的對象,以我們上面的黃牛類來說,它只能作爲學生類的代理(因爲代理類中需要持有委託類的引用),假如我們的業務情況突然發生了變化,老師類也需要通過黃牛來買票,那我們就不得不再建立另一個代理類TeacherScalper,並持有Teacher類的引用,來幫助老師進行買票。因此在有大量的實際類需要被代理的情況下,這種代理模式並不是很適合,會有大量的冗餘代碼。

  • 老師黃牛代理

package com.wxueyuan.DesignPettern.StaticProxy;


/**
 * Author: Jesmin
 * Description: 老師黃牛代理類,在黃牛的購票操作中,他實際上分成了4步,先收錢,然後排隊,然後購票,最後將票交給老師
 *
 */
public class TeacherScalper implements Operation{

    private Teacher realConsumer ;

    public TeacherScalper(Teacher t) {
        realConsumer = t;
    }

    @Override
    public void buyTicket(Ticket t) {
        // TODO Auto-generated method stub
        System.out.println("黃牛收取購票者的錢");
        System.out.println("黃牛連夜排隊");
        realConsumer.buyTicket(t);
        System.out.println("黃牛將票交給購票者");
    }

}
  • 老師實體類
package com.wxueyuan.DesignPettern.StaticProxy;

public class Teacher implements Operation{
    @Override
    public void buyTicket(Ticket t) {
        // TODO Auto-generated method stub
        System.out.println("老師買到一張票,票價爲"+t.getPrice());
    }
}
  • 測試類
package com.wxueyuan.DesignPettern.StaticProxy;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //學生需要購買的ticket實例
        Ticket t = new Ticket(200);
        //學生黃牛代理實例
        Scalper scalper = new Scalper(new Student());
        //黃牛爲學生執行買票操作
        scalper.buyTicket(t);

        //老師需要購買的ticket實例
        Ticket adultTicket = new Ticket(300);
        //老師黃牛代理實例
        TeacherScalper ts = new TeacherScalper(new Teacher());
        //黃牛爲老師執行買票操作
        ts.buyTicket(adultTicket);
    }

}

由於這種代理模式,提前已經抽象好了代理角色與委託角色共同的行爲接口,並且代理角色在編譯時已經確定了與委託類之間的委託關係,因此這種代理模式也被稱爲靜態代理

考慮到由多個委託類時,需要有多個代理類的情況,我們自然會想有沒有更加好的辦法,能夠通過一個代理類完成全部的代理功能呢?答案是有的,它就是動態代理

4.動態代理

在靜態代理中,一個代理只能代理一種類型,而且是在編譯期間就已經確定被代理的對象。而動態代理是在運行時,通過反射機制實現動態代理,並且能夠代理各種類型的對象。

下面我們以上面的老師,學生買火車票爲例,使用動態代理去完成這個例子,下面例子中使用到的Ticket實體類,Teacher實體類,Student實體類與Operation抽象接口均與靜態代理中的相同,故不再贅述。

  • 首先我們需要實現InvocationHandler接口:
package com.wxueyuan.DesignPettern.DynamicProxy;

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

public class TicketOperationInvocationHandler implements InvocationHandler {

    //將需要代理的委託對象傳入Handler中
    private Object target; 

    public TicketOperationInvocationHandler(Object target) {
        this.target = target;
    }

    //獲得幫助購票者買票的代理
    public Object getProxy() {
        return Proxy.newProxyInstance(Thread.currentThread()  
                .getContextClassLoader(), target.getClass().getInterfaces(),  
                this);  
    }

    //實際上黃牛執行的購票操作
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("黃牛收取購票者的錢");
        System.out.println("黃牛連夜排隊");
        Object ret = method.invoke(target, args);
        System.out.println("黃牛將票交給購票者");
        return ret;
    }

}
  • 接下來我們來測試一下我們的代理:
package com.wxueyuan.DesignPettern.DynamicProxy;

import com.wxueyuan.DesignPettern.StaticProxy.Operation;
import com.wxueyuan.DesignPettern.StaticProxy.Student;
import com.wxueyuan.DesignPettern.StaticProxy.Teacher;
import com.wxueyuan.DesignPettern.StaticProxy.Ticket;

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        //學生需要購買的ticket實例
        Ticket studentTicket = new Ticket(200);
        //老師需要購買的ticket實例
        Ticket adultTicket = new Ticket(300);

        //創建爲學生買票的黃牛代理
        Operation studentProxy = (Operation) new TicketOperationInvocationHandler(new Student()).getProxy();
        studentProxy.buyTicket(studentTicket);

        System.out.println("---------------------------");

        //創建爲老師買票的黃牛代理
        Operation teacherProxy = (Operation) new TicketOperationInvocationHandler(new Teacher()).getProxy();
        teacherProxy.buyTicket(adultTicket);
    }

}

執行結果爲:

黃牛收取購票者的錢
黃牛連夜排隊
學生買到一張票,票價爲200.0
黃牛將票交給購票者

黃牛收取購票者的錢
黃牛連夜排隊
老師買到一張票,票價爲300.0
黃牛將票交給購票者

現在讓我們分析一下動態代理的優點:1.與靜態代理相比我們不需要爲每一個需要被代理的委託類去建一個對應的代理類(上例中的TeacherScalper和Scalper),我們可以直接用抽象接口生成代理實例來代替不同的委託類去完成任務。2.動態代理另一個優點就是將公共接口中聲明的所有的方法都被轉移到一個集中的方法中去處理(invocke()方法),也就是說我們可以在invoke方法中爲所有接口中的方法附加相同的額外操作,比如記錄在方法執行前記錄當前時間,方法執行後記錄當前時間,用差值來獲得每個方法的實際執行時間等等,這其實就是AOP(面向切面編程)的基本原理

到這裏,博主就爲大家介紹了代理設計模式,以及靜態代理與動態代理之間的優缺點,有的同學可能對動態代理的使用以及原理還有一些困惑,歡迎大家關注博主的另一篇文章《Java源碼剖析——動態代理的實現原理》

本節博客的源碼全部放在這裏,歡迎大家下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章