Singleton パターン

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動: 案内検索

Singleton パターン(シングルトン・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。Singleton パターンを用いると、そのクラスインスタンスが1つしか生成されないことを保証することができる。 ロケールやLook&Feelなど、絶対にアプリケーション全体で統一しなければならない仕組みの実装に使用される。[1]

クラス図[編集]

Singleton パターンの一般的なクラス図を示す。

Singleton は同じ型のインスタンスを private なクラス変数として持つ。この変数には Singleton.getInstance() からアクセスする。Singleton のコンストラクタは private である。

このクラス図で注目すべきことは以下の3点である。

  • 同じ型のインスタンスが private なクラス変数として定義されている。
  • コンストラクタの可視性が private である。
  • 同じ型のインスタンスを返す getInstance()クラス関数として定義されている。

クラス図内にあるアンダーラインは、その項目がクラス変数あるいはクラス関数であることを意味している。

Javaでの実装例[編集]

以下にSingleton パターンを用いたクラスJavaによる例を示す。

 final class Singleton {
   private static Singleton instance;
   private Singleton(){}; 
   public static synchronized Singleton getInstance(){
     if(instance == null){ 
       instance = new Singleton();
     }
     return instance;
   }
 }

このクラスにおいて、コンストラクタprivateで定義されているため、他のクラスによって、Singletonクラスのインスタンスを生成することはできない。このクラスのインスタンスを生成したいときは、getInstance()メソッドを利用することになるが、このメソッドは最初に呼び出されたときにだけインスタンスを生成し、2回目以降に呼び出されたときは最初に生成したインスタンスを返すように作られている。そのため、プログラム中にSingletonクラスのインスタンスが1つしか存在しないことが保証される。

getInstance()メソッドがsynchronizedに指定されているのは、複数のスレッドからほぼ同時に呼び出された際に複数のインスタンスが生成されてしまう危険性をなくすためである。

問題点および改善策[編集]

Java における一般的な Singleton パターンの記述は上述した通りであるが、この方法は同期化コストが高い。そこでdouble-checked lockingというイディオム [2]が考えられたが、「アウトオブオーダー書き込み」を許すJavaプラットフォームのメモリー・モデルにより同期化に失敗する(言うまでもないが、この最適化による副作用は Java だけのものではない)。この問題を克服しようとするとコードが肥大化しコストがかかる。そこで現在では以下のように static フィールドを用いることが推奨されている。

 final class Singleton {
   private static final Singleton instance = new Singleton();
   private Singleton(){}
   public static Singleton getInstance(){
     return Singleton.instance;
   }
 }

これによりコストは改善される。同期化は行われないが、static フィールドの初期化はそのクラスが呼び出される最初の一回しか行われないため、何回getInstance()メソッドを呼んでもスレッドアンセーフを心配する必要はなくなるだけでなく、コストパフォーマンスも非常に高い。

ただしこの場合、Singletonクラスがロードされたときに初期化されるのであって、getInstance()が初めて呼ばれたときではない。このことはプログラマーの意図しないタイミングで初期化が始まってしまい混乱の元となる場合がある。 そこで en:Initialization-on-demand holder idiom と呼ばれる手法、すなわちinstanceフィールドのみを別のホルダークラスに隔離して、そのホルダークラスがロードされたときにSingletonの初期化がおこわれるよう改善したものが下記である。

final class Singleton {
  private Singleton(){}
  private static class SingletonHolder {
    private static final Singleton instance = new Singleton();
  }
  public static Singleton getInstance() {
    return SingletonHolder.instance;
  }
}

これでSingletonクラスがロードされたときではなく、getInstance()呼び出しによりSingletonHolderクラスがロードされたときにSingletonクラスが初期化される。

PHP4での実装例[編集]

PHP4 では、リファレンスと関数内の static 変数を応用することで擬似的に Singleton パターンを実現することが出来る。以下に例を示す。

 <?php
 
 class var{}
 
 function &get_var(){
   static $var;
   if(!isset($var)) $var = new var();
   return $var;
 }
 ?>

ただし、PHP4系では以下の理由により完全な Singleton パターンを実現することは不可能である。

  • クラスメソッドの可視性を設定できないため、コンストラクタが直接実行されるのを防ぐことが出来ない。
  • 返り値がリファレンスであるため、インスタンス(具体的には get_var 関数で宣言された変数 $var)が任意の値に上書きされる可能性がある。


PHP5での実装例[編集]

PHP5 では、可視性、クラス関数、クラス変数などの機能を備えてより Java に近い仕様となったため、Javaの例と同じ原理で Singleton パターンを実現できる。ソースコードも Java のサンプルコードとほとんど同じものになるため、ここでは省略する。

C++での実装例[編集]

C++ ではクラスのコンストラクタ、コピーコンストラクタと代入演算子を private におくことで唯一となる。

class Singleton {
private:
	Singleton(){}					//コンストラクタを private に置く
	Singleton(const Singleton& r){}			//コピーコンストラクタも private に置く
	Singleton& operator=(const Singleton& r){}	//代入演算子も private に置く
public:
	static Singleton* getInstance(){
		static Singleton inst;
		return &inst;
	}
 
	const char* getString(){
		return "Hello world!";		//Singleton::getInstance->getString(); と記述
	}
};

C++11ではコンパイラが生成する関数へのdefault/delete指定により、privateに置かなくてもよくなった。

class Singleton {
public:
	Singleton() = delete;					//コンストラクタを delete 指定
	Singleton(const Singleton& r) = delete;			//コピーコンストラクタも delete 指定
	Singleton& operator=(const Singleton& r) = delete;	//代入演算子も delete 指定
 
	static Singleton* getInstance(){
		static Singleton inst;
		return inst;
	}
 
	const char* getString(){
		return "Hello world!";		//Singleton::getInstance->getString(); と記述
	}
};

特徴[編集]

扱い次第では、グローバル変数のように機能させることもできる。例えば Java にグローバル変数はないと言われているが、この Singleton パターンで Singleton クラスを作成することで、コード中のどこからでも同一のインスタンスにアクセスすることができる。これはグローバル変数そのものであり、ゆえに Singleton パターンはグローバル変数と同様の問題を引き起こす危険性をはらんでいる。ただし、パッケージ名前空間)を指定することにより、インスタンスへアクセス可能なコード範囲を制限し、この問題を回避ないしは軽減することはできる。

