AD

Design Pattern - Singleton

.Introduction

        Singleton在軟體設計上屬於很常見, 也很易於使用的一個樣板, 主要設計用於開發者對某個物件限制其只能產生一個的時候使用, 以下對其設計思維以及實作方式做說明.




.Thinking in Singleton Pattern
         
        Singleton正如其名, 主要是為了確保目標物件只產生一個而設計, 常見的Design Patterns中有很多其他的樣板常使用Singleton來實作, 像是Abstract Factory, State Pattern等等.
        
        我們在什麼需求下會使用到Singleton, 在這邊舉一個簡單的例子來做說明, 假設我們要設計一個遊戲, 在這個遊戲裡面有各種各樣的場景, 為了不要讓程式架構中場景的處理太過混亂, 我希望設計一個場景管理器, 把這些場景的各種功能, 像是產生場景, 釋放場景等等都交由這個場景管理器處理, 那麼理所當然的, 我必須確認整個程式中所有的場景都交由這"一個"管理器做處理, 為了保證不讓程式中其他地方不小心產生額外的場景處理器, 此時就可以使用Singleton樣板.



.Implement

         為了確保只產生一個實體物件, Singleton的實作會在class中利用static成員變數的方式來確保產生唯一的Singleton物件, 並實作一個static函式提供用來取得這個Singleton物件, 架構並不複雜, 如下圖所示.


Singleton架構



        那麼, 實際以程式碼實作層面來看上圖的話, 需要注意幾個重點,以下列出注意點以及程式實做.

    • 為了不讓外部有權限產生Singleton物件, 其建構子會定為private
    • 為了不讓外部有權限對Singleton裡面存取的物件做更動, 用來儲存的static成員變數instance也會定為private
    • 外部要使用的話統一透過GetInstance函式取來用


    程式碼實做
    public class Singleton 
    {
        private static Singleton instance = null;
    
        private Singleton() 
        {        
        }
    
        public static Singleton getInstance() 
        {
            if (instance == null) 
            {
                instance = new Singleton();
            }
            return instance;
        }
    
        //other method...
        public void test()
        { 
            System.out.println("Hello Singleton!");
        };
    }
    
    class App
    {
        public static void main(String[] args) 
        {
            Singleton.getInstance().test();
        }
    };



              以上執行結果會印出"Hello Singleton!", 不過以上的架構只是最基本的Singleton, 有開發過多執行序程式的人就會發現, 假設有多個執行序幾乎同時在程式第一次呼叫getInstance時, 在if判斷null與new產生物件之間, 同時另一個執行序也有可能跑到if判斷, 結果因為還沒new, 判斷是null, 也進去new, 結果產生了多個Singleton導致問題, 針對此問題有很多人提出討論以及解決辦法, 以下簡略做說明.

     
    .Extended Reading


    Double-checked locking and the Singleton pattern
          -  http://www.ibm.com/developerworks/java/library/j-dcl/index.html

            如上列網址所探討, 針對在多執行序下Singleton的架構, 有人提出各種利用synchronized再多加一層處理的方式, 防止另一個執行序在當時同時做處理,如下所示

    利用synchronized處理Singleton
    public static synchronized Singleton getInstance()
    {
      if (instance == null)          //1
        instance = new Singleton();  //2
      return instance;               //3
    }
    
    public static Singleton getInstance()
    {
      if (instance == null)
      {
        synchronized(Singleton.class) {
          instance = new Singleton();
        }
      }
      return instance;
    } 
    
    public static Singleton getInstance()
    {
      if (instance == null)
      {
        synchronized(Singleton.class) {  //1
          if (instance == null)          //2
            instance = new Singleton();  //3
        }
      }
      return instance;
    }


            不過利用synchronized多多少少會影響到程式本身執行的效率, 所以也有人提出直接在class產生時就把物件建好, 一勞永逸, 不需要在GetInstance時還要判斷, 程式如下所示

    預先建好Singleton物件
    class Singleton
    {
      private Vector v;
      private boolean inUse;
      private static Singleton instance = new Singleton();
    
      private Singleton()
      {
        v = new Vector();
        inUse = true;
        //...
      }
    
      public static Singleton getInstance()
      {
        return instance;
      }
    }
    


            以這幾種方式來看, 如果class本身沒有很複雜, 我個人是比較建議直接在class產生就建好, 畢竟synchronized影響的效率也不算小,又是多執行序一堆在取, 影響又更大了, 所以以我而言應該就比較偏向採用先建好的方式來實作, 另外, 在Java上實作可能要注意一些其他問題, 如下面網址, 有空的人可以鑽研一下..... 


    The "Double-Checked Locking is Broken" Declaration
          -  http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html



    沒有留言:

    張貼留言