設計模式讀書筆記一 單例模式

1、單例模式介紹
單例模式是應用最廣泛的模式之一,也是可以說是初級工程師唯一會用的設計模式。在應用這一模式的時候,單例對象的類必須保證只有一個實例存在。許多時候整個系統只要一個全局對象,這樣有利于我們協調系統整體行為。如在一個應用中,應該只有一個ImageLoader實例,這個ImageLoader中又包含有線程池、緩存系統、網絡請求等,很消耗資源,因此,沒有理由讓它有多個實例。這種情況就是單例模式的使用場景。
2、單例模式的定義
確保類只有一個實例,而且自行實例化并向整個系統提供這個實例
3、使用場景
確保類只有一個對象的場景
4、UML類圖

5、單例模式的多種實現
(1)餓漢模式
這是單例模式的最簡單的一種寫法,在具體介紹之前我們寫一個簡單的例子,這樣也方便后面介紹單例模式的其他寫發。
例如:一個公司只有一個CEO、幾個VP、無數個員工,例子很簡單。下面我們一點點的實現

普通員工類:Staff.java

package com.example.singleton;

public class Staff {
       public void work()
      {
             //干活
      }

}

副總裁類:VP.java

package com.example.singleton;

public class VP extends Staff {
       @Override
       public void work() {
             // TODO Auto-generated method stub
             super .work();
             //管理下面的經理
      }

}

CEO類:CEO.java

package com.example.singleton;

public class CEO extends Staff {

       private static final CEO mCeo= new CEO();

       // 構造函數私有化(構造方法的私有化是單例模式的核心)
       private CEO() {

      }

       /**
       * 方法一
       * 餓漢單例模式
       *
       * @author HP
       *
       */
       public static CEO getCeo () {
             return mCeo ;
      }
       @Override
       public void work() {
             // TODO Auto-generated method stub
             super .work();
             // 管理 vp
      }

}

公司類:Company.java

package com.example.singleton;

import java.util.ArrayList;
import java.util.List;

/**
 * 公司類
 * @author HP
 *
 */
public class Company {

    private List<Staff> allStaffs=new ArrayList<>();

    public void addStaff(Staff per)
    {
        allStaffs.add(per);
    }
    public void showAllStaffs()
    {
        for(Staff per:allStaffs)
        {
            System.out.println("Obj:"+per.toString());
        }
    }
}

最后是Main.java

package com.example.singleton;

public class Main {

       /**
       * @param args
       */
       public static void main(String[] args) {
             // TODO Auto-generated method stub
            Company cp = new Company();
             //方法一 :餓漢模式
            Staff ceo1 = CEO. getCeo();
            Staff ceo2 = CEO. getCeo();

            cp.addStaff(ceo1);
            cp.addStaff(ceo2);

            cp.showAllStaffs();
            System. out .println("--------------------------------------" );
             //測試發現有兩條數據是一樣的,說明單例類只能提供一個對象

            Staff vp1= new VP();
            Staff vp2= new VP();

            Staff staff1= new Staff();
            Staff staff2= new Staff();
            Staff staff3= new Staff();


            cp.addStaff(vp1);
            cp.addStaff(vp2);
            cp.addStaff(staff1);
            cp.addStaff(staff2);
            cp.addStaff(staff3);

            cp.showAllStaffs();

      }

}

輸出結果:

Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.CEO@1cacd5d4
--------------------------------------
Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.CEO@1cacd5d4
Obj:com.example.singleton.VP@170a6001
Obj:com.example.singleton.VP@2a24ed78
Obj:com.example.singleton.Staff@5e6276e5
Obj:com.example.singleton.Staff@126be4cc
Obj:com.example.singleton.Staff@697a1686

以上就是這個例子的全部內容,其中CEO.java就是一個單例類,因為之前說了一個公司就只有一個CEO。從代碼中可以看到,CEO類不能通過new關鍵字來構造對象,因為構造方法已經被私有化。只能通過CEO類對外開放的getCeo方法來獲取對象,而這個對象是在申明的時候就已經被初始化,這就保證的對象的唯一性。以上單例模式的寫法就是所謂的餓漢模式。
(2)懶漢模式
懶漢模式與餓漢模式的區別在于,懶漢模式的對象是在調用了getInstance方法的時候初始化的。懶漢模式的實現方式如下,將CEO類修改。

package com.example.singleton;

public class CEO extends Staff {

       // private static final CEO mCeo=new CEO();
       private static CEO mCeo;

       // 構造函數私有化(構造方法的私有化是單例模式的核心)
       private CEO() {

      }

       // /**
       // * 方法一
       // * 餓漢單例模式
       // *
       // * @author HP
       // *
       // */
       // public static CEO getCeo() {
       // return mCeo;
       // }
       /**
       * 方法二
       * 懶漢模式
       * @return
       */
       public static synchronized CEO getInstance() {
             if (mCeo != null) {
                   mCeo = new CEO();
            }
             return mCeo ;
      }

       @Override
       public void work() {
             // TODO Auto-generated method stub
             super .work();
             // 管理 vp
      }

}

對應的修改Main.java

package com.example.singleton;

public class Main {

