單例模式的認識與理解

愉快的開始自己的技術積累 ,設計模式中使用最普遍的單例模式開始;

設計模式中最爲大家熟悉的必須是單例模式,項目中 必須 使用到的套路。首先陳述下我對 框架,模式的一些理解。

 

從學校出來,開始面試的時候張口框架,閉口模式,真的問道什麼是框架,我只能死鴨子嘴硬的強調 MVC ,MVP 等等技術博客上看到名詞。

有句話說的好,熟讀百遍,其義自見。不管是 六大開發原則,還是二十三種設計模式,亦或是熟悉的開發流程MVC MVP 等,都是套路。是前人總結,切實可行的套路、可以讓你沿着這一條條康莊大道追到小姐姐的技術、模式。你可以照本宣科,應用起來可能生硬,出格,但是隻要追到小姐姐,你就成功了。所以編程和泡妹一樣。知道你爲什麼還沒有女朋友嗎?是你的套路用錯了地方。

我一直認爲 六大開發原則 是總綱,相當於武林中的內功心法,而設計模式是外在表現,是功法。所以熟記原則至關重要。

 

1、優化代碼的第一步             —— 單一職責原則

2、讓程序更加穩定,靈活     —— 開閉原則

3、構建擴展性更好的系統     —— 里氏替換原則

4、讓項目擁有變化的能力     —— 依賴倒置原則

5、系統有更高的靈活性        —— 接口隔離原則

6、更好的可擴展原則            —— 迪米特原則

 

言歸正傳:此文文章是總結主講  單例模式

  1. 什麼是單例模式;
  2. 意圖;
  3. 簡單實現;
  4. 單例模式的幾種實現方式;
  5. 關於單利的類,實例化。
  6. 來來,面試了 你怕不怕。

1、什麼是單例模式:百度百科中解釋, 單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。通過單例模式可以保證系統中,應用該模式的類一個類只有一個實例。即一個類只有一個對象實例

2、意圖:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

3、簡單實現: 創建一個類的對象,私有化構造 private 修飾,對外提供一個唯一獲取對象的靜態方法。


public class SingleObject {
 
   //創建 SingleObject 的一個對象
   private static SingleObject instance = new SingleObject();
 
   //讓構造函數爲 private,這樣該類就不會被實例化
   private SingleObject(){}
 
   //獲取唯一可用的對象
   public static SingleObject getInstance(){
      return instance;
   }
 
}

4、單例模式的寫法五花八門,功能各不相同。

(一):懶漢式  線程不安全寫法;

不主動進行初始化,只有第一次調用的時候,才被動初始化話,但是因爲沒有枷鎖,在多線程中基本不能使用。

/*這種方式是最基本的實現方式,這種實現最大的問題就是不支持多線程。因爲沒有加鎖 synchronized,
 *所以嚴格意義上它並不算單例模式
 */

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

(二):懶漢式,線程安全寫法

懶漢式的優化版本,加一個 synchronized ,保證其在多線程中正常的工作,但 枷鎖的同時,也影響了其效率。說實在的,Andorid中使用 多線程同時訪問的場景微乎其微。


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

(三):惡漢式,基於classloader 加載機制。避免了多線程同步的問題,但是容易產生垃圾對象。


public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

(四):雙檢鎖/雙重校驗鎖、安全且在多線程情況下能保持高性能。

//volatile 開發中基本很少使用這個關鍵字,大概意思是保證併發編程的時候類的 
// 1、保證可見性
// 2、保證有序性

public class Singleton {  
    private volatile static Singleton singleton; 
 
    private Singleton (){}  

    public static Singleton getSingleton() {  
    if (singleton == null) { 
 
        synchronized (Singleton.class) { 
 
        if (singleton == null) {  

            singleton = new Singleton();  
        }  
      }  
    }  
    return singleton;  
    }  
}

(五)、關於單例模式的書寫,大概有三種。但是基本沒有用過了。有想了解的朋友可以自行百度。個人覺得,後面的寫法大概就是另闢蹊徑。簡單粗暴的陳述 我很吊。。。像我這種平凡玩家,還是不去和它們過不去了。

5、關於單利的類,實例化

我使用單例模式的地方很單一,保存用戶常用數據,保存APP 常用數據。方便你在程序任何地方都可以拿到這些常用信息,便於修改,保存。並在此調用。

這種使用場景,不可避免的需要序列化,而Andorid 中對類進行序列化的方式有兩種。Serializable 和 Parcelable

其中Serializable是Java自帶的,而Parcelable是安卓專有的。


