DesignPattern: Null Object, Singleton


Null object pattern/空物件模式

我認為空物件模式是讓程式碼保持乾淨,並且語意明確的一種方法。

為了好debug,在寫C時我已養成加上要檢查函數參數中的指標是否為NULL,不然可能程式執行到一半就會crash掉,然後找半天都不知道錯誤在哪裡;不過在寫Java時,這習慣就會變成不檢測,直接讓程式crash反正最後也會有jvm會幫我印出錯誤位置點。

Christopher Okhravi教學影片
https://www.youtube.com/watch?v=rQ7BzfRz7OY

如何實現 Null Object Pattern ? (使用PHP展示)
http://oomusou.io/design-pattern/nullobject/
Null Object 模式並非出自設計模式一書,而是出現在重構的 Ch 9.7,教大家將 null 值重構成 null 物件,因為只要有 null 值,就必須去 if 判斷是否 null,甚至於去 try catch,這樣的 API 並不好用,而且只要忘記判斷就可能出錯。

Singleton pattern/單例模式

這個類別在整個世界中只會存在一個實體, 不論如何取得都是同一個實體。

Christopher Okhravi教學影片
https://www.youtube.com/watch?v=hUE_j6q0LTQ

良葛格和RunNoob.com都有用java語言寫幾種範例

這個影片動態的展示了未保護的singleton在multithread狀態會有問題
https://www.youtube.com/watch?v=iyfqDV4wKAQ

1. 實作單例基本精神, 並具備multithread保護的能力
public class Singleton {
  private static Singleton instance;
  private Singleton (){}
  public static synchronized Singleton getInstance() {
    if (instance == null) {
      instance = new Singleton();
    }
    return instance;
  }
}

2. 多方都在用的double-checked locking方法, 避免使用synchronized method降低執行效率
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;
  }
}
關於volatile關鍵字特性:淺談Java的volatile
Volatile修飾的成員變數在每次被線程訪問時,都強迫從共用記憶體中重讀該成員變數的值。而且,當成員變數發生變化時,強迫線程將變化值回寫到共用記憶體。這樣在任何時刻,兩個不同的線程總是看到某個成員變數的同一個值。  

3. 沒有使用lock, 僅透過Java classloader在初始化class時只會使用單執行緒來完成的特性(個人覺得這個寫法也滿簡單的)
public class Singleton {
  private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
  }
  private Singleton (){}
  public static final Singleton getInstance() {
    return SingletonHolder.INSTANCE;
  }
}

4. 最特別的是使用java的enum關鍵字來實作單例模式
dzone的這篇文章提供更多資訊關於用enum實作單例模式, 這種實作方式可以防止有人使用java反射機制呼叫私有建構子, 導致上面這些singlton實作都破功
public enum SingletonEnum {
    INSTANCE;
    int value;
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
}

使用C++實作singlton可以參考
1. Stackoverflow的解答中, 在C++11可以用很簡單的寫法完成singleton
https://stackoverflow.com/questions/12248747/singleton-with-multithreads
static Singleton* Singleton::getSingletonInstance()
{
  static Singleton instance;
  return &instance;
}

不過這裡要注意最好將複製建構子跟賦值運算子都改成private避免C++的自動拷貝行為 private:
Singleton(Singleton const&) // copy constructor
void operator=(Singleton cons&); // assign operator

2. fcamel在以 double-checked locking 為例,了解 memory barrier 的作用以及thread 之間何時會同步資料 中引述了關於memory model說明, 為了防止CPU亂序執行(out-of-order execution)需要memory barrier的幫助

初始版本
這作法相當於java用synchrnized method
Singleton* s_instance;
Singleton* Singleton::GetInstance()
{
    ScopeLock lock;
    if (s_instance == nullptr)
        s_instance = new Singleton();
    return s_instance;
}

進階版本
fcamel引用自Preshing on Programming:Double-Checked Locking is Fixed In C++11
std::atomic<Singleton*> Singleton::s_instance;
std::mutex Singleton::s_mutex;

Singleton* Singleton::GetInstance() {
    Singleton* tmp = s_instance.load();
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(s_mutex);
        tmp = s_instance.load();
        if (tmp == nullptr) {
            tmp = new Singleton();
            s_instance.store(tmp);
        }
    }
    return tmp;
}

另外, 在(learn&think):浅谈设计模式六: 单例模式(Singleton)中說明, 為何不能在C++中使用volatile關鍵字來完成double-checked lokcking pattern的單例實作。

---

良葛格(林信良) Design Pattern(繁中)
https://openhome.cc/Gossip/DesignPattern/index.html

RunNoob.com(簡中)
http://www.runoob.com/design-pattern/design-pattern-tutorial.html

留言