Singleton は、 生成に関するデザインパターンの一つで、 この種類のオブジェクトがただ一つだけ存在することを保証し、 他のコードに対して唯一のアクセス・ポイントを提供します。
Singleton には、 大域変数とほぼ同じ長所と短所があります。 両方とも随分と便利ですが、 コードのモジュール性を犠牲にしています。
シングルトンのクラスに依存しているあるクラスを使う場合、 シングルトンのクラスも一緒に使う必要があります。 ほとんどの場合、 この制限は、 ユニット・テストの作成で問題となります。
概念的な例
通常、 構造体が最初に初期化される時にシングルトンのインスタンスが生成されます。 これを実現するために、 構造体に getInstance
メソッドを定義します。 このメソッドは、 シングルトンのインスタンスを生成して返します。 一度生成されたシングルトンのインスタンスは、 getInstance
が呼ばれるたびに同じものが返されます。
ゴルーチン (goroutine) では、 どうでしょう? シングルトンの構造体は、 複数のゴルーチンがそのインスタンスにアクセスしようとするたびに、 同じインスタンスを返す必要があります。 このため、 Singleton パターンを誤って実装することがよく起きます。 下記の例は、 正しいシングルトンの生成方法です。
注目すべき点:
-
初回は singleInstance
であることを確認するための nil
の検査が冒頭にあります。 これは、 getinstance
メソッドの呼び出しのたびに高価なロック操作を行うことを避けるためです。 この検査が陰性ならば、 それは singleInstance
フィールドはすでに値を持っているということです。
-
singleInstance
構造体は、 ロックの範囲内で生成されます。
-
ロック獲得後にもう一度 nil
検査があります。 これは、 二つ以上のゴルーチンが最初の検査をすり抜けた場合に、 ゴルーチン一つだけがシングルトンのインスタンスを作成できるようにするためです。 そしないと、 全部のゴルーチンがそれぞれ専用のシングルトン構造体を作成してしまいます。
single.go: シングルトン
main.go: クライアント・コード
output.txt: 実行結果
もう一つの例
Go には、 シングルトンのインスタンスを作成する他のメソッドがあります。
-
init
関数
init
関数の内部で単一のインスタンスを作成することが可能です。 これは、 インスタンスの早期初期化が許されている場合のみ適用できます。 init
関数は、 パッケージ中のファイルあたり 1 回だけ呼ばれます。 ですので、 ただ一つのインスタンスが作成されることを保証できます。
-
sync.Once
sync.Once
は、 1 回だけ実行されます。 下記のコードをご覧ください:
syncOnce.go: シングルトン
main.go: クライアント・コード
output.txt: 実行結果