Singletonはおそらく最も非難されているソフトウェアデザインパターンでしょう。本稿では、Singleton構造が抱える問題点、およびその対処方法について考えていきます。
-
- Page 1
Singletonパターンの例
Singletonはおそらく最も非難されているソフトウェアデザインパターンでしょう。その点では、開発者たちから複雑すぎるとけなされることの多いVisitorパターンといい勝負です。しかし、Singletonのベースにある考え方は単純で、アプリケーションの実行中に特定の型のインスタンスが1つしか存在しないことを保証する、というものです。
Javaのデフォルト動作では(他の大部分の言語でも同じですが)、クラスのインスタンスはいくつでも作成できます。J2SE 5まで、Javaにはインスタンスの数を制限する直接的な方法はありませんでした。J2SE 5以降のバージョンでは、enum構造を使って特定の型のオブジェクトの数を制限することができます。また、既知のインスタンスそれぞれに一意の名前を付けることもできます。
CやC++などの言語では、enum(列挙)は単なる一連の整数値であり、その中の各要素が一意のシンボルに対応します。enumを使用することで、より表現力のあるコードを作成することができます。Javaでは、enumは他のクラスと同じように見えますが、重要な違いがいくつかあります。enumはコンストラクタ、メソッド、およびフィールドを持つことができますが、サブクラス化することはできません。enum定義では、既知のインスタンスの名前を最初に指定します。その他のインスタンスはどんな方法を使っても作成できません。
単純に考えると、Singletonパターンの候補としては、図書館システム内のカタログなどが思い浮かびます。どの本がどのカタログに追加されたのかわからないという状況は望ましくないからです。リスト1は、enum構造を使ってSingletonのカタログを実装する例を示しています。
import java.util.*; public enum Catalog { soleInstance; private Map<String,Book> books = new HashMap<String,Book>(); public void add(Book book) { books.put(book.getClassification(), book); } public Book get(String classification) { return books.get(classification); } }
それほど難しいものではありませんね。クライアントコードは既知の単一オブジェクトsoleInstanceを指していなければなりません(リスト2を参照)。
import static org.junit.Assert.*; import org.junit.*; public class CatalogTest { @Test public void singleBook() { Book book = new Book("QA123", "Schmidt", "Bugs", "2005"); Catalog.soleInstance.add(book); assertSame(book, Catalog.soleInstance.get("QA123")); } }
別のCatalogオブジェクトを次の方法で直接インスタンス化しようとすると、
new Catalog(); // this won't compile!
コメントに書かれているように、コードをコンパイルすることさえできません。
デザインパターンらしく見えないかもしれませんが、これは確かにデザインパターンの実装であり、このおかげでJava言語は大きく単純化されています。
大抵の開発者は、おそらく自己完結型のSingletonの概念の方になじみがあることでしょう。Javaでは、これはstaticな作成メソッドを用意し、コンストラクタをprivateにすることを意味します。
import java.util.*; public class Catalog { private static final Catalog soleInstance = new Catalog(); private Map<String,Book> books = new HashMap<String,Book>(); public static Catalog soleInstance() { return soleInstance; } private Catalog() { // enforce singularity } public void add(Book book) { books.put(book.getClassification(), book); } public Book get(String classification) { return books.get(classification); } }
プライベートなコンストラクタにより、他のクライアントがCatalogオブジェクトを作成できないことが保証されます。クライアントの観点からすると、それほど違っているようには見えません(リスト4を参照)。
import static org.junit.Assert.*; import org.junit.*; public class CatalogTest { @Test public void singleBook() { Book book = new Book("QA123", "Schmidt", "Bugs", "2005"); Catalog.soleInstance().add(book); assertSame(book, Catalog.soleInstance().get("QA123")); } }
この単純で無害そうに見えるパターンが、いったいどういう理由で非難されているのでしょうか。
Singletonはグローバル変数をオブジェクト指向の言葉で表したものにすぎません。ソフトウェア開発コミュニティは、グローバル変数によって引き起こされる問題をずっと前から認識していました。最大の問題は、グローバル変数はアプリケーション全体に緊密な結合を持ち込むということです。
まず検討してほしいのは、本当にグローバル変数やSingletonを使う必要があるのかという点です。Singletonにしようと思っていたクラスのインスタンスを複数作成した場合、実際にどんな害があるのでしょうか。代わりに複数のインスタンスを許すように設計を変えたらどうなるでしょうか。もちろん、意味論から言えば、現実にはマスタカタログは1つしかありません。しかし、booksハッシュマップをstaticなフィールドとしてカプセル化するCatalogクラスを設計することは可能です。この場合、クライアントは必要に応じてCatalogインスタンスを複数作成できますが、それらはすべて同じstaticフィールドを指します(ちなみに、これはMonostateと呼ばれているパターンです)。
この記事は参考になりましたか?
- デザインパターンの使い方連載記事一覧
- この記事の著者
-
japan.internet.com(ジャパンインターネットコム)
japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
-
Jeff Langr(Jeff Langr)
本格的なソフトウェアの開発に四半世紀以上携わってきたベテランのソフトウェア開発者。『Agile Java: Crafting Code With Test-Driven Development』(Prentice Hall、2005年)と、他の1冊の著書がある。『Clean Code』(Uncle Bob Martin著、Prentice Hall、2008年8月)にも寄稿している。また、ソフトウェア開発に関する記事を80件以上執筆しており、そのうちの...
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
この記事は参考になりましたか?
この記事をシェア
