自己平時用到的設計模式總結


 

平時自己會在寫代碼的過程中嘗試使用一些常用的設計模式,對於代碼使用設計模式來說,其實並無強制性的要求,如果強中注入一些設計模式,可能會使得代碼變了味道,所以在使用的涉及模式的時候,

最好還是能用到最適合的場景中,下面結合項目中的東西,總結一下這些設計模式的使用原理以及場景:

 設計模式一:單例模式:

作為對象的創建模式,單例模式確保其某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例,這個類稱為單例類。單例模式有以下特點:

1、單例類只能有一個實例;

2、單例類必須自己創建自己的唯一實例,也就是說構造函數是私有的;

3、單例類必須給其他所有對象提供這一實例,通過一個統一的結構提供;

下面看一下單例模式的三種寫法,除了這三種寫法,靜態內部類的方式、靜態代碼塊的方式、enum枚舉的方式也都可以,不過異曲同工,這三種方式就不寫了。

首先聲明就是 在我們項目工程中 我們完全不用使用懶漢式 因為有鎖使用的地方就有效率低的存在; 

餓漢式

顧名思義,餓漢式,就是使用類的時候不管用的是不是類中的單例部分,都直接創建出單例類,看一下餓漢式的寫法:

public class SingleEager {
    
    public static SingleEager se = new SingleEager();
    
    public static SingleEager getInstance()
    {
        return se;
    }
  private SingleRager()
  {}
}

這就是餓漢式單例模式的寫法,也是一種比較常見的寫法。這種寫法會不會造成競爭,引發線程安全問題呢?答案是不會。

可能有人會覺得奇怪:第3行,CPU執行線程A,實例化一個EagerSingleton,沒有實例化完,CPU就從線程A切換到線程B了,線程B此時也實例化這個EagerSingleton,然后EagerSingleton被實例化出來了兩次,有兩份內存地址,不就有線程安全問題了嗎?

沒關系,我們完全不需要擔心這個問題,JDK已經幫我們想到了。Java虛擬機2:Java內存區域及對象文中可以看一下對象創建這一部分,沒有寫得很詳細,其實就是"虛擬機采用了CAS配上失敗重試的方式保證更新操作的原子性和TLAB兩種方式來解決這個問題"。

 

懶漢式

同樣,顧名思義,這個人比較懶,只有當單例類用到的時候才會去創建這個單例類,看一下懶漢式的寫法:

public class LazySingleton
{
    private static LazySingleton instance = null;
    
    private LazySingleton()
    {
    }
    
    public static LazySingleton getInstance()
    {
        if (instance == null)
            instance = new LazySingleton();
        return instance;
    }
}

這種寫法基本不用,因為這是一種線程非安全的寫法。試想,線程A初次調用getInstance()方法,代碼走到第12行,線程此時切換到線程B,線程B走到12行,看到instance是null,就new了一個LazySingleton出來,這時切換回線程A,線程A繼續走,也new了一個LazySingleton出來。這樣,單例類LazySingleton在內存中就有兩份引用了,這就違背了單例模式的本意了。

可能有人會想,CPU分的時間片再短也不至於getInstance()方法只執行一個判斷就切換線程了吧?問題是,萬一線程A調用LazySingleton.getInstance()之前已經執行過別的代碼了呢,走到12行的時候剛好時間片到了,也是很正常的。

 

雙檢鎖【其實這個地方叫做 帶鎖的雙檢懶漢式單利模式】

既然懶漢式是非線程安全的,那就要改進它。最直接的想法是,給getInstance方法加鎖不就好了,但是我們不需要給方法全部加鎖啊,只需要給方法的一部分加鎖就好了。

雙檢的目的是為了提高效率,當第一次線程創建了實例對象后,后邊進入的線程通過判斷第一個是否為null,可以直接不用走入加鎖的代碼區;

基於這個考慮,引入了雙檢鎖(Double Check Lock,簡稱DCL)的寫法:

