Android設計模式之單例模式

吳小龍同學發表於2019-03-04

設計模式

設計模式(design pattern)是對軟體設計中普遍存在(反覆出現)的各種問題,所提出的解決方案。這個術語是由埃裡希·伽瑪(Erich Gamma)等人在1990年代從建築設計領域引入到電腦科學的。《設計模式》一書原先把設計模式分為建立型模式、結構型模式、行為型模式,把它們通過授權、聚合、診斷的概念來描述。

建立範例

建立範例全部是關於如何建立例項的。這組範例可以被劃分為兩組:類建立範例及物件建立範例。類建立例項在例項化過程中有效的使用類之間的繼承關係,物件建立範例則使用代理來完成其任務。包括:

  • 抽象工廠(Abstact Factory)
  • 構造器(Builder Pattern)
  • 工廠方法(Factory Method pattern)
  • 原型(Prototype pattern)
  • 單例模式(Singleton pattern)

結構範例

這組範例都是關於類及物件複合關係的。包括:

  • 介面卡(Adapter pattern)
  • 橋接(Bridge pattern)
  • 組合(Composite pattern)
  • 裝飾(Decorator pattern)
  • 外觀(Façade pattern)
  • 享元(Flyweight pattern)
  • 代理(Proxy pattern)

行為範例

這組範例都是關於物件之間如何通訊的。包括:

  • 職責鏈(Chain-of-responsibility pattern)
  • 命令(Command pattern)
  • 翻譯器(Interpreter pattern)
  • 迭代器(Iterator pattern)
  • 仲裁器(Mediator pattern)
  • 回憶(Memento pattern)
  • 觀察者(Observer pattern)
  • 狀態機(State pattern)
  • 策略(Strategy pattern)
  • 模板方法(Template method pattern)
  • 參觀者(Visitor)

單例模式

什麼是單例模式?確保一個類只有一個例項,並提供對該例項的全域性訪問,其建構函式私有化。

七種實現方式

各種寫法各有利弊,讓我們看看具體寫法:

懶漢模式,執行緒不安全

public class SingletonPattern {

    private static SingletonPattern singletonPattern = null;

    private SingletonPattern() {
    }

    public static SingletonPattern getInstance() {
        if (singletonPattern == null) {
            singletonPattern = new SingletonPattern();
        }
        return singletonPattern;
    }
}複製程式碼

類載入時只是申明例項,並未例項化,當呼叫getInstance方法,才進行例項化,但執行緒不安全,多個執行緒併發呼叫getInstance方法可能會導致建立多份相同的單例出來,解決的辦法就是使用synchronized關鍵字。

懶漢模式,執行緒安全

public class SingletonPattern {

    private static SingletonPattern singletonPattern = null;

    public static SingletonPattern getInstance() {
        synchronized (SingletonPattern.class) {
            if (singletonPattern == null)
                singletonPattern = new SingletonPattern();
        }
        return singletonPattern;
    }
}複製程式碼

synchronized保證一個時間內只能有一個執行緒得到執行,另一個執行緒必須等待當前執行緒執行完才能執行,使得執行緒安全。缺點每次呼叫getInstance方法都進行同步,造成了不必要的同步開銷。

餓漢模式

 public class SingletonPattern {
    //餓漢模式
    private static final SingletonPattern singletonPattern = new SingletonPattern();

    private SingletonPattern() {
    }

    public static SingletonPattern getInstance() {
        return singletonPattern;
    }
}複製程式碼

類載入時就已經進行例項化,類載入較慢,但獲取物件速度快,執行緒安全。

雙重校驗DCL模式

 public class SingletonPattern {
    private static volatile SingletonPattern singletonPattern = null;

    private SingletonPattern() {
    }

    public static SingletonPattern getInstance() {
        //第一層校驗,為了避免不必要的同步
        if (singletonPattern == null) {
            synchronized (SingletonPattern.class) {
                //第二層校驗,例項null的情況下才建立
                if (singletonPattern == null)
                    singletonPattern = new SingletonPattern();
            }
        }
        return singletonPattern;
    }
}複製程式碼

