普段よく使うJavaライブラリにも、GoFのデザインパターンが隠されています。日々の作業が忙しく見逃しがちですが、たまにはじっくり一種の芸術ともいえる美しい設計を味わってみましょう。
今回の芸術
ソースファイル
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
...
...
// メモリ上に新しいXMLドキュメントを生成
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.newDocument();
// XMLの要素を生成
Element element = document.createElement("element");
Text text = document.createTextNode("text");
Attr attribute = document.createAttribute("attribute");
...
JavaでXMLのドキュメントと要素を生成するシーンですが、あらためて鑑賞してみると、重厚でミステリアスなインターフェイスに見入ってしまいます。
鑑賞のポイント
XMLのDocument
インスタンスを生成するために直接newするのではなく、わざわざDocumentBuilderFactory
クラスとDocumentBuilder
クラスを介しています。機動戦士ガンダムでガンダムを苦しめた、ドムによるジェットストリームアタック(ドムのパイロット3人組による攻撃)1を連想させる3連続のnewXXX
のインターフェイスですが、正直なところ、冗長さが目につき、あまり美しいようには見えません。しかし、裏に隠れているソースコードを読んでみれば、何か感じることがあるかもしれません。設計者の想いを一緒に探っていきましょう。
Abstarct Factoryパターンを使わない場合
それではまず、最も直感的なコードで、XMLのドキュメントと要素を生成してみましょう。なお、このセクションのソースコードは、説明のため、架空のクラスを含みます。
// Documentは要素のFactoryクラスに相当
Document document = new Document();
// XMLの要素を生成
XMLElement element = document.createXMLElement("element");
XMLText text = document.createXMLTextNode("text");
XMLAttr attribute = document.createXMLAttribute("attribute");
...
この用途だけだと特に問題はありません。では、今度は、XMLの要素の他に、SOAP2の要素も生成できるようにしてみます。
// SOAPの要素を作成
SOAPElement element = document.createSOAPElement("element");
SOAPText text = document.createSOAPTextNode("text");
SOAPAttr attribute = document.createSOAPAttribute("attribute");
...
すると、createSOAP...
のように、createXXX...
バリエーションが増えてしまいます。XML/SOAPなどのドキュメントの種類をひとつ増やすだけで、既存のクラス(Document
)にたくさんのcreateXXX...
メソッドを追加しないといけないのは美しくありませんね。
Abstarct Factoryパターンを使った場合
ここでAbstract Factoryパターンを適用してみましょう。まずはXMLドキュメントの生成からです。
// XMLドキュメントを生成する場合(DocumentはFactoryクラスに相当)
Document document = new XMLDocument(); // Documentのサブクラス
SOAPドキュメントの生成は以下のように行います。
// SOAPドキュメントを生成する場合(DocumentはFactoryクラスに相当)
Document document = new SOAPDocument(); // Documentのサブクラス
要素の生成は、どちらの場合でも以下のように行います。
// 要素を生成
Element element = document.createElement("element");
Text text = document.createTextNode("text");
Attr attribute = document.createAttribute("attribute");
抽象化されたFactoryに相当するDocument
クラスを継承して、具体的なFactoryに相当するXMLDocument
、SOAPDocument
というクラスを作ります。(このクラスは架空のものです。)すると、XML/SOAPなどのドキュメントの種類を増やしても、createXXX...
メソッドを追加する必要がなくなりました。また、同時に、Factoryが生成するモノ(Element
、Text
、Attr
など)も抽象化したため、XMLElement
/SOAPElement
などの要素の種類にとらわれず同じように扱うことができます。シンプルで美しいですね。
Abstract FactoryパターンのGoFでの定義は「関連、または依存しあうオブジェクトのファミリを、その具象クラスを指定することなしに生成するインターフェイスを提供する3」です。ここでの「関連、または依存しあうオブジェクトのファミリ」はElement
、Text
、Attr
に相当します。また、「その具象クラスを指定することなしに生成するインターフェイス」はDocument
に相当します。
Abstarct Factoryパターンはむずかしい?
Abstarct Factoryパターンは上記の通りとてもシンプルなのですが、「むずかしい」と思われがちです。私もエンジニア1,2年目の頃はピンときていませんでした。(大丈夫、あの結城浩さんでも一番難しいと感じたパターンとのことです。)むずかしく感じる理由は、登場するクラスの数が多いこともありますが、それ以上に、何がうれしいのかわかりにくいことにあると思います。
正直、上記の例や、デザインパターン入門のWEBや書籍の例程度では、Abstract Factoryパターンを使っても、そんなにうれしいことはありません。これらでは、パターンの本質をわかりやすくするためにプログラムがシンプルになっているのですが、実際にAbstract Factoryパターンの恩恵を受けられるのは、プログラムがある程度複雑なときなのです。
Abstract Factoryパターンの恩恵を受けられるシーン
結論から言うと、Abstract Factoryパターンの恩恵を最も受けられるシーンは、抽象化されたFactoryクラスの生成を隠蔽したいときだと思います。
図にすると以下のようになります。「最低限の使い方」と「本領発揮の使い方」です。
|呼び出し元|
↓ ↓
↓使う ↓Factoryを選んで生成する
↓ ↓
↓ |抽象化されたFactoryを |
↓ |継承する具体的なFactory|
↓ ↓
↓ ↓生成する
↓ ↓
|Factoryで作るモノ|
|呼び出し元|
↓ ↓
↓使う ↓使う
↓ ↓
↓ |抽象化されたFactory|
↓ ↓
↓ ↓Factoryを選んで生成する
↓ ↓
↓ |具体的なFactory|
↓ ↓
↓ ↓生成する
↓ ↓
|Factoryで作るモノ|
「最低限の使い方」と「本領発揮の使い方」では、「Factoryを選んで生成する」個所が異なります。最低限の方では、呼び出し元が抽象化されたFactoryの実装クラスを決めます。一方、本領発揮の方では、抽象化されたFactoryクラスが動的クラスローディング4や設定ファイルを使ってFactoryの実装クラスを決めます。そのため、呼び出し元ではソースコードを変えずに、具体的なFactoryの実装クラスを入れ替えることができます。
ここで、ページ冒頭のコードをもう一度見てみましょう。
// 要素のFactoryクラスに相当するDocumentを生成
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.newDocument();
// 要素を生成
Element element = document.createElement("element");
Text text = document.createTextNode("text");
Attr attribute = document.createAttribute("attribute");
DocumentBuilderFactory
は、Abstract Factoryパターンの「抽象化されたFactory」に相当します。newInstance
メソッドで、このDocumentBuilderFactory
のサブクラスである「具体的なFactory」を生成します。具体的なFactoryの実装クラスは、以下の中から好きなベンダーのものを選ぶことができます。
具体的なFactoryの実装クラスのリスト
- com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
- com.google.gdata.util.common.xml.parsing.SecureGenericXMLFactory
- com.meterware.httpunit.dom.DocumentBuilderFactoryFilter
- com.sun.msv.verifier.jaxp.DocumentBuilderFactoryImpl
- net.sf.saxon.dom.DocumentBuilderFactoryImpl
- org.allcolor.xml.parser.CDocumentBuilderFactory
- ...
これらの実装クラスは、DocumentBuilderFactory
のnewInstance
メソッド内で、次のような順番で自動的に見つけ出されます。
- Javaのシステムプロパティ(Java VM起動時のコマンドライン引数)で指定されたクラス
- JREディレクトリ内の
lib/jaxp.properties
ファイルに書かれたクラス - jarファイル内の
META-INF/services/javax.xml.parsers.DocumentBuilderFactory
ファイルに書かれたクラス(JavaのServiceLoaderの仕組みが使われる) - デフォルトの実装クラス(=
com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
)
そのため、実装クラスを変えても、呼び出し元のコードを一切変更しなくてよいのです。この仕組みには、芸術性を感じずにはいられません。
なお、冒頭のコードには、Abstract Factoryパターンが2つ隠れています5。それぞれ以下のような構成になっています。(Factoryという言葉に対してFactoryで作るモノをProductと表現しています。)
1つめのAbstract Factory
- 抽象化されたFactory
- DocumentBuilderFactoryクラス
- 具体的なFactory
- 上記リスト(「具体的なFactoryの実装クラスのリスト」)のクラス
- 抽象化されたProduct
- DocumentBuilderクラス
- 具体的なProduct
- 上記リストのクラスが生成するDocumentBuilderの実装クラス
2つめのAbstract Factory
- 抽象化されたFactory
- Document
- 具体的なFactory
- 上記リスト(「具体的なFactoryの実装クラスのリスト」)のクラスが生成するDocumentクラス
- 抽象化されたProduct
- Element, Text, Attrクラス
- 具体的なProduct
- 上記リストのクラスが生成するElement, Text, Attrの実装クラス
Abstract Factoryパターンへの専門家のコメント
多くの専門家からも、Abstract Factoryパターンを評価するコメントが寄せられています。
アラン・シャロウェイさん、ジェームズ・R・トロットさん
例えば、マルチプラットフォーム上で動作するシステムのユーザインターフェイスを開発している場合、使用しているOS別に用意されたインターフェイスオブジェクト群を使用することになります。このように、状況に応じてオブジェクト群を使い分けるために、Abstract Factoryパターンを使用することができるわけです。
株式会社ジグスさん
よく似たパターンに「Factory Method」パターンがありますが、「Factory Method」パターンは、『オブジェクト生成』の抽象化にポイントを置いたパターンであるのに対し、「Abstract Factory」パターンは、『関連するオブジェクト郡をまとめて生成するための手順』の抽象化にあります。
最後に
わざわざ美術館に行かなくても、たった数行のコードを眺めるだけで知的な愉しみを味わうことができるのは、プログラマーの醍醐味でしょう。
Abstract Factoryパターンの芸術性に共感してくださったエンジニアの方は、ぜひ当社(クオリサイトテクノロジーズ株式会社)の採用担当までご連絡ください!
関連記事
インスタンスを作る
- よく使うJavaライブラリで味わうデザインパターン - Factoryパターン
- よく使うJavaライブラリで味わうデザインパターン - Builderパターン
- よく使うJavaライブラリで味わうデザインパターン - Abstract Factoryパターン
インターフェイスをシンプルにする
他のクラスに任せる
参考URL
- Factory Method と Abstract Factory の違いを順に理解する
- Abstract factory pattern - Wikipedia
- AbstractFactory パターン - TECHSCORE
- DOM: XMLデータを作成する(1)
- DocumentBuilderFactory (Java Platform SE 8)
- DocumentBuilderFactoryの実装一覧 - Stack Overflow
- Java でプラグイン機構を簡単に実現する - mallowlabsの備忘録
- JAXPによるDOM解析
-
XMLで通信するためのプロトコル。HTTP上で使われる。 ↩
-
カッコつけて書いていますが、単に、Java標準のClassLoaderを使って、ソースコードに文字列でハードコードされたパッケージ名とクラス名からインスタンスを生成する方法のことです。 ↩
-
厳密に言うと、1つめのAbstract Factoryは、抽象化されたProductが
DocumentBuilder
の1種類しかないため、GoFで定義されているAbstract Factoryパターンではありません。 ↩