public class DoubleCheckLockSingleton
{
    private static DoubleCheckLockSingleton instance = null;
    
    private DoubleCheckLockSingleton()
    {
        
    }
    
    public static DoubleCheckLockSingleton getInstance()
    {
        if (instance == null)
        {
            synchronized (DoubleCheckLockSingleton.class)
            {
                if (instance == null)
                    instance  = new DoubleCheckLockSingleton();
            }
        }
        return instance;
    }
}

 

雙檢鎖的寫法是不是線程安全的呢?是的,至於為什么,不妨以分析懶漢式寫法的方式分析一下雙檢鎖的寫法。

線程A初次調用DoubleCheckLockSingleton.getInstance()方法,走12行,判斷instance為null,進入同步代碼塊,此時線程切換到線程B,線程B調用DoubleCheckLockSingleton.getInstance()方法,由於同步代碼塊外面的代碼還是異步執行的,所以線程B走12行,判斷instance為null,等待鎖。結果就是線程A實例化出了一個DoubleCheckLockSingleton,釋放鎖,線程B獲得鎖進入同步代碼塊,判斷此時instance不為null了,並不實例化DoubleCheckLockSingleton。這樣,單例類就保證了在內存中只存在一份。

 

單例模式在Java中的應用及解讀

Runtime是一個典型的例子,看下JDK API對於這個類的解釋"每個Java應用程序都有一個Runtime類實例,使應用程序能夠與其運行的環境相連接,可以通過getRuntime方法獲取當前運行時。應用程序不能創建自己的Runtime類實例。",這段話,有兩點很重要:

1、每個應用程序都有一個Runtime類實例

2、應用程序不能創建自己的Runtime類實例

只有一個、不能自己創建,是不是典型的單例模式?看一下,Runtime類的寫法:

public class Runtime {
    private static Runtime currentRuntime = new Runtime(); //使用餓漢式

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance 
     * methods and must be invoked with respect to the current runtime object. 
     * 
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() { 
    return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

    ...
}

后面的就不黏貼了,到這里已經足夠了,看到Runtime使用getRuntime()方法並讓構造方法私有保證程序中只有一個Runtime實例且Runtime實例不可以被用戶創建。

 

單例模式的好處

作為一種重要的設計模式,單例模式的好處有:

1、控制資源的使用,通過線程同步來控制資源的並發訪問

2、控制實例的產生,以達到節約資源的目的

3、控制數據的共享,在不建立直接關聯的條件下,讓多個不相關的進程或線程之間實現通信

 

設計模式二:工廠模式:

一、簡單工廠模式

public abstract class INoodles {
    /**
     * 描述每種面條啥樣的
     */
    public abstract void desc();
}

先來一份蘭州拉面(具體的產品類):

public class LzNoodles extends INoodles {
    @Override
    public void desc() {
        System.out.println("蘭州拉面 上海的好貴 家里才5 6塊錢一碗");
    }
}

程序員加班必備也要吃泡面(具體的產品類):

public class PaoNoodles extends INoodles {
    @Override
    public void desc() {
        System.out.println("泡面好吃 可不要貪杯");
    }
}

還有我最愛吃的家鄉的干扣面(具體的產品類):

public class GankouNoodles extends INoodles {
    @Override
    public void desc() {
        System.out.println("還是家里的干扣面好吃 6塊一碗");
    }
}

准備工作做完了,我們來到一家“簡單面館”(簡單工廠類),菜單如下:

public class SimpleNoodlesFactory {
    public static final int TYPE_LZ = 1;//蘭州拉面
    public static final int TYPE_PM = 2;//泡面
    public static final int TYPE_GK = 3;//干扣面

