19
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

よく使うJavaライブラリで味わうデザインパターン - Abstract Factoryパターン

Last updated at Posted at 2017-05-25

普段よく使う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のドキュメントと要素を生成してみましょう。なお、このセクションのソースコードは、説明のため、架空のクラスを含みます。

Factoryパターン
// Documentは要素のFactoryクラスに相当
Document document = new Document();

// XMLの要素を生成
XMLElement element = document.createXMLElement("element");
XMLText text = document.createXMLTextNode("text");
XMLAttr attribute = document.createXMLAttribute("attribute");
...

この用途だけだと特に問題はありません。では、今度は、XMLの要素の他に、SOAP2の要素も生成できるようにしてみます。

Factoryパターンの続き
// 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ドキュメントの生成からです。

AbstractFactoryパターン(Factory)
// XMLドキュメントを生成する場合(DocumentはFactoryクラスに相当)
Document document = new XMLDocument(); // Documentのサブクラス

SOAPドキュメントの生成は以下のように行います。

AbstractFactoryパターン(Factory)
// SOAPドキュメントを生成する場合(DocumentはFactoryクラスに相当)
Document document = new SOAPDocument(); // Documentのサブクラス

要素の生成は、どちらの場合でも以下のように行います。

AbstractFactoryパターン(Factoryが生成するモノ)
// 要素を生成
Element element = document.createElement("element");
Text text = document.createTextNode("text");
Attr attribute = document.createAttribute("attribute");

抽象化されたFactoryに相当するDocumentクラスを継承して、具体的なFactoryに相当するXMLDocumentSOAPDocumentというクラスを作ります。(このクラスは架空のものです。)すると、XML/SOAPなどのドキュメントの種類を増やしても、createXXX...メソッドを追加する必要がなくなりました。また、同時に、Factoryが生成するモノ(ElementTextAttrなど)も抽象化したため、XMLElement/SOAPElementなどの要素の種類にとらわれず同じように扱うことができます。シンプルで美しいですね。

Abstract FactoryパターンのGoFでの定義は「関連、または依存しあうオブジェクトのファミリを、その具象クラスを指定することなしに生成するインターフェイスを提供する3」です。ここでの「関連、または依存しあうオブジェクトのファミリ」はElementTextAttrに相当します。また、「その具象クラスを指定することなしに生成するインターフェイス」はDocumentに相当します。

Abstarct Factoryパターンはむずかしい?

Abstarct Factoryパターンは上記の通りとてもシンプルなのですが、「むずかしい」と思われがちです。私もエンジニア1,2年目の頃はピンときていませんでした。(大丈夫、あの結城浩さんでも一番難しいと感じたパターンとのことです。)むずかしく感じる理由は、登場するクラスの数が多いこともありますが、それ以上に、何がうれしいのかわかりにくいことにあると思います。

正直、上記の例や、デザインパターン入門のWEBや書籍の例程度では、Abstract Factoryパターンを使っても、そんなにうれしいことはありません。これらでは、パターンの本質をわかりやすくするためにプログラムがシンプルになっているのですが、実際にAbstract Factoryパターンの恩恵を受けられるのは、プログラムがある程度複雑なときなのです。

Abstract Factoryパターンの恩恵を受けられるシーン

結論から言うと、Abstract Factoryパターンの恩恵を最も受けられるシーンは、抽象化されたFactoryクラスの生成を隠蔽したいときだと思います。

図にすると以下のようになります。「最低限の使い方」と「本領発揮の使い方」です。

AbstractFactory(最低限の使い方)
|呼び出し元|
↓    ↓
↓使う  ↓Factoryを選んで生成する
↓    ↓
↓  |抽象化されたFactoryを |
↓  |継承する具体的なFactory|
↓    ↓
↓    ↓生成する
↓    ↓
|Factoryで作るモノ|
AbstractFactory(本領発揮の使い方)
|呼び出し元|
↓    ↓
↓使う  ↓使う
↓    ↓
↓  |抽象化された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
  • ...

これらの実装クラスは、DocumentBuilderFactorynewInstanceメソッド内で、次のような順番で自動的に見つけ出されます。

  • 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」パターンは、『関連するオブジェクト郡をまとめて生成するための手順』の抽象化にあります。

IT専科 - デザインパターン入門 > Abstract Factory パターン より

最後に

わざわざ美術館に行かなくても、たった数行のコードを眺めるだけで知的な愉しみを味わうことができるのは、プログラマーの醍醐味でしょう。

Abstract Factoryパターンの芸術性に共感してくださったエンジニアの方は、ぜひ当社(クオリサイトテクノロジーズ株式会社)の採用担当までご連絡ください!

関連記事

インスタンスを作る

インターフェイスをシンプルにする

他のクラスに任せる

参考URL

  1. 黒い三連星 - Wikipedia

  2. XMLで通信するためのプロトコル。HTTP上で使われる。

  3. 『オブジェクト指向における再利用のためのデザインパターン』 より

  4. カッコつけて書いていますが、単に、Java標準のClassLoaderを使って、ソースコードに文字列でハードコードされたパッケージ名とクラス名からインスタンスを生成する方法のことです。

  5. 厳密に言うと、1つめのAbstract Factoryは、抽象化されたProductがDocumentBuilderの1種類しかないため、GoFで定義されているAbstract Factoryパターンではありません。

19
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?