2010年5月23日日曜日

Abstract Factoryパターン

前に紹介したパターンでは部品を抽象化して、マネージャーとかファクトリーに当たるところはせいぜいインターフェースで定義したAPIのみを使うという形でしたが、今度はファクトリーも抽象化します。ちなみに、ファクトリーが抽象的なら、そのファクトリーが扱うパーツも抽象的です。

※部品というより製品って言った方がいいかもな・・・

まず、抽象的なPartは以下のような感じです。
public abstract class Part {
    protected String name;

    public Part(String name) {
        this.name = name;
    }

    public abstract void doSomething();
}

nameフィールドがprotectedになっていますが、これは子クラスでもnameフィールドにアクセス出来るようにするためです。
そのPartを扱うFactoryは以下のようにします。
public abstract class Factory {
    public abstract Part createPart(String name);
}

もし、種類の違うPartを用意したければ、Partクラスとは違うクラスを作成し、Factoryクラスの中にもう一つcreateメソッドを用意すればいいです(*1)

抽象的なクラスがあれば、具体的なクラスもあります(ようは、抽象的なクラスを継承したクラスを作るのです)。具体的なクラスは例として2種類用意しました。

まず、1つ目のABCPartは以下の感じです。doSomething()はとりあえずはnameを表示するだけにしました。
import factory.Part;

public class ABCPart extends Part {
    public ABCPart(String name) {
        super(name);
    }

    public void doSomething() {
        System.out.println("ABCPart - name: " + name);
    }
}
ABCPartを扱うABCFactoryは以下の通りです。
import factory.Factory;
import factory.Part;

public class ABCFactory extends Factory {
    public Part createPart (String name) {
        return new ABCPart(name);
    }
}
2つ目はDEFPartです。ソースは以下の通りです。こちらのdoSomething()もnameを表示するだけですが、表示形式を変えています。
import factory.Part;

public class DEFPart extends Part {
    public DEFPart(String name) {
        super(name);
    }

    public void doSomething() {
        System.out.println("DEFPartです。名前は " + name + " です。");
    }
}
DEFPartを扱うDEFFactoryは以下の通りです。
import factory.Factory;
import factory.Part;

public class DEFFactory extends Factory {
    public Part createPart (String name) {
        return new DEFPart(name);
    }
}
Factoryを扱うMainは以下の通りです。
import factory.Factory;
import factory.Part;
import abc_factory.ABCFactory;
import def_factory.DEFFactory;

public class Main {
    private static final String ABC_FACTORY = "ABCFactory";
    private static final String DEF_FACTORY = "DEFFactory";

    public static void main(String[] args) {
 // TODO argsの長さが0だったり、2以上の場合があるので、その時はSystem.exit(0)を呼んで終了させる。

        Factory factory = createFactory(args[0]);

        Part part = factory.createPart("sample");
        part.doSomething();
    }

    private static Factory createFactory(String factoryName) {
        if (ABC_FACTORY.equals(factoryName)) {
            return new ABCFactory();
        } else if (DEF_FACTORY.equals(factoryName)) {
            return new DEFFactory();
        } else {
            return null;// TODO こうすると呼び出し側でnullチェックをしないといけないので望ましくない。
        }
    }
}
ABCFactoryとDEFFactoryの2種類があるので、引数で選択します。ですが、実際にFactoryやPartに処理をお願いするときにはどちらのFactory、Partを使っているのか気にする必要はありません。前回やったパターンと同じで、共通のAPIを利用するだけです。

ということで、今回はFactoryも抽象化してみました。前回同様、具体的なことは知らなくても処理をすることができます。

ちなみに、Mainクラスは具体的なクラスをimport文を利用して知っちゃっています。Mainクラスも具体的なクラスを知らないようにする方法は以下のとおりです。
public static Factory createFactory(String factoryName) {
    Factory factory = null;
    try {
        factory = (Factory)Class.forName(factoryName).newInstance();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
        return factory;
    }
}
これはクラス名を引数にとり、ClassクラスのforName()で、指定したクラスを動的に読み込みます。読み込んだクラスのインスタンス化はnewInstance()です。こうすることで、import abc_factory.ABCFactoryと書いたり、new ABCFactory();と書く必要がなくなります。言い換えると、MainはABCFactory()などを知っておく必要はないということです。

関連パターン
Builderパターン」どちらも複雑なインスタンスを生成するパターン
Factory Methodパターン」FactoryクラスのcreateのところをFactory Methodパターンにすることがある。
Compositeパターン」ファクトリーで作られる部品をComposite(つまり、部品の中に更なる部品)になる場合がある。
Singletonパターン」ファクトリーをいくつ作っても仕方がないので、それをSingletonパターンにすることがある。


*1 なので、Factory Methodパターンを利用するのが良いかと

0 件のコメント:

コメントを投稿