    public static INoodles createNoodles(int type) {
        switch (type) {
            case TYPE_LZ:
                return new LzNoodles();
            case TYPE_PM:
                return new PaoNoodles();
            case TYPE_GK:
            default:
                return new GankouNoodles();
        }
    }
}

優點:

1 它是一個具體的類,非接口 抽象類。有一個重要的create()方法,利用if或者 switch創建產品並返回。

2 create()方法通常是靜態的,所以也稱之為靜態工廠。

缺點:

1 擴展性差(我想增加一種面條,除了新增一個面條產品類,還需要修改工廠類方法)

2 不同的產品需要不同額外參數的時候 不支持。

 

工廠方法模式:

package com.demoFound.factoryMethod.factory;  
import com.demoFound.factoryMethod.message.IMyMessage;  
/** 
 * 工廠方法模式_工廠接口 
 */  
public interface IMyMessageFactory {  
    public IMyMessage createMessage(String messageType);  
}  
/** 
 * 工廠方法模式_工廠實現 
 */  
public class MyMessageFactory implements IMyMessageFactory {  
    @Override  
    public IMyMessage createMessage(String messageType) {  
        // 這里的方式是:消費者知道自己想要什么產品;若生產何種產品完全由工廠決定,則這里不應該傳入控制生產的參數。  
        IMyMessage myMessage;  
        Map<String, Object> messageParam = new HashMap<String, Object>();  
        // 根據某些條件去選擇究竟創建哪一個具體的實現對象,條件可以傳入的,也可以從其它途徑獲取。  
        // sms  
        if ("SMS".equals(messageType)) {  
            myMessage = new MyMessageSms();  
            messageParam.put("PHONENUM", "123456789");  
        } else  
        // OA待辦  
        if ("OA".equals(messageType)) {  
            myMessage = new MyMessageOaTodo();  
            messageParam.put("OAUSERNAME", "testUser");  
        } else  
        // email  
        if ("EMAIL".equals(messageType)) {  
            myMessage = new MyMessageEmail();  
            messageParam.put("EMAIL", "test@test.com");  
        } else  
        // 默認生產email這個產品  
        {  
            myMessage = new MyMessageEmail();  
            messageParam.put("EMAIL", "test@test.com");  
        }  
        myMessage.setMessageParam(messageParam);  
        return myMessage;  
    }  
} 

產品:

package com.demoFound.factoryMethod.message;   
import java.util.Map;  
/** 
 * 工廠方法模式_產品接口  
 * @author popkidorc  
 */  
public interface IMyMessage {  
  
    public Map<String, Object> getMessageParam();  
    public void setMessageParam(Map<String, Object> messageParam);  
    public void sendMesage() throws Exception;// 發送通知/消息  
  
}  

 

package com.demoFound.factoryMethod.message;  
import java.util.Map;  
  
/** 
 * 工廠方法模式_虛擬產品類 
 * @author popkidorc 
 */  
public abstract class MyAbstractMessage implements IMyMessage {  
  
    private Map<String, Object> messageParam;// 這里可以理解為生產產品所需要的原材料庫。最好是個自定義的對象,這里為了不引起誤解使用Map。  
    @Override  
    public Map<String, Object> getMessageParam() {  
        return messageParam;  
    }  
  
    @Override  
    public void setMessageParam(Map<String, Object> messageParam) {  
        this.messageParam = messageParam;  
    }  
}  
package com.demoFound.factoryMethod.message;  
/** 
 * 工廠方法模式_email產品 
 * @author popkidorc 
 */  
public class MyMessageEmail extends MyAbstractMessage {  
  
    @Override  
    public void sendMesage() throws Exception {  
        // TODO Auto-generated method stub  
        if (null == getMessageParam() || null == getMessageParam().get("EMAIL")  
                || "".equals(getMessageParam().get("EMAIL"))) {  
            throw new Exception("發送短信,需要傳入EMAIL參數");// 為了簡單起見異常也不自定義了  
        }// 另外郵件內容,以及其他各種協議參數等等都要處理  
  
        System.out.println("我是郵件,發送通知給" + getMessageParam().get("EMAIL"));  
    }  
  
}  

 

 
package com.demoFound.factoryMethod.message;  
  
/** 
 * 工廠方法模式_oa待辦產品 
 */  
public class MyMessageOaTodo extends MyAbstractMessage {  
  
