CoffeeScriptで書くSingletonパターン


プライベートクラスを使い、staticメソッドでアクセスさせる実装

class Singleton
  [instance] = []

  @getInstance: ->
    instance ?= new PrivateClass()

  class PrivateClass
    constructor: ->

console.log Singleton.getInstance() is Singleton.getInstance() #=> true

CoffeeScript Cookbook » Singleton Pattern に載っている方法。-> JS Binでみる

インスタンス化するクラスをプライベートにする実装。

インスタンス化するクラスPrivateClassに外部からアクセスできないため、instanceofによるプロトタイプチェーンの検査ができない。個人的には、言語機能の一部が失われる邪悪な実装だと思っている。

インスタンスをキャッシュし、returnする実装

class Singleton
  [instance] = []

  constructor: ->
    return instance if instance?
    instance = @

console.log new Singleton() is new Singleton() #=> true

javascript - Singleton class in coffeescript - Stack Overflow で紹介されている方法。-> JS Bin でみる

1度目のコンストラクトでインスタンスをキャッシュする。2度目以降のコンストラクタのコールでキャッシュしたインスタンスをreturnするパターン。

暗黙的にキャッシュされたインスタンスが戻されるので、シングルトンなのかを意識する必要がなくなる。しかし、これは同時にシングルトンなのかを意識することができなくなるというデメリットとも考えられる。もっと明示的な実装をしたい場合は次の実装がよい。

インスタンスをキャッシュし、staticメソッドでアクセスさせる実装

class Singleton
  [isInternal, instance] = []

  @getInstance: ->
    return instance if instance?
    isInternal = true
    new Singleton

  constructor: ->
    throw new Error "Can't call new #{@constructor.name}(), use #{@constructor.name}.getInstance() instead." unless isInternal
    isInternal = false
    instance = @

console.log Singleton.getInstance() is Singleton.getInstance() #=> true

-> JS Bin でみる

直接コンストラクタを呼ぶとエラーする。Singleton.getInstance()経由でインスタンスを取得するとインスタンス未生成の場合は生成しキャッシュしreturnする。生成済みの場合はキャッシュしたインスタンスをreturnする。

注意しなければならないのは

class Singleton
  [isInternal, instance] = []

  @getInstance: ->
    return instance if instance?
    isInternal = true
    instance = new Singleton # BAD WAY

  constructor: ->
    throw new Error "Can't call new #{@constructor.name}(), use #{@constructor.name}.getInstance() instead." unless isInternal
    isInternal = false

console.log Singleton.getInstance() is Singleton.getInstance() #=> true

のようにinstanceへのキャッシュ登録をコンストラクタの戻りで行うのではなく、コンストラクタ内でinstance = @とすること。
これをミスると、コンストラクタから呼ばれた他のメソッドの中からSingleton.getInstance()しているとinstancenullのままgetInstance()が呼ばれコンストラクタのコールがスタックに積み上がるループに嵌る