第2章 逆引きカタログ ロジック編
Authors:Yoshihara Hidehiko
オブジェクトを生成するnewは非常に負荷のかかる処理ですので、使いまわしが効くオブジェクトを毎回newするのはパフォーマンス上問題です。
たとえば、後述のファクトリメソッドパターンで取り上げるファクトリは毎回newする必要がないため、初めに生成したオブジェクトを再利用すぺきです。
また、データベースのコネクションプール数を制限したい場合、データベースアクセスオブジェクトの生成数を制限する必要があります。
このようにオブジェクトの生成数を制限したいときは、シングルトンパターンの出番です。
このパターンを使えば、オブジェクトを外部から直接生成させることを防ぐことができ、クラス自体に同時に生成できるオブジェクトの数を管理する機能を持たせることができます。
シングルトンパターンの特徴は、シングルトンクラスのオブジェクト生成を、シングルトンクラス自身が提供するオブジェクト生成用メソッドで行うことです。
まず,シングルトンにしたいクラスをstaticなクラスフィールドとして用意します。
これによって生成されたオブジェクトはグローバル領域で管理され、どこからでも参照できます。
次に、コンストラクタをprivateに指定して、シングルトンクラス以外からコンストラクタの呼出し(newが使えなくなる)ができないようにします。
そして、(外部よりnewが使えないため)取得するメソッドを用意してあげる必要があります。
このメソッドは、どこからでも参照できるようにpublic static宣言をする必要があります(
注1)。
ここで、生成するインスタンスの数を制限することができます(図1〜2)。
それでは具体的な例を見てみましょう。
業務処理では、ファイルの内容を読み込む処理は欠かせません。
しかし、ファイルへのアクセス処理は非常に負荷がかかり、処理速度に影響を及ぼします。
ファイルの内容の更新頻度が高い場合はともかく、更新は行われないが、参照は頻繁に行われるようなファイルを毎回読み込むのは、効率が良いとは言えません。
そんなときは、読み込んだファイルの内容を保持するキャッシュ機構を設けることで、問題を改善できます。
このキャッシュ機構はシステム内で1つあれば十分ですので、キャッシュ機構をシングルトン化しましょう。
シングルトン化することにより、システム内で共通のキャッシュテーブルを参照させることができます。
例には、後述するストラテジパターンのサンプルで使用するCSVファイル(カンマ区切りファイル)を使います。
読み込むファイルは、ファイル名“YYYYMM.csv“(例:200406.csv)として月単位で用意されているとします。
コードは
リスト1のようになります。リスト1では次の処理を行っています。
- @YYYYMM“をキーとしてMapからファイルデータの取得を行う
- AMapからファイルデータの取得ができなかった場合、CSVファイルの読み込みを行い、Mapに登録を行う
リスト1 StaffListCache.java
public class StaffListCache {
private static Log log = LogFactory.getLog(StaffListCache.class);
private static StaffListCache instance = new StaffListCache();
private Map stafflistmap = new HashMap();
private StaffContext context = new StaffContext(
AnalysisStaffCompanyA.COMPANYCODE_A);
private StaffListCache() {
}
public static StaffListCache getInstance() {
// オブジェクトを生成するのは、初めの1回
if (instance == null) {
instance = new StaffListCache();
}
return instance;
}
public List getStaffList(String filename) {
List list = (List)stafflistmap.get(filename);| A |
if (list == null) {
list = new LinkedList();
FileReader freader;
try {
freader = new FileReader(filename + ".csv");
BufferedReader breader =
new BufferedReader(freader);
String line;
while((line = breader.readLine()) != null) {
list.add(context.getStaff(line));
}
stafflistmap.put(filename, list);
freader.close();
System.out.println("ファイルから取得");
} catch (IOException e) {
log.error(e.getMessage(), e);
}
|
} else {
System.out.println("ファイルから取得");
}
return list;
}
}
◎グローバル変数としてのシングルトン
シングルトンパターンの目的はインスタンスの数を制限することですが、生成されるインスタンスを1つに制限した場合、グローバル変数としての役割も持たせることができます。
このような使い方はシングルトンパターン本来の使い方ではなく、そもそもグローバル変数自体があまり好ましいものとは考えられていません。
しかし、
グローバル変数として使えてしまうことは事実で、案外このような使い方をすることのほうが多いのも事実なのです。
また、シングルトンとして生成されたオブジェクトはJava VM内で使いまわされるので、テストの際に問題が起こります。
テストを継続して行う場合、オブジェクトの内容が他処理で書き換えられている可能性があるため、期待する結果は約束されません。
◎マルチスレッド環境での使用
今回の例ではマルチスレッド環境を想定していません。
Webシステムなどでは、複数のスレッドがシングルトンクラスを同時に使用することが頻発します。
そのため、クラス取得メソッド処理内で割り込みが発生することにより、想定しないオブジェクトが生成される可能性があります。
対応として、クラス取得用のメソッドをsynchronized指定してスレッド間で同期を取らせる必要があります(
リスト2)。
ただし、synchronizedは処理速度の低下にもつながりますので注意が必要です。
リスト2 synchronized指定による同期化
class Factory {
static Factory instance = null;
public static synchronized Factory getInstace() {
if (instance == null) {
instance = new Factory();
}
return instance;
}
}
《注1》
「グローバルに参照させたいのであれば、全メソッドをstatic宣言してしまえば良いのでは?」と思うかもしれません。
確かにグローバル参照を実現させることは可能です。ただしstatic宣言されたメソッドは抽象化することができません。
このため、クラス間の結合が強くなる問題がでてきます。