どうもです。今日は暖かいので親子でピクニックに行ってきました。
で、はい。本題ですが。
今回はFactoryパターン編。別名SimpleFactoryとも言われています。
GoFではFactoryMethodパターンが紹介されていますが、ここではシンプルなFactoryパターンを紹介します。(FactoryMethodパターンは次回のエントリで紹介します)
というか、DIコンテナベースの話になっているので、DIコンテナ上でのFactoryパターンの使い方の説明になっています。。。
まずは事例から
いつものごとく、ダイちゃんのところのサンプルを改編!やはり今回もDIコンテナを前提に考えます。
現状のBusinessLogicは、MySQLとPostgreSQL用にそれぞれ別々のコンポーネントを用意して使うようにdiconファイルに定義されています。これはこれで正解だと思います。
でも、今回は一つのBusinessLogicで実行時に必要に応じてMySQLとPostgreSQLの両方に対応するにはどうたらよいかを考えてみます。
BusinessLogicクラス単体だけを見た場合、Conveterはdiconファイル上で静的に決定されています。これはシステムに対する要件が静的に決まるのであればFactoryは必要ありません。必要なConveterをDIするだけでOKです。
ですが、実行時にConverterを選ばないといけない場合(たとえば、ユーザがUI上から生成するDBMSの種類を変更したなど)は、dicon上で静的にマッピングしていたのでは不可能です。
現状のBusinessLogic
public class BusinessLogic { private Converter converter; public BusinessLogic(Converter converter) { this.converter = converter; } public void doBusiness(Table table) { String sql = converter.convert(table); System.out.println(sql); } }
で、MySQLとPostgreSQLが実行時に変化する場合のロジックを考えてみましょう。
doBusinessメソッドの引数でConveterのモードを指定できるようにしましょう。こうすることで実行時指定できるようにします。(便宜上、converterModeは文字列ですが、本来ならenumなどがよいかと思います)
public class BusinessLogic { // 省略 public void doBusiness(String conveterMode, Table table) { // 省略 } }
次にそのConverterのモードに応じたConverterをSeasar2から取得するFactoryクラスを作りましょう。
はい。以下です。createメソッドの引数の内容によってコンテナから取得するコンポーネントを変化させます。
diconファイルに定義してください。(DIコンテナ内で管理する前提なので、createメソッドはクラスメソッド化していません)
import org.seasar.framework.container.S2Container; public class ConverterFactory { private S2Container s2Container; public void setS2Container(S2Container container) { s2Container = container; } public Converter create(String mode) { if ("MYSQL".equals(mode)) { return (Converter) this.s2Container.getComponent("mySQLConverter"); } else if ("POSTGRESQL".equals(mode)) { return (Converter) this.s2Container .getComponent("postgreSQLConverter"); } return null; } }
引き続きBusinessLogicのほうですが、以下が最終版。コンストラクタインジェクションでConverterFactoryをDIするようにします。このようにすれば実行時動的にコンポーネントを切り替えれます。
余談ですが,上記のcreateメソッドのif文がなんか美しくないなぁと思うかもしれませんが,まぁ この程度なら実用的に問題ないと思います.if文を使わないですむ方法がFactoryMethodパターンなんですよね.それは次回で紹介します.
public class BusinessLogic { private ConverterFactory converterFactory; public BusinessLogic(ConverterFactory converterFactory) { this.converterFactory = converterFactory; } public void doBusiness(String conveterMode, Table table) { Converter converter = converterFactory.create(conveterMode); String sql = converter.convert(table); System.out.println(sql); } }
diconファイルは以下のような感じ。BusinessLogicで作るコンポーネントは一種類。
ConverterFactoryをコンストラクタインジェクションします。
<component name="conveterFactory" class="jp.xet.tutorial.s2.ConverterFactory"/> <component class="jp.xet.tutorial.s2.BusinessLogic"> <arg>conveterFactory</arg> </component>
まとめ
DIコンテナが登場してFactoryの出番が少なくなったとはいえ、上記のように動的にコンポーネントを選択しなければならない場合は、やはりFactoryの出番となります。
本来、ConverterFactory#createメソッド内で各Converterクラスの生成を行うところを、DIコンテナからgetComponentしてます。これはコンテナで対象のコンポーネントをどのようなインスタンス管理をするかで、動きが変わってきます。Converterをsingletonで管理している場合は、毎回同一のインスタンスが返されます。厳密にはFactoryは生成することが責務なので、prototypeで各Conveterクラスを管理させたほうがよいかもしれません。
prototypeにしてもsetterインジェクションで各ConverterをDIしてしまうと、createメソッドは毎回同じインスタンスを返してしまいますので、createメソッド内でgetComponentする必要があります。
というか、私の場合は基本的にはそのようにするようにしています。
<component class="org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister"> <property name="instanceDef">@org.seasar.framework.container.deployer.InstanceDefFactory@PROTOTYPE</property> <initMethod name="addClassPattern"> <arg>"jp.xet.tutorial.s2.converter"</arg> <arg>".*"</arg> </initMethod> </component>
以上、ご参考までに。