    @Override  
    public void sendMesage() throws Exception {  
        // TODO Auto-generated method stub  
        if (null == getMessageParam()  
                || null == getMessageParam().get("OAUSERNAME")  
                || "".equals(getMessageParam().get("OAUSERNAME"))) {  
            throw new Exception("發送OA待辦,需要傳入OAUSERNAME參數");// 為了簡單起見異常也不自定義了  
        }// 這里的參數需求就比較多了不一一處理了  
  
        System.out  
                .println("我是OA待辦,發送通知給" + getMessageParam().get("OAUSERNAME"));  
    }  
  
}  
package com.demoFound.factoryMethod.message;  
  
/** 
 * 工廠方法模式_sms產品 
 */  
public class MyMessageSms extends MyAbstractMessage {  
  
    @Override  
    public void sendMesage() throws Exception {  
        // TODO Auto-generated method stub  
        if (null == getMessageParam()  
                || null == getMessageParam().get("PHONENUM")  
                || "".equals(getMessageParam().get("PHONENUM"))) {  
            throw new Exception("發送短信,需要傳入PHONENUM參數");// 為了簡單起見異常也不自定義了  
        }// 另外短信信息,以及其他各種協議參數等等都要處理  
        System.out.println("我是短信,發送通知給" + getMessageParam().get("PHONENUM"));  
    }  
  
} 

消費者:

package com.demoFound.factoryMethod;  
  
import com.demoFound.factoryMethod.factory.IMyMessageFactory;  
import com.demoFound.factoryMethod.factory.MyMessageFactory;  
import com.demoFound.factoryMethod.message.IMyMessage;  
  
/** 
 * 工廠方法模式_消費者類 
 * @author popkidorc 
 */  
public class MyFactoryMethodMain {  
  