這裡使用了volatile關鍵字,因為多個執行緒併發時初始化成員變數和物件例項化順序可能會被打亂,這樣就出錯了,volatile可以禁止指令重排序。雙重校驗雖然在一定程度解決了資源的消耗和多餘的同步,執行緒安全問題,但在某些情況還是會出現雙重校驗失效問題,即DCL失效。

靜態內部類單例模式

 public class SingletonPattern {

    private SingletonPattern() {
    }

    private static class SingletonPatternHolder {
        private static final SingletonPattern singletonPattern = new SingletonPattern();
    }

    public static SingletonPattern getInstance() {
        return SingletonPatternHolder.singletonPattern;
    }
}複製程式碼

第一次呼叫getInstance方法時載入SingletonPatternHolder 並初始化singletonPattern,這樣不僅能確保執行緒安全,也能保證SingletonPattern類的唯一性。

使用容器

SingletonManager

public class SingletonManager {
    private SingletonManager() {
    }

    private static Map<String, Object> instanceMap = new HashMap<>();

    public static void registerInstance(String key, Object instance) {
        if (!instanceMap.containsKey(key)) {
            instanceMap.put(key, instance);
        }
    }

    public static Object getInstance(String key) {
        return instanceMap.get(key);
    }
}複製程式碼

SingletonPattern

public class SingletonPattern {
    SingletonPattern() {
    }

    public void doSomething() {
        Log.d("wxl", "doSomeing");
    }
}複製程式碼

程式碼呼叫:

 SingletonManager.registerInstance("SingletonPattern", new SingletonPattern());
SingletonPattern singletonPattern = (SingletonPattern) SingletonManager.getInstance("SingletonPattern");
singletonPattern.doSomething();複製程式碼

根據key獲取物件對應型別的物件,隱藏了具體實現,降低了耦合度。

列舉單例模式

 public enum SingletonEnum {
    INSTANCE;

    public void doSomething() {
        Log.d("wxl", "SingletonEnum doSomeing");
    }
}複製程式碼

程式碼呼叫:

 SingletonEnum.INSTANCE.doSomething();複製程式碼

更加簡潔,執行緒安全,還能防止反序列化導致重新建立新的物件,而以上方法還需提供readResolve方法,防止反序列化一個序列化的例項時,會建立一個新的例項。列舉單例模式,我們可能使用的不是很多,但《Effective Java》一書推薦此方法,說“單元素的列舉型別已經成為實現Singleton的最佳方法”。不過Android使用enum之後的dex大小增加很多,執行時還會產生額外的記憶體佔用,因此官方強烈建議不要在Android程式裡面使用到enum,列舉單例缺點也很明顯。

Android應用示例

AccessibilityManager

public final class AccessibilityManager {
    static final Object sInstanceSync = new Object();
    private static android.view.accessibility.AccessibilityManager sInstance;
    public static AccessibilityManager getInstance(Context context) {
        synchronized (sInstanceSync) {
            if (sInstance == null) {
                //省略部分程式碼
                sInstance = new AccessibilityManager(context, null, userId);
            }
        }
        return sInstance;
    }
}複製程式碼

InputMethodManager

public final class InputMethodManager {
    static android.view.inputmethod.InputMethodManager sInstance;
    public static InputMethodManager getInstance() {
        synchronized (InputMethodManager.class) {
            if (sInstance == null) {
                //省略部分程式碼
                sInstance = new InputMethodManager(service, Looper.getMainLooper());
            }
            return sInstance;
        }
    }
}複製程式碼

參考

廣而告之

我開通了小密圈,連線彼此,一起進步~

什麼是小密圈?小密圈是一個擁有共同話題、目標的人在一起使用的朋友圈。它作為高品質知識社群,需付費加入。

為什麼我要建立小密圈,是為我的讀者們建立一個更好的互動空間,讓讀者可以很方便聯絡到另一個讀者。

為什麼是收費的,收費是最好的門檻,是一種很好的篩選機制。

這裡我們除了建立聯絡,我還會不定期分享一些過來人的經驗和總結,不止於Android 。

Android設計模式之單例模式

Android設計模式之單例模式

相關文章