図1●一緒に使ってほしいオブジェクトを返す
図1●一緒に使ってほしいオブジェクトを返す
[画像のクリックで拡大表示]

 今回紹介する2つのパターンの名前には、どちらも「Factory(工場)」という言葉が入っています。何を作る工場なんでしょう?「オブジェクト」を生成する工場です。オブジェクトは、クラスのコピーをメモリにロードしたものであり「クラスのインスタンス」とも呼ばれます。Javaでは、MyClass m = new MyClass(); という構文でオブジェクトを生成します。newという命令を使っていることから、オブジェクトを生成することを「newする(ニューする)」とも言います。

 複数のプログラマが1つのシステムを構築する場合には「私はクラスを作る人、貴方はクラスを使う人」という役割分担ができます。クラスを使う人は、他の人が作ったクラスをnewするわけですが、ちょっと発想を変えてみましょう。クラスを作る人が、クラスをnewしたらどうでしょう。つまり、何らかのクラスをnewして返すメソッドを用意しておくのです。クラスを使う人にとって、便利になるはずです。これが、Factoryの基本的なアイディアです。ただし、GoFの2つのパターンでは、もう一ひねりも二ひねりも工夫が凝らされています。

【お役立ち度】★★★★
●一緒に使ってほしいオブジェクトを生成するFactory Methodパターン

 その名もズバリ「Factory Method(工場メソッド)」というパターンは、クラスをnewして返すメソッドを用意するというものです。クラスのメンバの中に、他のクラスのオブジェクトを返すメソッドがあるのです。何でそんなメソッドが必要なのかと言うと、一緒に使ってほしいオブジェクトだからです。

 「それなら、一緒に使う機能を1つのクラスにまとめてしまえばいいじゃないか」と思われるかもしれません。確かにその通りですが、一緒にまとめると意味的に不自然なクラスになってしまうこともあります。いろいろな機能を盛り込み過ぎると、保守性の悪いクラスになってしまうこともあります。

 現実的ではありませんが、わかりやすさを優先した例を示しましょう。おとぎ話をイメージしてください。「浦島太郎クラス」と「金太郎クラス」があるとします。それぞれ「亀クラス」と「熊クラス」と一緒に使ってほしいとします(バカバカしくてごめんなさい)。浦島太郎クラスのメンバに、亀クラスのオブジェクトを返すcreaetKame( )メソッドを用意します。金太郎クラスのメンバに、熊クラスのオブジェクトを返すcreateKuma( )メソッドを用意します。これによって、一緒に使ってほしいクラスを確実に生成できます。

 しかし、このままでは、クラスを使う人から「ぜんぜん便利じゃないよ」と文句を言われてしまうでしょう。クラスごとに名前の異なるcreateKame( )やcreateKuma( )というメソッドを覚えるのが面倒だからです。何のオブジェクトが生成されるかわからないが、どのクラスでも同じcreatePair( )という名前のメソッドを呼べば、適切なペアが得られるようにしておきましょう。

 亀オブジェクトでも熊オブジェクトでも返せるcreatePair( )メソッドを実現するためには、亀クラスと熊クラスを汎化(汎化は、プログラム上で継承として記述されます)したスーパークラスを定義しておく必要があります。createPair( )メソッドの戻り値のデータ型をスーパークラスにしておけば、亀オブジェクトでも熊オブジェクトでも返せます。

 さらに、浦島太郎クラスと金太郎クラスに、同名のcreatePair( )メソッドを確実に持たせるようにしましょう。そのためには、createPair( )メソッドを持つクラスを作り、それを浦島太郎クラスと金太郎クラスで継承し、それぞれcreatePair( )メソッドの処理内容を適切にオーバーライド(上書き変更)すればよいのです。浦島太郎クラスのcreatePair( )メソッドでは亀オブジェクトを返し、金太郎クラスのcreatePair( )メソッドでは熊オブジェクトを返します。これで、準備OKです(図1[拡大表示])。

 オブジェクトを生成して返すメソッドというアイディは、とっても素晴らしいと思います。クラスを使う人が、楽になるからです。ただし、クラスの関連を複雑にしてしまっているとも言えます。あまりにもクラスの数が多いため、どのクラスとどのクラスを一緒に使えばいいか混乱してしまいそうなときだけに利用すべきパターンである、という注意の気持ちを込めて、僭越ですが評価は4つにさせていただきました。

【お役立ち度】★★★★★
●プログラムの一部を丸ごと交換するAbstract Factoryパターン

 状況に応じて、プログラムの一部を丸ごと交換したいときがあるでしょう。たとえば、特定のOSやDBMSに依存した機能などです。皆さんなら、プログラムにどんな工夫をしますか?

 まったく同じメンバを持つ同名のクラスを2種類用意しておくという案があるでしょう。たとえば、DBMSであるSQL ServerとOracleのためのクラスを、どちらも同じDataBaseという名前のクラスにして、まったく同じメンバを持たせるのです。ソースファイルを交換するだけでDBMSを変えられ、プログラムの内容には一切変更が不要です。ただし、見た目だけでは区別しにくいため、取り扱いを誤ってしまう危険性があります。それなら、メンバは同じでクラス名だけを変えるという案はどうでしょう。SQL Server用はSqlDataBaseクラス、Oracle用はOracleDataBaseクラスとするのです。取り扱いを誤ることはないでしょうが、DBMSを変更する際に、プログラムの中にあるクラス名を変更しなければなりません。いずれも問題があるのです。

 5つ(最高点)のアイディアをお教えしましょう。Abstract Factory(抽象的な工場)パターンです。これは、必要なオブジェクトを作るクラス(「ファクトリクラス」と呼ぶ)をいくつか用意しておくというものです。Windows+SQL Server環境のためのクラス群とUnix+Oracle環境のためのクラス群を切り替えるサンプルコードをお見せしましょう(リスト1[拡大表示])。

リスト1●OSとDBMSの環境を切り替えるファクトリクラス

public abstract class OsDbFactory{
    // 抽象メソッド
    public abstract OS createOS();
    public abstract DBMS createDBMS();

    // ファクトリクラスのオブジェクトを返すメソッド
    public static OsDbFactory getFactory(String db) {
        if (db.equals("WinSql") {
            return new WinSqlFactory();
        }
        else if (db.equals("UnixOracle")) {
            return new UnixOracleFactory();
        }
        else {
            return null;
        }
    }
}

 処理内容が記述されていないメソッドを「抽象メソッド」と呼び、抽象メソッドを持つクラスを「抽象クラス」と呼びます。Javaでは、それらをabstractというキーワードで示します。抽象クラスOsDbFactory(これが抽象な工場)には、createOS( )とcreateDBMS( )という2つの抽象メソッドがあります。これらのメソッドの処理内容は、OsDbFactoryクラスを継承したWinSqlFactoryクラスおよびUnixOracleクラスで実装(処理内容を記述すること)されます。これらのクラスもファクトリクラスです。

 WinSqlFactoryクラスのcreateOS( )メソッドはWindows用のクラスのオブジェクトを返し、createDBMS( )メソッドはSQL Server用のクラスのオブジェクトを返します。同様に、UnixOracleFactoryクラスのcreateOS( )メソッドはUnix用のクラスのオブジェクトを返し、createDBMS( )メソッドはOracle用のクラスのオブジェクトを返します。

 OsDbFactoryクラスのgetFactory( )メソッドは、引数に指定された文字列に応じて、WinSqlFactoryクラスまたはUnixOracleFactoryクラスのオブジェクトを返します。このメソッドによって、Windows+SQL Server環境とUnix+Oracle環境を切り替えることができるのです。

 ファクトリクラスを使う人は、最初にgetFactory( )メソッドを呼び出して適切なファクトリクラスのオブジェクトを取得し、そのcreateOS( )とcreateDBMS( )を使って適切なクラスのオブジェクトを取得します(リスト2[拡大表示])。

リスト2●ファクトリクラスの使い方

// 変数envに環境を示す文字列が格納されているとする
OsDbFactory f = OsDbFactory.getFactory(env);

// 環境に応じたクラスのオブジェクトを取得する
OS sys = f.createOS();
DBMS db = f.createDBMS();

 2段階の手順になっていることを冗長だと感じたら、どうぞご自身のアイディアでファクトリクラスを作ってください。たとえば、createOS( )とcreateDBMS( )に環境を指定する引数を持たせ、それに応じて適切なクラスのメソッドを返すという案もいいでしょう。GoFデザインパターンに示された通りでなければいけない、という決まりはないのですから。

 次回は、FacadeパターンとChain of Responsibilityパターンを紹介します。

矢沢久雄

グレープシティ株式会社(http://www.grapecity.com)アドバイザリースタッフ

表紙ページへ