    public static void main(String[] args) {  
        IMyMessageFactory myMessageFactory = new MyMessageFactory();  
        IMyMessage myMessage;  
        // 對於這個消費者來說,不用知道如何生產message這個產品,耦合度降低  
        try {  
            // 先來一個短信通知  
            myMessage = myMessageFactory.createMessage("SMS");  
            myMessage.sendMesage();  
  
            // 來一個oa待辦  
            myMessage = myMessageFactory.createMessage("OA");  
            myMessage.sendMesage();  
  
            // 來一個郵件通知  
            myMessage = myMessageFactory.createMessage("EMAIL");  
            myMessage.sendMesage();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}  

 

抽象工廠方法:

抽象工廠模式代碼

復制代碼
interface IProduct1 {  
    public void show();  
}  
interface IProduct2 {  
    public void show();  
}  
  
class Product1 implements IProduct1 {  
    public void show() {  
        System.out.println("這是1型產品");  
    }  
}  
class Product2 implements IProduct2 {  
    public void show() {  
        System.out.println("這是2型產品");  
    }  
}  
  
interface IFactory {  
    public IProduct1 createProduct1();  
    public IProduct2 createProduct2();  
}  
class Factory implements IFactory{  
    public IProduct1 createProduct1() {  
        return new Product1();  
    }  
    public IProduct2 createProduct2() {  
        return new Product2();  
    }  
}  
  
public class Client {  
    public static void main(String[] args){  
        IFactory factory = new Factory();  
        factory.createProduct1().show();  
        factory.createProduct2().show();  
    }  
} 
復制代碼

抽象工廠模式的優點

        抽象工廠模式除了具有工廠方法模式的優點外,最主要的優點就是可以在類的內部對產品族進行約束。所謂的產品族,一般或多或少的都存在一定的關聯,抽象工廠模式就可以在類內部對產品族的關聯關系進行定義和描述,而不必專門引入一個新的類來進行管理。

抽象工廠模式的缺點

       產品族的擴展將是一件十分費力的事情,假如產品族中需要增加一個新的產品,則幾乎所有的工廠類都需要進行修改。所以使用抽象工廠模式時,對產品等級結構的划分是非常重要的。

 

設計模式三:觀察者模式:

/**
* @Auther: maxw
* @Date: 2018/11/10 16:14
* @Description:觀察者模式
* 基本概念:
* 觀察者模式屬於行為型模式,其意圖是定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。
* 這一個模式的關鍵對象是目標(Subject)和觀察者(Observer)。一個目標可以有任意數目的依賴它的觀察者,一旦目標的狀態發生改變,所有的觀察者都得到通知,
* 作為對這個通知的響應,每個觀察者都將查詢目標以使其狀態與目標的狀態同步。
* 適用場景:
* 觀察者模式,用於存在一對多依賴關系的對象間,當被依賴者變化時,通知依賴者全部進行更新。
* 因此,被依賴者,應該有添加/刪除依賴者的方法,且可以將添加的依賴者放到一個容器中;且有一個方法去通知依賴者進行更新。
*/
public class Test2 {
public static void main(String args[]){
Vector<Student> students =new Vector<Student>();
Teacher teacher =new Teacher();
for(int i=0;i<5;i++){
Student student =new Student(teacher,"student"+i);
students.add(student);
teacher.addStudent(student);
}
for(Student student :students){
student.showTelphone();
}
teacher.setTelphone("100000");
for(Student student :students){
student.showTelphone();
}
teacher.setTelphone("111111");
for(Student student :students){
student.showTelphone();
}
}
}
class Teacher{
private String telphone;
private Vector<Student> students;

public Teacher() {
telphone = "";
students = new Vector();
}
//添加學生
public void addStudent(Student student){
students.add(student);
}
//移除學生
public void removeStudent(Student student){
students.remove(student);
}

public String getTelphone() {
return telphone;
}
//更新手機號碼
public void setTelphone(String telphone) {
this.telphone = telphone;
notice(); //設置手機號碼的時候通知學生 關鍵
}

private void notice(){
for(Student student : students){
student.updateTelphone();
}
}
}
class Student{
private String teachPhone;
private Teacher teacher;
private String name;

public Student(Teacher teacher, String name) {
this.teacher = teacher;
this.name = name;
}
public void showTelphone(){
System.out.println(name+"老師電話為:"+teachPhone);
}
public void updateTelphone(){
teachPhone = teacher.getTelphone();
}
}

 

 

 設計模式四:模板模式

博客有一篇文章專門講了模板方法的使用;詳見:https://www.cnblogs.com/gxyandwmm/p/9375843.html

 

設計模式五:策略模式:

使用場景實例:

  假設某個網站銷售各種書籍,對初級會員沒有提供折扣,對中級會員提供每本10%的促銷折扣,對高級會員提供每本20%的促銷折扣。

  折扣是根據以下的3個算法中的1個進行的:

  算法1:對初級會員沒有提供折扣。

  算法2:對中級會員提供10%的促銷折扣。

  算法3:對高級會員提供20%的促銷折扣。

  該實例的UML圖:

  

折扣接口

1 public interface MemberStrategy {
2     /**
3      * 計算圖書的價格
4      * @param booksPrice    圖書的原價
5      * @return    計算出打折后的價格
6      */
7     public double calcPrice(double booksPrice);
8 }

 

初級會員折扣實現類

 1 public class PrimaryMemberStrategy implements MemberStrategy {
 2 
 3     @Override
 4     public double calcPrice(double booksPrice) {
 5         
 6         System.out.println("對於初級會員的沒有折扣");
 7         return booksPrice;
 8     }
 9 
10 }

  

中級會員折扣實現類

 1 public class IntermediateMemberStrategy implements MemberStrategy {
 2 
 3     @Override
 4     public double calcPrice(double booksPrice) {
 5 
 6         System.out.println("對於中級會員的折扣為10%");
 7         return booksPrice * 0.9;
 8     }
 9 
10 }

 

  高級會員折扣實現類

1 public class AdvancedMemberStrategy implements MemberStrategy {
2 
3     @Override
4     public double calcPrice(double booksPrice) {
5         
6         System.out.println("對於高級會員的折扣為20%");
7         return booksPrice * 0.8;
8     }
9 }

 

  價格類【相當於中間處理類 拿到策略接口 進行區分不同的子類策略】

 1 public class Price {
 2     // 持有一個具體的策略對象
 3     private MemberStrategy strategy;
 4     /**
 5      * 構造函數,傳入一個具體的策略對象
 6      * @param strategy    具體的策略對象
 7      */
 8     public Price(MemberStrategy strategy){
 9         this.strategy = strategy;
10     }
11     
12     /**
13      * 計算圖書的價格
14      * @param booksPrice    圖書的原價
15      * @return    計算出打折后的價格
16      */
17     public double quote(double booksPrice){
18         return this.strategy.calcPrice(booksPrice);
19     }
20 }

 

客戶端【客戶端需要知道所有的策略子類才可以進行決定使用哪一個】

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         // 選擇並創建需要使用的策略對象
 5         MemberStrategy strategy = new AdvancedMemberStrategy();
 6         // 創建環境
 7         Price price = new Price(strategy);
 8         // 計算價格
 9         double quote = price.quote(300);
10         System.out.println("圖書的最終價格為:" + quote);
11     }
13 }

