Effective Java(第3版)の読書会で出た疑問点について補足します。
いつシングルトンを使用するべきなのか?
シングルトンは安易に使用するべきではない、という意見が多いかと思います。
では、どのような状況で使用するべきなのか、という根本的な疑問が出ました。
書籍の記述内容から一歩踏み込んだ内容となりますが、まとめてみます。
なぜシングルトンを安易に使用するべきではないか
シングルトンはアンチパターン扱いされることも多いですが、デメリットを以下にまとめます。
- メソッド内等どこでもインスタンスを取得できるため、インスタンス間の依存関係が分かりづらくなる
- ユニットテストでインスタンスをモック化することが困難になる
シングルトン使用の誘惑
いつシングルトンを使うべきか、という議論も重要ですが、なぜ使いたくなるのか、
という議論も重要と思います。
以下に代表的な理由をあげます。
- 生成場所が遠く離れたオブジェクト同士で同じデータを共有したい
- 機能追加時に修正量を抑えてデータを共有したい
特に機能追加時の誘惑が強いと思います。
どう使用すれば良いのか
シングルトンはあくまでインスタンスの取得方法が特殊なだけで、
他は通常のインスタンスと同等に考えるべきです。
例えば、末端のクラスのメソッド内で大きなビジネスロジックを行うインスタンスをnewしないのと同様、
メソッド内で突然シングルトンインスタンスを取得するのも避けるべきです。
このような処理を行うと、クラス間の依存関係が把握しきれなくなっていくためです。
しかし、機能追加等に影響範囲を抑えたい等の現実を考えると、以下くらいが妥協可能なラインではないかと思います。
interface A {
void process();
}
class SingletonA implements A {
private static final SingletonA INSTANCE = new SingletonA();
private SingletonA() {}
static A getInstance() {return INSTANCE;}
public void process() { /** 処理 */ }
}
class B {
private final A a;
B() {this(SingletonA.getInstance());}
B(A a) {this.a = a;}
void process() {a.process();}
}
class C {
// Bと同じようにコンストラクタでAを受け取ってDIする
}
これで、クラスB、Cの依存関係の秩序を保ちつつ、ユニットテストのためのモックへの差し替え等の両立もできます。
もちろん理想はB、Cの生成を設計しなおして自然にAのインスタンスを共有することだと思いますが、
現実との妥協点としてはこのくらいかと思います。