Serializable使用IO讀寫存儲在硬盤上。序列化過程使用了反射技術,並且期間產生大量的臨時對象。優點代碼少。

Parcelable是直接在內存中讀寫,我們知道內存的讀寫速度肯定優於硬盤讀寫速度,所以Parcelable序列化方式性能上要優於Serializable方式很多。但是代碼寫起來相比Serializable方式麻煩一些。


Serializable的使用:

public class Person implements Serializable {

    private static final long serialVersionUID = -3139325922167935911L;
    // 首先Serializable 是一個javaSE標記接口,所產生的序列化值和所發生序列化的bean
    // 有關,如果沒有設置此值,在發生反序列化的時候,改Bean的成員變量發生變更,可能導致
    // 反序列化失敗。
    


    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Parcelable 的使用

(一):首先實現 Parcelable 接口需要重寫的幾個方法。

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    private static Rect rect = new Rect();

    public static final Parcelable.Creator<Rect> CREATOR = new
            Parcelable.Creator<Rect>() {
                public Rect createFromParcel(Parcel in) {

                //從序列化中的對象創建原始對象

                    return new Rect(in);
                }

                public Rect[] newArray(int size) {

                //創建指定長度的原始對象數組

                    return new Rect[size];

                }
            };

    private Rect() {
    }

    public static Rect getIntent(){
         return rect;
    }    

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out) {
    //
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }
}

OK,是不是沒有任何難度,告訴你。如果你只知道這些出去面試。肯定碰一鼻子灰。

來道附加題:請問我對一個類進行單利,不管是懶漢 餓漢,還是雙校驗,我進行了序列化,之後再進行反序列化。那麼請問我拿到這個類,還是唯一的嗎?

?????

我剛聽到這個問題的時候一臉懵逼,說實在的開發中並且沒有注意這些問題,不過不會我們可以猜,大膽猜測,我麻痹從硬盤中取出數據,對於整個層序來說肯定不是唯一的了啊。

那麼,怎麼保證唯一;

好吧:答案是 重寫readReslove,如此可以保證。至於原因,關心的建議百度。

public class ReadResolveDemo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static final ReadResolveDemo INSTANCE = new ReadResolveDemo();
 
    private ReadResolveDemo() {
    }
 
    public static ReadResolveDemo getInstance() {
        return INSTANCE;
    }
 
    private Object readResolve() {
        return INSTANCE;
    }
 
}

那麼,我使用反射拿到這個類,不是也不能保證此類的單一。

怎麼那麼多那麼,你有病啊,自己寫的程序,自己給自己挖坑。不過吐槽歸吐槽,畢竟人家反編譯,或者壓根是你以SDK方式提供別人引用的呢。所以。。

保證 反射拿不到類就可以了吧,怎麼保證呢:上代碼

 
public class ReadResolveDemo implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final ReadResolveDemo INSTANCE = new ReadResolveDemo();
    private static boolean flag = false;
    
    synchronized (Elvis.class) {

    System.out.println(" try to instance");

        if (flag == false) {

            System.out.println("first time instance");

            flag = !flag;

         } else {

            throw new RuntimeException("單例模式被侵犯!");

         }

       }

    }

    private ReadResolveDemo() {
    }
 
    public static ReadResolveDemo getInstance() {
        return INSTANCE;
    }
 
    private Object readResolve() {
        return INSTANCE;
    }

}

 

文章的最後讓我們舉個栗子,這是我和朋友開玩笑講解,的想出來。

怎麼理解上述一系列操作呢?


一羣工科生班級裏,突然有一個女神級妹子,經過 同學A 的各種殷勤加跪舔,終於讓你拿下了 妹子,作爲自己的女朋友。

都是自己女朋友了,我肯定不能讓班級裏一羣牲口在去調戲了啊, 加上單例,果斷的想要約我女朋友社交,必須通過我纔行,不然你們聯繫不上。因爲他的電話只有我知道。

可是放假了,妹子要回家了(序列化了),我的天啊,這下子脫離我的掌控了。腫麼辦,不行手機給開個呼叫轉移。你打電話聯繫就到我電話上了(重寫 ReadReslove )

可是妹子要畢業了,父母給安排了一系列的相親,那哪成,腫麼辦。哎、我去破壞他們。嗯,先加個判斷,再拋個異常。老兄你放棄吧,妹子是我的。你通過Android爸爸提供的反射,也不行。


下一篇正在整理,關於使用SP 存儲用戶數據的工具類。一直在用,感覺挺帶感的。就上傳github上,供大家參詳。

 

 

 

 

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