  策略模式的重心不是如何實現算法,而是如何組織、調用這些算法,從而讓程序結構更靈活,具有更好的維護性和擴展性。策略算法是相同行為的不同實現。在運行期間,策略模式在每一個時刻只能使用一個具體的策略實現對象。把所有的具體策略實現類的共同公有方法封裝到抽象類里面,將代碼向繼承等級結構的上方集中。

  

  

  策略模式優點:

  1 通過策略類的等級結構來管理算法族。

  2 避免使用將采用哪個算法的選擇與算法本身的實現混合在一起的多重條件(if-else if-else)語句。

  策略模式缺點:

  1 客戶端必須知道所有的策略類,並自行決定使用哪一個策略類。

  2 由於策略模式把每個算法的具體實現都單獨封裝成類,針對不同的情況生成的對象就會變得很多。

從上邊看 策略模式和模板模式很想,下面看一下他們的具體區別:

 策略模式,是指將某個類運行過程中,把核心算法提取出來、封裝,使得對象在針對不同的情境可以方便地更換執行策略(算法),使得算法獨立變化而不影響客戶端的使用。

 模板模式從一個例子引入:

     泡茶的流程是:燒水,拿茶壺,放入茶葉,沖泡;

     泡咖啡的流程:燒水,拿咖啡壺,放入可可,沖泡。

     二者的流程是不是非常接近?,難道我們要寫兩個類去區分他們?當然不是,模板模式就是為了解決這個問題而存在的。 所以,模板方法是指:定義一個算法流程的骨架,把一些可變節點延遲到具體的子類中去執行,

  • 執行流程:模板模式一定是按照一定次序執行程序的,任何一個節點的重載不會影響到這個次序;策略模式則不一定,它只提供了某個情景下的執行策略,是為了優化這個情景而制定的,為了達到需求,只要入口相同,執行順序不做需求;
  • 可變節點:模板模式可變節點大於等於一;策略模式被重載的節點一般唯一;
  • 重載側重點:模板模式要求算法流程中的某幾個節點會被替換,但順序不變;策略模式中整個算法都是可以被替換的。

設計模式六:動態代理模式:

JDK動態代理的實現
  JDK動態代理的思維模式與之前的一般模式是一樣的,也是面向接口進行編碼,創建代理類將具體類隱藏解耦,不同之處在於代理類的創建時機不同,動態代理需要在運行時因需實時創建。
  第一步:定義總接口Iuser.java

1 package ceshi1;
2 public interface Iuser {
3   void eat(String s);
4 }

  第二步:創建具體實現類UserImpl.java

1 package ceshi1;
2 public class UserImpl implements Iuser {
3   @Override
4   public void eat(String s) {
5     System.out.println("我要吃"+s);
6   }
7 }

