広告

Java : Singleton パターン (図解/デザインパターン)

Singleton パターンとは、GoF によって定義されたデザインパターンの1つです。
そのクラスのインスタンスが1つしか生成されないことを保証します。

本記事では、Singleton パターンについてコード付きで解説していきます。


デザインパターン(GoF) 関連記事

概要

Singleton パターンとは、そのクラスのインスタンスが1つしか生成されないことを保証するデザインパターンのことである。

Singleton パターンとは、

  • あるクラスがあり
  • そのクラスのインスタンスが最大で1つしか生成されない

ということを保証するための設計です。

シングルトン図

Java においてインスタンスの生成とは、クラスに対して new 演算子を使うことをいいます。

class Sample {
}

// Sample クラスのインスタンスを生成
final var instance = new Sample();

さて、Singleton パターンはどのような場面で有用でしょうか?

先ほど引用した Wikipedia では、

  • ロケールやルック・アンド・フィールなど、絶対にアプリケーション全体で統一しなければならない仕組みの実装に使用される

とあります。
他には、アプリケーションの基盤となりうるフレームワークなどにも使ってよいかもしれませんね。


【補足】

個人的には、Singleton パターンを積極的に使うことはおすすめしません。
Singleton パターンにはデメリットもあります。

使う前に、

  • 本当にシングルであることが必要か?

ということはよく検討したほうがよいでしょう。

デメリットについては下記の記事でも解説していますので、合わせてご確認いただけると幸いです。

関連:シングルトン・パターンの乱用はやめよう


クラス図とコード例

クラス図

上の図は、Singleton パターンの一般的なクラス図となります。

このクラス図に対して実装の仕方はいろいろありますが、Java で推奨されている実装例は次のようになります。

public class Singleton {

    private static final Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }

    // テスト用
    public void hello() {
        System.out.println("Hello, Singleton!");
    }
}

hello メソッドはテスト用です。
実際に Singleton クラスを使ってみましょう。

final var instance = Singleton.getInstance();
instance.hello();

// 結果
// ↓
// Hello, Singleton!

Singleton クラスを使う側では、new 演算子ではなく、代わりに getInstance メソッドでインスタンスを取得します。
そして、テスト用の hello メソッドを呼び出しました。

getInstance で取得されるインスタンスは、

  • どこから呼び出しても
  • 何回呼び出しても

必ず 同一 のものとなります。
つまり、インスタンスは1つ というわけですね。

これが、Sigleton クラスを使う一連の流れになります。

シーケンス図

それでは、もう少し細かく見ていきましょう。

外からのインスタンス生成を抑止

1つめのポイントは、コンストラクタの アクセス修飾子private にすることです。

public class Singleton {

    private Singleton() {
    ^^^^^^^ <--- アクセス修飾子
    }
}

これにより、Singleton クラスの外では、Singleton のインスタンス生成ができなくなります。
例えば Main クラスで Singleton クラスを new しようとするとコンパイルエラーになります。

public class Main {
    public static void main(String[] args) {
        // コンパイルエラー
        final var instance = new Singleton();
    }
}

ただ、外から Singleton クラスのインスタンスがまったく使えないのはダメですよね。

そこで、インスタンスを取得するための getInstance メソッドが必要となります。

public class Singleton {

    private Singleton() {
    }

    public static Singleton getInstance() {
        ...
    }
}

外からは、new 演算子の代わりに getInstance を使ってインスタンスを取得します。

// インスタンスを取得
final var instance = Singleton.getInstance();

getInstance メソッドで返すインスタンスを毎回同じものに制限できれば、それは Singleton パターンとなります。

インスタンスを1つに制限

インスタンスを1つに制限するために、Java では static フィールドを使います。
static フィールドは、クラス変数とも呼ばれています。

public class Singleton {

    private static final Singleton instance = new Singleton();
            ^^^^^^                 ^^^^^^^^
    ...
}

static フィールドは、

  • クラスのインスタンスがいくつ生成されても
  • もしくはインスタンスが0個でも

実体は1つだけになります。

Singleton パターンではインスタンスは1つだけ生成します。
その1つのインスタンスを保持するのに、static フィールドは都合がよいわけですね。


マルチスレッド対応

実は、前述してきたコード例はマルチスレッド環境で安全に使えます。
それは、static フィールドの初期化時に Singleton クラスのインスタンスを生成しているためです。

public class Singleton {

    private static final Singleton instance = new Singleton();
            ^^^^^^                            ^^^^^^^^^^^^^^^
    ...
}

8.3.2. Field Initialization
...
If the declarator is for a class variable (that is, a static field) (§8.3.1.1), then the following rules apply to its initializer:
・At run time, the initializer is evaluated and the assignment performed exactly once, when the class is initialized (§12.4.2).

static フィールドの初期化処理は、そのクラスの初期化時(ロード時) に 一度だけ 実行されます。
Java 言語仕様にも明記されています。

複数のスレッドから同時に getInstance メソッドを呼び出したとしても、そのときにはクラスのロードは完了しています。
(ロードが完了していないと、そもそも getInstance メソッドを呼び出せないので…)

クラスのロードが完了しているということは、

private static final Singleton instance = new Singleton();

この処理も完了しているということです。

よって、複数のスレッドからこの Singleton クラスを使う場合でも、getInstance メソッドに synchronized キーワードは必要はありません。

public static synchronized Singleton getInstance() {
              ^^^^^^^^^^^^ <---- 必要なし!
    return instance;
}

補足

  • 正確には、Singleton クラスのインスタンス生成と getInstance メソッドはマルチスレッドに対して安全です。
    しかし、テスト用の hello メソッドは安全ではない(同期されていない) のでご注意ください。

もしマルチスレッドをまったく考慮しなくてよいのであれば、次のような実装でも問題ありません。

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }

        return instance;
    }
}

ただ、getInstancce のたびに、

if (instance == null) {
    ...
}

このチェックをするのは、ちょっと無駄な気もしますよね…

やはり、static フィールドの初期化処理でインスタンス生成してしまうのがおすすめです。
getInstance メソッドも余計な処理が入らずにシンプルになります。

まとめ

Singleton パターンは、

  • あるクラスがあり
  • そのクラスのインスタンスが最大で1つしか生成されない

ということを保証するための設計です。

ただし、デメリットもありますので、よく検討してから使うことをおすすめします。

関連:シングルトン・パターンの乱用はやめよう


関連記事

ページの先頭へ