datchの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

【デザインパターン】ファクトリーメソッドパターン

今回はファクトリーパターンについて触れていきます。
この章は非常に長く、複数のファクトリーパターンについて取り扱っているので一つ一つ取り上げていきたいと思います。
今回は、そのなかでも中盤で紹介される『FactoryMethod』パターンです。

前回 → 「【デザインパターン】シンプルファクトリーパターン - datchの日記

参考書



さらっと読める「OREILLY Head First デザインパターン」を参考にしながら書いています。
しばらくはこいつを参考にデザインパターンについて書いていくよ!

FactoryMethodパターンって何ですか?



FactoryMethodパターンはデザインパターンのFactoryパターンの中でも最も一般的なパターンです。
前回のSimpleFactoryパターンを拡張したような形式になっています。*1
FactoryMethodパターンは、クラスをグループ単位で管理し、そのグループのインスタンスを生成するようにするデザインパターンです。
SimpleFactoryパターンを理解していれば、このパターンの理解はすんなりと行えるでしょう。
次のより高度なAbstractFactoryパターンを理解するためにも、ここでしっかりと理解していこう。

FactoryMethodパターンの利点



前回も触れたが、Factoryパターンを使うことでサブクラスを動的に変化させる部分を抜き出し、変更する部分を取り出す事が出来る。
前回のSimpleFactoryパターンではクラスの生成だけを担当していたが、
FactoryMethodパターンではそのクラスの生成も抽象メソッドとして定義され、継承によって上書きされて処理が一任されている点が違います。

簡単な使用例


前回と同じように参考書のとあるピザ屋の話を元に進めていきます。

さて、前回のSimpleFactoryパターンで、ピザの種類はいくつでも増やすことが出来るようになりました。
これでピザの種類をどんどん増やすことが出来ます。

しかし、このような話が舞い込んできました。

フランチャイズ展開をして、店によって同じ商品でも様々な個性が付けられるようにしよう!

ソフトウェアによくある変更がやってきました。
さて、ここではこの問題に対してFactoryMethodパターンを使用して対応しましょう。

まずは前回のSimpleFactoryパターンのコードを見てみましょう。

SimpleFactory

public class PizzaStore{
    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory)
    {
        this.factory = factory;
    }

    public Pizza orderPizza(String type){
        Pizza pizza;

        SimplePizzaFactory factory = new SimplePizzaFactory();
        Pizza pizza = factory.cratePizza(type);

        // 以下は全てのピザに対して同様の処理を行います
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

public class SimplePizzaFactory{
    public Pizza createPizza(String type){
        Pizza pizza = null;

        if(type.equals("チーズ")){ pizza = new CheesePizza();}
        else if(type.equals("ペパロニ")){ pizza = new PepperoniPizza(); }
        else if(type.equals("クラム")){ pizza = new ClamPizza(); }
        else if(type.equals("野菜")){ pizza = new VeggiePizza(); }

        return pizza;
    }
}

このままでは、同じ店舗でしかピザを作ることが出来ません。
ここで、拡張性を高めるためにPizzaStoreを抽象クラスにして、以下のように変更を加えてみましょう。

public abstract class PizzaStore{
    public Pizza orderPizza(String type){
        Pizza pizza;

        SimplePizzaFactory factory = createPizza();
        Pizza pizza = factory.cratePizza(type);

        // 以下に全てのピザに対して同様の処理を行います
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    abstract Pizza createPizza(String type); // ファクトリオブジェクトをこの中に定義する
}

前回はSimpleFactoryパターンに任せていた部分を、createPizza()という抽象関数として定義することで、PizzaStoreを継承して新しい店舗を作ることが出来るようになりました。
更に、createPizza()に対応するピザを定義すれば今までと同様の動作(orderPizzaに変更を加えていない!)で拡張性を高めることが出来ます!

それでは、今度は今回のフランチャイズ展開するニューヨークスタイルとシカゴスタイルのピザ屋を作っていきます。

public class NYSytlePizzaStore extends PizzaStore{
    public Pizza createPizza(String type){
        Pizza pizza = null;

        if(type.equals("チーズ")){ pizza = new NYStyleCheesePizza();}
        else if(type.equals("ペパロニ")){ pizza = new NYStylePepperoniPizza(); }
        else if(type.equals("クラム")){ pizza = new NYStyleClamPizza(); }
        else if(type.equals("野菜")){ pizza = new NYStyleVeggiePizza(); }

        return pizza;
    }

    /// ピザの調理法、梱包方法などを書かないが定義されているとする
}

public class ChicagoSytlePizzaStore extends PizzaStore{
    public Pizza createPizza(String type){
        Pizza pizza = null;

        if(type.equals("チーズ")){ pizza = new ChicagoStyleCheesePizza();}
        else if(type.equals("ペパロニ")){ pizza = new ChicagoStylePepperoniPizza(); }
        else if(type.equals("クラム")){ pizza = new ChicagoStyleClamPizza(); }
        else if(type.equals("野菜")){ pizza = new ChicagoStyleVeggiePizza(); }

        return pizza;
    }
    
    /// ピザの調理法、梱包方法などを書かないが定義されているとする
}

ここでは、前回見たSimpleFactoryパターンの技法が使われています。
ニューヨークとシカゴで作られるピザが違う事に注目してください!

これによりピザ屋を変更(正確には多態性を使う)することで今までと同様に注文が行えます。

お待たせしました。
それでは実際にピザを注文してみましょう。
以下のコードに注目して下さい。

public static void main(String[] args){
    // まずは異なる2つの店を作成する
    PizzaStore nyStore = new NYPizzaStore();
    PizzaStore chicagoStore = new ChicagoPizzaStore();

    // そして、それぞれの店に対して同様の注文を行ってみる
    Pizza nyPizza = nyStore.orderPizza("チーズ");
    Pizza chicagoPizza = chicagoStore.orderPizza("チーズ");
}

さて、上記のコードを見てもらうとまったく同様の方法で注文が行うことが出来る。
更に注目して欲しいのは、FactoryMethodパターンを使用するのにほとんど既存のコードは変更していないという点だ。
変わったのはPizzaStoreを抽象クラスにして、SimpleFactoryのプロパティをcreatePizza()に変えただけだ。
ピザを調理・梱包などをする手順には一切手を加えていない。

最後に



これがFactoryMethodパターンの全容だ。
前回に引き続き非常に短い内容になってしまったが、重要なのはFactoryMethodパターンを使用することでより柔軟性が上がったことだろう。
そして、勘の読者ならまだこのFactoryMethodパターンは変更に弱い点があるのに気づいているだろう。
その欠点を補うのが次回にやるAbstractFactoryパターンだ。
一気に理解の難易度が跳ね上がるが、これまでの2つのパターンをしっかりと理解していれば大丈夫。

ではでは!

*1:実を言うと、SimpleFactoryパターンはデザインパターンというよりはイディオムに近いものだ