  第三步:創建實現InvocationHandler接口的代理類

 1 package ceshi1;
 2 import java.lang.reflect.InvocationHandler;
 3 import java.lang.reflect.Method;
 4 public class DynamicProxy implements InvocationHandler {
 5   private Object object;//用於接收具體實現類的實例對象
 6   //使用帶參數的構造器來傳遞具體實現類的對象
 7   public DynamicProxy(Object obj){
 8     this.object = obj;
 9   }
10   @Override
11   public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
12     System.out.println("前置內容");
13     method.invoke(object, args);
14     System.out.println("后置內容");
15     return null;
16   }
17 }

  第四步:創建測試類ProxyTest.java

 1 package ceshi1;
 2 import java.lang.reflect.InvocationHandler;
 3 import java.lang.reflect.Proxy;
 4 public class ProxyTest {
 5   public static void main(String[] args) {
 6     Iuser user = new UserImpl();
 7     InvocationHandler h = new DynamicProxy(user);
 8     Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
 9     proxy.eat("蘋果");
10   }
11 }

  運行結果為:

動態代理前置內容
我要吃蘋果
動態代理后置內容

4、通過上面的動態代理實例我們來仔細分析研究一下動態代理的實現過程
(1)首先我要說的就是接口,為什么JDK的動態代理是基本接口實現的呢?
  因為通過使用接口指向實現類的實例的多態實現方式,可以有效的將具體的實現與調用之間解耦,便於后期修改與維護。
再具體的說就是我們在代理類中創建一個私有成員變量(private修飾),使用接口來指向實現類的對象(純種的多態體現,向上轉型的體現),然后在該代理類中的方法中使用這個創建的實例來調用實現類中的相應方法來完成業務邏輯功能。
這么說起來,我之前說的“將具體實現類完全隱藏”就不怎么正確了,可以改成,將具體實現類的細節向調用方完全隱藏(調用方調用的是代理類中的方法,而不是實現類中的方法)。
  這就是面向接口編程,利用java的多態特性,實現程序代碼的解耦。
(2)創建代理類的過程
  如果你了解靜態代理,那么你會發現動態代理的實現其實與靜態代理類似,都需要創建代理類,但是不同之處也很明顯,創建方式不同!
  不同之處體現在靜態代理我們知根知底,我們知道要對哪個接口、哪個實現類來創建代理類,所以我們在編譯前就直接實現與實現類相同的接口,直接在實現的方法中調用實現類中的相應(同名)方法即可;而動態代理不同,我們不知道它什么時候創建,也不知道要創建針對哪個接口、實現類的代理類(因為它是在運行時因需實時創建的)。
  雖然二者創建時機不同,創建方式也不相同,但是原理是相同的,不同之處僅僅是:靜態代理可以直接編碼創建,而動態代理是利用反射機制來抽象出代理類的創建過程。
  讓我們來分析一下之前的代碼來驗證一下上面的說辭:
    第一點:靜態代理需要實現與實現類相同的接口,而動態代理需要實現的是固定的Java提供的內置接口(一種專門提供來創建動態代理的接口)InvocationHandler接口,因為java在接口中提供了一個可以被自動調用的方法invoke,這個之后再說。
    第二點:private Object object;
        public UserProxy(Object obj){this.object = obj;}
  這幾行代碼與靜態代理之中在代理類中定義的接口指向具體實現類的實例的代碼異曲同工,通過這個構造器可以創建代理類的實例,創建的同時還能將具體實現類的實例與之綁定(object指的就是實現類的實例,這個實例需要在測試類中創建並作為參數來創建代理類的實例),實現了靜態代理類中private Iuser user = new UserImpl();一行代碼的作用相近,這里為什么不是相同,而是相近呢,主要就是因為靜態代理的那句代碼中包含的實現類的實例的創建,而動態代理中實現類的創建需要在測試類中完成,所以此處是相近。
    第三點:invoke(Object proxy, Method method, Object[] args)方法,該方法是InvocationHandler接口中定義的唯一方法,該方法在調用指定的具體方法時會自動調用。其參數為:代理實例、調用的方法、方法的參數列表
  在這個方法中我們定義了幾乎和靜態代理相同的內容,僅僅是在方法的調用上不同,不同的原因與之前分析的一樣(創建時機的不同,創建的方式的不同,即反射),Method類是反射機制中一個重要的類,用於封裝方法,該類中有一個方法那就是invoke(Object object,Object...args)方法,其參數分別表示:所調用方法所屬的類的對象和方法的參數列表,這里的參數列表正是從測試類中傳遞到代理類中的invoke方法三個參數中最后一個參數(調用方法的參數列表)中,在傳遞到method的invoke方法中的第二個參數中的(此處有點啰嗦)。
    第四點:測試類中的異同
  靜態代理中我們測試類中直接創建代理類的對象,使用代理類的對象來調用其方法即可,若是別的接口(這里指的是別的調用方)要調用Iuser的方法,也可以使用此法
