web-dev-qa-db-ja.com

「単純なファクトリ」の欠点は何ですか?

私はO'Reillyの本" Head First Design Patterns "を読んでいます。ファクトリメソッドパターンを説明する前に、まずシンプルファクトリを紹介します。

彼らはピザ屋の例を使用しています。最初のステップで彼らは問題を示します:

Pizza orderPizza(string type) {
  Pizza pizza;
  if (type.equals("Pepperoni") { pizza = new PepperoniPizza(); }
  else if (type.equals("Salmon") { pizza = new SalmonPizza(); }
  // ... and so on

  pizza.Prepare();
  pizza.Bake();
  pizza.Cut();
  pizza.Pack();
  return pizza;
}

明らかな問題は、Pizzaを追加または削除するたびにPizzeriaを変更する必要があったことです。
そこで、彼らは最初に「シンプルファクトリーイディオム」を紹介します。作成部分をクラス「SimplePizzaFactory」に移動します。これで、ピザを追加または削除するときにピッツェリアを変更する必要がなくなりました。

次に、複数のピッツェリアが(複数の町に)ある場合、このアプローチはそれほど良くないと彼らは言います。
私は彼らの推論を本当に理解していません。彼らは次のコード例を示し、各ピッツェリアは上記で実装された手順を使用しないが、ピザを「焼く」、「切る」、「パックする」ために異なる方法を使用していたと言います。

BerlinPizzaFactory berlinFactory = new BerlinPizzaFactory();
Pizzeria berlinPizzeria = new Pizzeria(berlinFactory);
berlinPizza.Order("Pepperoni");

Simple Factoryを使用する代わりに、Factory Methodパターンの使用を提案しています。

まず、BerlinPizzeriaがこの手順を使用しないことになっている理由がわかりません。それはまだピッツェリアであり、注文を呼び出すときは、同じ手順を使用しています。

私の推測では、カフェテリア(私は意図的にまったく違うものを意図的に使用しています)を実装し、ファクトリー(ピザ屋とは独立しているため)を使用して準備できることを示唆しています。好きなようにピザを。

ただし、ファクトリーメソッドパターンを使用している場合でも、デフォルトの手順を使用するように強制されるものはありません。別の方法で行っていることを「隠す」のはさらに簡単です。それらのコード例はJavaおよびJavaメソッドはデフォルトで仮想です。したがって、BerlinPizzeriaを実装してOrderをオーバーライドすることができます(または明示的に宣言する必要がありました)ただし、クライアントは、私のBerlinPizzeriaが別の方法で実行していることに気付かないでしょう。

結論として、シンプルファクトリとファクトリメソッドパターンの間に大きな違いはありません。私が見ているファクトリー・メソッド・パターンの唯一の利点は、いくつかのクラス(つまり、「アウトソーシングされた」ファクトリー)を保存することです。

では、Simple Factoryの実際の欠点は何ですか?また、作成部分を「外部委託」するのはなぜ良い考えではないのですか?
または、ファクトリメソッドパターンの実際の利点は何ですか。なぜサブクラスに実装されている作成部分を強制するのが良いのでしょうか。

3
Em1

_Simple Factory_を見てみましょう__Simple Factory_の意図は簡単です。具体的なクラスのインスタンスを生成します。あなたの例に固執する:Pizzeriaがあり、order()のようなメソッドがあります:

_public enum Pizzas {
    QUADDROSTAGGIONI, SALMONE, ROMANA
}

public class Pizzeria {        
    public static Pizza order(Pizzas type){
        switch(type){
            case ROMANA: return new Romana();
            case SALMONE: return new Salmone();
            case QUADDROSTAGGIONI: return new Quaddrostaggioni();
            default: throw new NoRecipeException();
        }
    }

}
_

これは本当に工場の単純なケースです。 Salmone-Pizzaが必要だと決めたら、次のようなインスタンスをorderするだけです。

_  Pizza salmone=Pizzeria.order(Pizzas.SALMONE);
_

利点:ピザの使用とその作成を分離しました。あなたはピザを、その作成プロセスとは関係なく必要としています。自分で焼かないでピザを楽しむことができます。

欠点:ピザの特定のフレーバーのサブタイプが複数ある場合(例:ROMANABerlinStyle、ROMANAHamburgStyle)、2つの選択肢があります。

1)新しい種類を既存のピッツェリアに追加します。それはあなたのピッツェリアを散らかし、あなたはそれほど違いのないいくつかの味に対処する必要があります(例えば、ROMANABerlinStyleはニンニクを追加し、ROMANAHamburgStyleは野生のニンニクを追加します)1つのピッツェリアで。その上、あなたがベルリンスタイルまたはハンバーグスタイルのピザを消費することを知っていることは、何の利点もありません。あなたはピッツェリアからいくつかの明確に定義された種類のピザを望んでいました。

2)さらに1ステップ抽象化します。特定の種類の個々のフレーバーを_QUADDROSTAGGIONI, SALMONE, ROMANA_を独立したファクトリに分離します。つまり、BerlinPizzeriaはそのレシピを知っているので、HamburgPizzeriaも知っています。

これで、独自のレシピを持つことができるさまざまなピッツェリアを持つことができます。

上から伝統的なピッツェリア

_public class PizzeriaImpl implements Pizzeria {

    public Pizza order(Pizzas type){
        switch(type){
            case ROMANA: return new Romana();
            case SALMONE: return new Salmone();
            case QUADDROSTAGGIONI: return new Quaddrostaggioni();
            default: throw new NoRecipeException();
        }
    }

}
_

そしてベルリンスタイルのもの

_public class BerlinPizzeriaImpl implements Pizzeria{

    public Pizza order(Pizzas type){
        switch(type){
            case ROMANA: return new BerlinRomana();
            case SALMONE: return new BerlinSalmone();
            case QUADDROSTAGGIONI: return new Quaddrostaggioni();
            default: throw new NoRecipeException();
        }
    }

}
_

これでフランチャイズを始めることができます:

_public class PizzaFranchiseImpl implements Pizzeria{

    Pizzeria p;

    public  Pizza order(Pizzas type){
        return p.order(type);
    }

    public PizzaFranchiseImpl(Pizzeria p){
        this.p=p;
    };

}
_

ベルリンにいる場合は、BerlinPizzeriaImplを挿入するだけです。

利点:各都市でサポートするフレーバーを自由に決定できます(BerlinRomana、BerlinSalmone、伝統的なQuadrostaggioni)。そして、これは顧客/消費者に対して完全に透過的です。

これはOpenClosed-principleに従います:ピザの固定セットがあるため、ピザを閉じます。これは、(QUADDROSTAGGIONI、SALMONE、ROMANA)、しかしあなたは新しいフレーバー、例えばberlinstyle

さらに、各ピッツェリア自体が1つのスタイルのピザに専念しています。 BerlinPizzeriaImplは、従来のRomanaSingle Responsibilty Principle)をどのように実行するかを気にしません。

最後に重要なことですが、フランチャイズからさまざまなピザのスタイルを切り離します。フランチャイズは、注文の処理方法を知っている必要があるだけです。

編集:私は死体を美味しくて死んだ魚に置き換えました:D

3
Thomas Junk

まず第一に、私はその本を読んだし、それが大好きだ。

Simple Factoryの古典的な実装は、1つのクラスにカプセル化された "静的な作成"メソッドの集まりにすぎません。

クラスの実装をその作成から分離すると主張できますが、それだけです。それ以上の利点はなく、そのステートメントでさえ疑わしいものです。私の見解では、静的な作成メソッドを別のファイルに分離しているだけです。

ほとんどの実際のケースでは、ピザだけでなく、Burgerers、Icecreamsなどの他の多くの無関係なオブジェクトも生成する巨大な「ファイル」になってしまうため、単一責任の原則を破ります。これは、最初はほとんどのオブジェクトのcreateメソッドが2つしかないので、「自然な」ことは、それらを1つの小さなSimple Factoryクラスで孤独に感じないようにグループ化することです。

したがって、主な欠点は乱雑です。ほとんどの場合、最終的に巨大なファイルまたは多数の小さなファイルになります。

ただし、主なことは、Simple Factoryは設計パターンではなく、いくつかのケース(例:ユニットテスト)で役立つ可能性がある単なる規約です。ほとんどの場合、同じクラスに含めることができるいくつかのcreateメソッドが必要です(そのための外部クラスは必要ありません)。

1つのクラスに多数のcreateメソッドがある場合、大きな問題が発生します。このクラスの処理が多すぎるか、パラメーターの受け渡しが悪いことであるか、その他のランダムな理由であると考えています。

Factoryメソッドは、Simple Factoryとは異なり、継承を使用して、正確なクラスを指定せずにオブジェクトを作成する問題を解決します。

プロキシの章を待ちます。プロキシ(簡単に言えば)は、別のオブジェクトを「代弁」するオブジェクトです。この場合、プロキシまたは実際のオブジェクト自体になるインスタンスを作成するので、ファクトリメソッドが役立ちます。

0
FranMowinckel