web-dev-qa-db-ja.com

これはFactoryメソッドパターンの十分な例ですか?

Factory methodパターンのWikiページには、次の例があります。

public interface IPerson
{
    string GetName();
}

public class Villager : IPerson
{
    public string GetName()
    {
        return "Village Person";
    }
}

public class CityPerson : IPerson
{
    public string GetName()
    {
        return "City Person";
    }
}

public enum PersonType
{
    Rural,
    Urban
}

public class Factory
{
    public IPerson GetPerson(PersonType type)
    {
        switch (type)
        {
            case PersonType.Rural:
                return new Villager();
            case PersonType.Urban:
                return new CityPerson();
            default:
                throw new NotSupportedException();
        }
    }
}

ここでは、インスタンス化するオブジェクトを決定するためにPersonTypeをオンに切り替えています。同じタイプの複数のオブジェクトからオブジェクトの作成を分離するためにFactory methodパターンを使用することは理にかなっていますが、上記のコードはopen-closedの原則を満たしていません。新しいタイプのIPersonがあるときはいつでも、Factoryの実装に変更を加える必要があり、将来的に考えると、時間の経過とともにこのファクトリーメソッドはかなり大きくなる可能性があります。

Factoryパターンのもう1つのかなり一般的な例は、データベースのインスタンス化で、さまざまなデータベースサーバーから選択できます。この場合、設定ファイルを作成して、インスタンス化するデータベースを指定できます。また、複数の選択肢から1つのオブジェクトのみを作成するようなものです。上記のIPersonの場合、複数の選択肢を持つ複数のオブジェクトをインスタンス化しています。 Factoryパターンは正しい選択ですか、それとも考えすぎですか?

1
Navjot Singh

上記のコードは、開閉の原則を満たしていません。新しいタイプのIPersonがあるときはいつでも、ファクトリーの実装に変更を加える必要があり、将来を考えると、時間の経過とともにこのファクトリーメソッドはかなり大きくなる可能性があります。

厳密にが開閉の原則に従っている場合、実際にはファクトリを変更しません。あなたはそれを拡張するでしょう。

たとえば、PersonTypeという名前の新しいHomelessを追加した場合、次のようにできます。

public class ExtendedFactory : Factory
{
    public override IPerson GetPerson(PersonType type)
    {
        if (type == PersonType.Homeless) return new Hobo();
        return base.GetPerson(type);
    }
}

そうは言っても、この場合OCPに従うと本当に必要以上に複雑になるので、おそらく工場を適切に変更します。

2
John Wu

はい、これは factory method の適切な例です:

  • GoF は、このパターンの実装のための2つの主要なバリアントを識別します。これは2番目のもので、Factoryconcreteです。よりクリーンなバリアントは、抽象ファクトリメソッドをオーバーライドする具体的なファクトリを持つ抽象Factoryクラスです。

  • 別のバリアントは、ファクトリメソッドの呼び出しに関するものです。基本的なパターンでは結合は作成されません。抽象Factoryクラスは依存関係の注入を目的としているため、具象メソッドは1種類の製品のみを返します。あなたの場合、それはparameterizedfactoryのより手の込んだバリアントです方法

実際、具体化されたパラメーター化された実装により、ファクトリーとすべての専門分野の間のカップリングが作成されます。

オープン/クローズの原則 の意味でのより柔軟なアプローチは、すべての特殊化を動的に( )ファクトリに登録させることです。次に、enum引数は動的ID(たとえば、登録時に返される)または文字列に置き換えられます。ただし、コードに不要な結合がなくなったとしても、ファクトリコンシューマとインスタンス化するクラスの間に依存関係が隠されています(使用できる引数を知っている必要があるため)。

0
Christophe

オープンファクトリの原則に違反する静的ファクトリメソッドの懸念は、パターンがコードのメインサイドで使用されている場合、実際には懸念になりません。アプリケーション側で使用されている場合は、問題になる可能性があります。

コードがOpen-Close原則に従うと、コードはスケーラブルになります。ただし、複雑さも増します。したがって、開閉の原則に従うかどうかの決定は、トレードオフに基づいて実行する必要があります。コードを100%オープン/クローズにすると、複雑さが非常に高くなります。

アプリケーション(ビジネス)コードとメイン(ブートストラップ)コードにコードを分離することは、アプリケーション(Web、デスクトップ、埋め込みなど)の優れた設計です。

アプリケーション側コード

  • ビジネスロジックと高レベルのポリシーが含まれます。
  • プロジェクト内のほとんどのコードはこちら側にあります。
  • ここではほとんどの原則が守られています(または少なくとも守られることが望まれます)。
  • この面は、理想的には100%の単体テストカバレッジを持つことができます。
  • この側はメイン側に依存するべきではありません。

メインサイドコード

  • 依存関係の実装と、依存関係の特定の実装を選択するためのファクトリロジックが含まれています。
  • これは、ブートストラップコードとも呼ばれます。
  • メイン側のコードは、一般的に小さく非常にシンプルである必要があり、特定のデプロイメントまたは特定のクライアント用に記述されているため、拡張する必要はありません。
  • コードの主な側面は、コードの最も外側の層です。プロジェクトの他の部分は、メインのサイドコードに依存しません。メインサイドコードは、プロジェクトの他のほぼすべての部分に依存しています。
  • メインサイドコードの性質上、変更のコストは非常に低くなっています。

まとめ

静的ファクトリパターンは、通常、複雑さが低く、変更のコストも低いメインサイドコードで使用されます。

以下のクラス図は、視覚化に役立ちます。メイン側のクラス間の相互作用は示されていません。下の図では、bootstrappingMainとdependencyProjectはメインサイドコードと見なされます Class Diagram of a generic application