動態代理中要復雜的多,首先我們要將之前提到的實現類的實例創建(補充完整),然后利用這個實例作為參數,調用代理來的帶參構造器來創建“代理類實例對象”,這里加引號的原因是因為它並不是真正的代理類的實例對象,而是創建真正代理類實例的一個參數,這個實現了InvocationHandler接口的類嚴格意義上來說並不是代理類,我們可以將其看作是創建代理類的必備中間環節,這是一個調用處理器,也就是處理方法調用的一個類,不是真正意義上的代理類,可以這么說:創建一個方法調用處理器實例。
  下面才是真正的代理類實例的創建,之前創建的”代理類實例對象“僅僅是一個參數
    Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
  這里使用了動態代理所依賴的第二個重要類Proxy,此處使用了其靜態方法來創建一個代理實例,其參數分別是:類加載器(可為父類的類加載器)、接口數組、方法調用處理器實例
  這里同樣使用了多態,使用接口指向代理類的實例,最后會用該實例來進行具體方法的調用即可。

(3)InvocationHandler

  InvocationHandler是JDK中提供的專門用於實現基於接口的動態代理的接口,主要用於進行方法調用模塊,而代理類和實例的生成需要借助Proxy類完成。

  每個代理類的實例的調用處理器都是實現該接口實現的,而且是必備的,即每個動態代理實例的實現都必須擁有實現該接口的調用處理器,也可以這么說,每個動態代理實例都對應一個調用處理器。

  這里要區分兩個概念,代理類和代理實例,調用處理器是在創建代理實例的時候才與其關聯起來的,所以它與代理實例是一一對應的,而不是代理類。

(4)Proxy

  Proxy類是JDK提供的用於生成動態代理類和其實例的類。

  我們可以通過Proxy中的靜態方法getProxyClass來生成代理類,需要的參數為類加載器和接口列表(數組),然后再通過反射調用代理類的構造器來生成代理實例,需要以一個InvocationHandler作為參數(體現出方法調用是與實例相關的,而非類)。

1     InvocationHandler handler = new MyInvocationHandler(...);
2     Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
3     Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);

  我們也可以直接通過Proxy中的靜態方法newProxyInstance方法來直接生產代理實例,需要提供參數為上面的三個參數,即類加載器,接口數組,InvocationHandler。

1     Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class<?>[] { Foo.class },handler);

(5)、總結

  我們總結下JDK動態代理的實現步驟:

    第一步:創建接口,JDK動態代理基於接口實現,所以接口必不可少(准備工作)

    第二步:實現InvocationHandler接口,重寫invoke方法(准備工作)

    第三步:調用Proxy的靜態方法newProxyInstance方法生成代理實例(生成實例時需要提供類加載器,我們可以使用接口類的加載器即可)

    第四步:使用新生成的代理實例調用某個方法實現功能。

  我們的動態代理實現過程中根本沒有涉及到真實類實例。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM