datchの日記

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

【デザインパターン】アブストラクトファクトリーパターン

どうも、4件中2つの仕事を片して現在は研究とチーム開発に注力中です。
ただ、今やっている研究のif条件が多くて綺麗に構造化するのがなかなか難しいので、かなり頭を捻っています。
現在はメインの計算処理部分をやっているのですが、計算工程の最初期でクラスが既に3つ。
計算部分に使用するのが確定しているだけでクラスが10個。
更に拡張性をあげるためにFactoryとDecoratorを組み合わせたデザインパターンを使用すると、クラスが一気に倍の20個くらいになり、モジュールの凝集度は上がるけど可読性が問題になるしで、色々と頭を悩ませています。
プログラミングって本当に難しいですね。

参考書



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

流れ


  1. AbstractFactoryパターンって何?
  2. それが使えて何が嬉しいの?
  3. Factoryパターンを使わないコードからの考察
  4. 使用例

以上の流れでAbstaractFactoryについて説明していきます。

AbstractFactoryパターンって何?



FactoryMethodではCreator(前回でいうPizzaStore)というfactoryMethodを持つクラスを継承したオブジェクト(ConcreteCreator)にProduct(前回でいうPizza)の生成が一任されていました。
しかし、AbstractFactoryでは前回のProductにあたるPizzaが決定されることで、使用されるFactoryが決定され生成が一任される、という逆の関係になります。

以下のクラス図を見て下さい。
Creator(PizzaStore)が消えて、Product(Pizza)であったものがClient(Pizza)になり、商品によって生成される物が決定されるという関係になっているのが分かると思います。

画像参照元wikipedia

FactoryMethodクラス図


AbstractFactoryクラス図

それが使えて何が嬉しいの?


  1. 部品群というグループ単位でクラスの切り替えが可能
  2. FactoryMethodでは実装していた部品郡をグループ化することでクラスの整合性を保つこと出来る

Factoryパターンを使わないコードからの考察



さて、ここで話を一転させてまったくFactoryパターンを取り入れてないコードに戻ってみよう。

public class DependentPizzaStore{
    public Pizza createPizza(String style, String type)
    {
        Pizza pizza = null;
        if(style.equals("ニューヨーク")) {
            if(type.equals("チーズ")){
                pizza = new NYStyleCheesePizza();
            }else if(type.equals("野菜")){
                pizza = new NYStyleVeggiePizza();
            }else if(type.equals("クラム")){
                pizza = new NYStyleClamPizza();
            }else if(type.equals("ペパロニ")){
                pizza = new NYStylePepperoniPizza();
            }
        }else if(style.equals("シカゴ")) {
            if(type.equals("チーズ")){
                pizza = new ChicagoStyleCheesePizza();
            }else if(type.equals("野菜")){
                pizza = new ChicagoStyleVeggiePizza();
            }else if(type.equals("クラム")){
                pizza = new ChicagoStyleClamPizza();
            }else if(type.equals("ペパロニ")){
                pizza = new ChicagoStylePepperoniPizza();
            }
        }else{
            System.out.println("エラー:無効なピザの種類");
            return null;
        }
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

目を覆いたくなるように複雑になってきましたね。
さて、あたなは更にここに店舗を追加したいと思いますか?

オブジェクトの依存関係を考えてみる

前回のFactoryMethodでは店の注文元であるPizzaStoreが注文するピザを決定するという流れでした。
しかし、これを逆に考えてみましょう。
OO設計減速には依存性反転の法則という物があります。
「抽象に依存する。具象クラス(abstractではないクラス)に依存してはいけない」という法則だ。
PizzaStoreはPizzaを注文する側であり、Pizzaはその注文によって決定されます。
では、この考え方を反転してみましょう。
つまり、どのようなPizzaが作られるかで、それに対応する変更の少ないPizzaを構成する部品を決定するという流れにしてみましょう。
これによって、ConcretePizzaに部品について実装するのはなく、集約化(has-a)させることで作る事が出来るようになりました。

そう。これが、AbstractFactroyパターンです。

使用例



では、実際にAbstractFactoryパターンのコードの内容を見て行きましょう。
まずはピザの部品を作成するFactoryから見てみましょう。

public interface PizzaIngredientFactory
{
    public Dough createDough(); // 生地の作成
    public Sauce createSauce(); // ソースの作成
    public Cheese createCheese(); // チーズの作成
    public Veggies[] createVeggies(); // 野菜郡の作成
    public Pepperoni createPepperoni(); // ペパロニの作成
    public Clam createClam(); // クラムの作成
}

このPizzaIngredientFactoryを継承してニューヨークやシカゴの工場を作成することで、対応する部品*1が決定される。
詳細な実装はここでは記述しないが、対応する生地やソースの具象クラスを定義していくだけでいい。

次にPizzaがどのように変更されたかを見てみよう。

public abstract class Pizza
{
    String name;
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;

    // prepareに必要な素材を集めるので、抽象メソッドにする
    abstrat void prepare();

    // prepare以外はメソッドが変更されてない点に注目!
    void bake(){ ... }

    void cut(){ ... }

    void box(){ ... }

    ...
}

変わるのはどの部品を用意するか、という部分だ。
それ以外の工程は一切変更が加えられてない。
非常に依存性が低いコードになっているのが伺える。

さて、ではこのピザがチーズピザとして定義されたコードを見てみよう。

public class CheesePizza extends Pizza{
    PizzaIngredientFactory ingredientFactory;

    // ピザを作るには食材を提供するファクトリが必要なため、ここでファクトリのインスタンス変数を渡しています
    public CheesePizza(PizzaIngredientFactory ingredientFactory)
    {
        this.ingredientFactory = ingredientFactory;
    }

    // 必要な部品だけを用意することで、チーズピザが出来上がってきます!
    void prepare()
    {
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSause();
        cheese = ingredientFactory.createCheese();
    }
}

必要な食材工場をPizzaに教えてあげることで、作られるピザのスタイル(ニューヨーク、シカゴなど)を変更することが出来ます。
つまり、Pizzaによって作る工場が決まることで、ピザのスタイルも自動的に決定されるというものです。

どうでしょうか!?
AbstractFactoryパターンの真髄!

必要な部品グループを管理する工場を通知することで、自在にオブジェクトの種類を変化させることが出来るのです。

最後に



これで、AbstractFactoryパターンは終わりです。
このFactoryパターンの章はとても長かったですが、私の研究においても非常に役に立つデザインパターンとなっています。
大抵のものは状況によって大なり小なり振る舞いや構成を変える必要がありますが、その法則性を見ぬくことでFactoryパターンに適応することで非常にまとまりのあるコードになり、依存性も下げることが出来るでしょう!

*1:ここでは素材と言った方が自然か