また、コードを工夫すればインスタンスの個数制限を設けることもできる。たとえば、上記のサンプルコード中の getInstance() を複数回呼び出したとき、10回目までは毎回新しいインスタンスを生成するが、11回目以降は以前のインスタンスを再利用する、といったような機構である。これは単なるグローバル変数で実現することはできない利点である。

Multiton パターン[編集]

Multiton パターンのクラス図

Singletonパターンを拡張したデザインパターンとしてMultiton パターンと呼ばれるものがある。これはSingletonで生成されるインスタンスを連想配列で複数保持する。ただしMultiton パターンはユニットテストを難しくし[3]、ガーベジコレクションのある言語ではオブジェクトへの強い参照によりメモリリークを起こす恐れがあることも念頭に置く必要がある。

Javaでの例[編集]

public class FooMultiton {
    private static final Map<Object, FooMultiton> instances = new HashMap<Object, FooMultiton>();
 
    private FooMultiton() {
        // no explicit implementation
    }
 
    public static synchronized FooMultiton getInstance(Object key) {
 
        // Our "per key" singleton
        FooMultiton instance = instances.get(key);
 
        if (instance == null) {
            // Lazily create instance
            instance = new FooMultiton();
 
            // Add it to map   
            instances.put(key, instance);
        }
 
        return instance;
    }
 
    // other fields and methods ...
}


脚注[編集]

[ヘルプ]

関連項目[編集]