Singletonパターン


 ★★☆

Singletonパターン

インスタンスが1つしか存在しないことを保証する

あるクラスのインスタンスが一つしかないことを保証したい場合があります。注意深く設計すれば、唯一のインスタンスを使いまわすことは可能でしょうが、このインスタンスが唯一であることを保障するものとはなりません。このような場合に、威力を発揮するのが Singleton パターンです。

先に、「インスタンスが一つしかないことを保証」と言いましたが、実はインスタンスが一つしか生成できないわけではありません。任意の数のインスタンスに制限できます。


Singleton パターンの適用方法として以下の2つが考えられます。

1. インスタンスの生成を制限する


例えば、データベースへのコネクションの数を制限したい場合、データベースアクセスインスタンスの生成数を制限する必要があります。また、シーケンス番号などを生成するような場合、複数のインスタンスが生成されてしまっては、その整合性に苦労することになります。

2. 無駄なインスタンスの生成を抑制する

例えば、ファイルへのアクセス処理です。ファイルへのアクセス処理は非常に負荷がかかり、処理速度に影響を及ぼします。ファイルの内容の更新頻度が高い場合はともかく、更新は行われないが、参照は頻繁に行われるようなファイルを毎回読み込むのは、効率が良いとは言えません。そんなときは、読み込んだファイルの内容を保持するキャッシュ機構を設けることで、問題を改善できます。

このキャッシュ機構はシステム内で1つあれば十分ですので、キャッシュ機構を Singleton 化します。Singleton 化することにより、ファイルアクセスのたびに実際にファイルを読み込むのではなく、システム内で共通のキャッシュを参照させることができます。


Singleton パターンの特徴は、Singleton クラスのインスタンス生成を、Singleton クラス自身が提供するオブジェクト生成用メソッドで行うことです。

まず、Singleton にしたいクラスをstaticなクラスフィールドとして用意します。これによって生成されたインスタンスはグローバル領域で管理され、どこからでも参照できます。

次に、コンストラクタを privateに指定して、Singleton クラス以外からコンストラクタの呼出し(newが使えなくなる)ができないようにします。そして、(外部よりnewが使えないため)インスタンスを取得するメソッドを用意してあげる必要があります。このメソッドは、どこからでも参照できるように、public でなおかつ static として宣言しておきます。



例題

1からのシリアルナンバーを生成するクラス SerialNumber を作りなさい。


実行結果
Start.      
0:1
1:2
2:3
3:4
4:5
5:6
End.
Start.
0:7
1:8
2:9
3:10
End.















Singletonパターンを使用しない例

SerialNumber.java
public class SerialNumber { private static int sn = 1; public synchronized int getNextSerialNumber() { return sn++; } }
Main.java
public class Main { public static void main(String[] args) { exec(new SerialNumber(), 6); exec(new SerialNumber(), 4); } private static void exec(SerialNumber sn, int n) { System.out.println("Start."); for (int i = 0; i < n; i++) { System.out.println(i + ":" + sn.getNextSerialNumber()); } System.out.println("End."); } }

Singletonパターンを使用した例

SerialNumber.java
public class SerialNumber { private int sn = 1; private static SerialNumber singleton = new SerialNumber(); private SerialNumber() {} public static SerialNumber getInstance() { return singleton; } public synchronized int getNextSerialNumber() { return sn++; } }
Main.java
public class Main { public static void main(String[] args) { exec(SerialNumber.getInstance(), 6); exec(SerialNumber.getInstance(), 4); } private static void exec(SerialNumber sn, int n) { System.out.println("Start."); for (int i = 0; i < n; i++) { System.out.println(i + ":" + sn.getNextSerialNumber()); } System.out.println("End."); } }

private で static なクラス変数を宣言し、同じ番号のシリアルナンバーが返らないようにしています。

synchronized に注意してください。これは、宣言されたメソッドが同時に複数のインスタンスから呼ばれないようにするためのものです。これによって、シリアルナンバーを参照して1加算する前に、別のインスタンスから呼ばれて、同じ数値を参照してしまうというトラブルを避けています。

コンストラクタは、誤ってnewされないようにprivate宣言されています。ただし、その代わりに、インスタンスを返すメソッド(getInstance)が用意されています。シリアルナンバーが欲しいインスタンスは、このメソッドを呼びインスタンスを受け取ります。何回呼ぼうが同じインスタンスが得られます。よって、得られるシリアルナンバーもこのインスタンスが管理しているシリアルナンバーということになります。


この例から分かるように、別に Singletonパターンを使わなくても、private で static なクラス変数で構成されたクラスを使えば1つであることが保証されます。

それでは、Singletonパターンを使わなければできないことは何でしょう? それは、インスタンスの数を1つではなく、2つや3つのように特定の数に制限することです(getInstance() の中で数を数えて、new SerialNumber() をする)。staticを使った場合は、1つに制限することしかできません。