Singletonパターン

GoFデザインパターンについて学んでます.

出版から15年経ち,大分古いので自分で調査しながらアップデートしています.
今回は「Singleton」パターンについて学びます.

Singletonって?

インスタンスが1個しか存在しないことを保証するパターンのこと.
プログラマーが注意してnew Class()を1回だけ実行すれば1個しか生成しない.
しかしそれは現実的ではないのでプログラム上でインスタンス生成を
絶対に1個しか存在しないことを保証するために使う.

参考記事

デザインパターン「Singleton」 - Qiita
singletonの色んな実装方法を集めました。 - Qiita
JavaのSingleton実装方法5つとメリットデメリット使用場面とかいろいろ書いてみる - Junks, GC cannot sweep
シングルトンにInitialize-On-Demand Holder - No Programming, No Life
MSC07-J. シングルトンオブジェクトのインスタンスを複数作らない

クラス図

f:id:xrdnk:20200102144326p:plain

singleton:getInstanceで生成したインスタンスを保持するためのstatic変数

コンストラクタ:scopeをprivateに定義することで,
getInstanceでしかインスタンス生成できないようにする

getInstance:唯一のインスタンスを取得するstaticメソッド

サンプルコード

以下がデザインパターン入門のサンプルコードです.

  • Mainクラス
package singleton;

public class Main {

    public static void main(String[] args) {
        System.out.println("Start.");
        // 同一性の確認のため,シングルトンインスタンスを2つ生成
        Singleton obj1 = Singleton.getInstance();
        Singleton obj2 = Singleton.getInstance();
        // 同一性の確認
        if(obj1 == obj2) {
            System.out.println("obj1とobj2は同じインスタンスである");
        }else {
            System.out.println("obj1とobj2は同じインスタンスではない");
        }
        System.out.println("End.");
    }

}

getInstance()を二回呼び出し,同一性の確認をしています.

  • Singletonクラス(Not Thread Safe)
package singleton;

public final class Singleton {

    /** getInstanceで生成されたインスタンスを保持するためのstatic変数 */
    private static final Singleton singleton = new Singleton();

    /** コンストラクタのscopeをprivateに定義することでgetInstanceからのみ生成できるようにする */
    private Singleton() {
        // インスタンスが生成したかどうか確認
        System.out.println("インスタンスを生成した");
    }

    /** 唯一のインスタンスを取得するstaticメソッド */
    public static Singleton getInstance() {
        return singleton;
    }
}

インスタンスを得るためのstaticメソッドを用意し,
うっかりクラスの外からnewされないようにコンストラクタをprivateにしている.
書籍ではclass宣言にfinalをつけていませんが,
サブクラスが作られるとシングルトン性が保証されないため自分はつけてます.

  • 出力結果
Start.
インスタンスを生成した
obj1とobj2は同じインスタンスである
End.

出力結果はこんな感じ.同一性が保証されてます.
ただ書籍のSingletonのサンプルコードはこれはThread Safeではないです.
つまり複数スレッドで初期化が並列に実行される可能性があります.
それを防ぐためにgetInstance()にsynchronized修飾子をつけて
同期処理させる方法がありますが,この場合,処理速度が低下する場合があります. 【Effective Java】項目71:遅延初期化を注意して使用する - The King's Museum

  • Singletonクラス(Initialize-On-Demand Holder Idiom)
package singleton;

public final class Singleton {

    /** 遅延初期化Holderクラス(Initialize-On-Demand Holder Idiom)
     * (static inner クラス) */
    private static class SingletonHolder{
        /** getInstanceで生成されたインスタンスを保持するためのstatic変数 */
        private static final Singleton INSTANCE = new Singleton();
    }
    /** コンストラクタのscopeをprivateに定義することでgetInstanceからのみ生成できるようにする */
    private Singleton() {
        // インスタンスが生成したかどうか確認
        System.out.println("インスタンスを生成した");
    }

    /** 唯一のインスタンスを取得するstaticメソッド */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

最近ではこちらの記法の方が優れているとのこと.
static innerクラスであるHolderの中でstatic変数を宣言しています.
これによってThread Safeが保証されます.個人的にこちらの記法が好き.

Unityでの活用例

ゲーム管理オブジェクト(GameManagerとか)に使われることが多い.
例えば,Sceneが遷移するたびにGameManagerを
生成してしまうとインスタンスが重複存在してしまう.
これによってバグが発生することもある.そこでSingletonパターンの出番.

以下のテラシュールブログ様の記事が詳しく書かれています.
シングルトンなオブジェクトを作る - テラシュールブログ
もっと楽なシングルトンの実装 - テラシュールブログ
Unityで少しだけ高速なシングルトン(Singleton) - テラシュールブログ

AudioManagerに活用した例はこちら.
【Unity開発】シングルトン(Singleton)パターンまとめ【ひよこエッセンス】 - Unity(C#)初心者・入門者向けチュートリアル ひよこのたまご