       /**
       * @param args
       */
       public static void main(String[] args) {
             // TODO Auto-generated method stub
            Company cp = new Company();
//          //方法一 :餓漢模式
//          Staff ceo1 = CEO.getCeo();
//          Staff ceo2 = CEO.getCeo();

             //方法二:懶漢模式
            Staff ceo1 = CEO. getInstance();
            Staff ceo2 = CEO. getInstance();



            cp.addStaff(ceo1);
            cp.addStaff(ceo2);

            cp.showAllStaffs();
            System. out .println("--------------------------------------" );
             //測試發現有兩條數據是一樣的,說明單例類只能提供一個對象

            Staff vp1= new VP();
            Staff vp2= new VP();

            Staff staff1= new Staff();
            Staff staff2= new Staff();
            Staff staff3= new Staff();


            cp.addStaff(vp1);
            cp.addStaff(vp2);
            cp.addStaff(staff1);
            cp.addStaff(staff2);
            cp.addStaff(staff3);

            cp.showAllStaffs();

      }

}

懶漢模式中的getInstance方法中添加了synchronized關鍵字,也就是說getInstance是一個同步方法,這就是在多線程情況下保證對象唯一性的手段。但是有一個問題,那就是即使mCeo已經被初始化,每次調用getInstance方法的時候還是會同步,這樣就造成了不必要的資源浪費。這也是懶漢模式的最大問題所在。

△ 懶漢模式的優缺點
懶漢模式的優點是單例只有使用的時候才會被初始化,在一定程度上節約了資源;缺點是第一次加載的時候反應稍慢,最大了問題是每次調用getInstance方法的時候都要同步,會造成不必要的開銷。

(3)DCL實現方式
這種方式既可以實現在需要的時候在初始化實例,又保證線程安全,并且在調用getInstance方法的時候不同不同,其實現如下:

package com.example.singleton;

public class CEO extends Staff {

//     private static final CEO mCeo=new CEO();
       private static CEO mCeo;

       // 構造函數私有化(構造方法的私有化是單例模式的核心)
       private CEO() {

      }

//     /**
//     * 方法一
//     * 餓漢單例模式
//     *
//     * @author HP
//     *
//     */
//     public static CEO getCeo() {
//     return mCeo;
//     }
//    /**
//     * 方法二
//     * 懶漢模式
//     * @return
//     */
//    public static synchronized CEO getInstance() {
//          if (mCeo == null) {
//                mCeo = new CEO();
//          }
//          return mCeo;
//    }

       /**
       * 方法三
       * DCL
       */
       public static CEO getInstance() {
             if (mCeo == null) {
                   synchronized (CEO.class ) {
                         if (mCeo == null) {
                               mCeo =new CEO();
                        }
                  }
            }
             return mCeo ;
      }

       @Override
       public void work() {
             // TODO Auto-generated method stub
             super .work();
             // 管理 vp
      }

}

這不是一種被推薦使用的單例模式寫法
(4)靜態內部類實現單例模式
其實現如下:

package com.example.singleton;

public class CEO extends Staff {

       // private static final CEO mCeo=new CEO();
       // private static CEO mCeo;

       // 構造函數私有化(構造方法的私有化是單例模式的核心)
       private CEO() {

      }

       // /**
       // * 方法一
       // * 餓漢單例模式
       // *
       // * @author HP
       // *
       // */
       // public static CEO getCeo() {
       // return mCeo;
       // }
       // /**
       // * 方法二
       // * 懶漢模式
       // * @return
       // */
       // public static synchronized CEO getInstance() {
       // if (mCeo == null) {
       // mCeo = new CEO();
       // }
       // return mCeo;
       // }

       // /**
       // * 方法三
       // * DCL
       // */
       // public static CEO getInstance() {
       // if (mCeo==null) {
       // synchronized (CEO.class) {
       // if (mCeo==null) {
       // mCeo=new CEO();
       // }
       // }
       // }
       // return mCeo;
       // }

       /**
       * 方法四 靜態內部類單例模式(推薦使用的單例模式)
       *
       * @return
       */
       public static CEO getInstance() {
             return SinletonCEO. mCeo;
      }

       private static class SinletonCEO {
             private static final CEO mCeo = new CEO();
      }

       @Override
       public void work() {
             // TODO Auto-generated method stub
             super .work();
             // 管理 vp
      }

}

當第一次加載CEO類的時候并不會初始化mCeo,只有在第一次調用CEO的getInstance方法的時候mCeo才會被初始化。因此,第一次調用getInstance方法會導致虛擬機加載SinletonCEO類,這種方式不僅能夠確保線程安全,同時也能夠保證單例對象的唯一性,同時也延遲了單例的實例化。所以這是一種推薦使用的單例實現方式。

(5)其他單例實現方式
其中還有兩種單例的實現方式,他們是枚舉單例和使用容器實現單例模式。這里就不再具體介紹了,想了解的同學可以翻閱《Android源碼設計模式解析與實戰》藝術。

6 總結
不管是哪種方式實現的單例模式,它們的核心就是構造函數的私有化,并且通過靜態方法來獲取一個唯一的實例,在這個過程中我們必須要保證線程安全、防止反序列化導致重生成實例等問題!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容