工場パターンはオープン/クローズド原則に違反しますか?


13

このShapeFactoryが条件付きステートメントを使用して、インスタンス化するオブジェクトを決定するのはなぜですか。将来他のクラスを追加する場合、ShapeFactoryを変更する必要はありませんか?なぜこれはオープンクローズド原則に違反しないのですか?

工場パターン設計

ShapeFactoryデザイン


2
どの「工場パターン」を正確に参照していますか?一般に、ファクトリは、オブジェクトをインスタンス化するのに役立つオブジェクトまたはメソッドです。次に、Abstract Factory Patternなど、この一般的なアイデアの特定のバリエーションがあります。各ファクトリインスタンスは、通常、条件ではなくサブクラス化によって管理される特定の選択肢のパレットを表します。
アモン


3
その情報をありがとう、これは多くのことを片付けます。これは間違いなくファクトリーパターンの例ですが、一般的にファクトリーパターンに関連付けられている抽象ファクトリーパターンの例ではありません。その記事のコードは非常に疑わしいものであり、実際のコードでそのようなものを見ることはないでしょう。
アモン

@ArmonSafai:あなたはこのブログ投稿をたくさんリンクしていますが、あなたは本当にその理由を説明していません。どういうわけか私たち全員はパターンに無知ですか?あなたと同じようにGoogleもあります。
ロバートハーヴェイ

1
@RobertHarvey私は、そのページの工場パターンが条件文を使用しているかを示すためにこのブログの記事をリンクしています
アルモンSafaiを

回答:


20

従来のオブジェクト指向の知恵は、ifステートメントを回避し、抽象クラスのサブクラスでオーバーライドされたメソッドの動的ディスパッチでそれらを置き換えることです。ここまでは順調ですね。

しかし、ファクトリパターンのポイントは、個々のサブクラスについて知る必要がなく、抽象スーパークラスのみで作業することから解放することです。アイデアは、どの特定のクラスをインスタンス化するかをファクトリがあなたよりよく知っているということであり、スーパークラスによって公開されたメソッドでのみ作業する方が良いでしょう。これはしばしば真実であり、価値のあるパターンです。

したがって、ファクトリクラスをif記述することでステートメントを無視することはできません。それは、呼び出し元に特定のクラスを選択する負担シフトする正確パターンは避けるようになっているものを。いないすべての原則は、(実際には、絶対的原則は絶対的ではありません)、そしてあなたは、このパターンを使用する場合は、それからの利益は、使用していないの利益よりも大きいことを前提としたいですif


2
多くのを使わずにファクトリパターンを作成することは完全に可能ifです。これを実現する方法の簡単な例については、@BЈовићの回答を参照してください。ダウン投票。
デビッドアルノ


11
@DavidArno当然、具体的なクラスを選択するにはさまざまな方法があります。Service Locatorは1つで、構成可能なIoCコンテナーはもう1つです。これらは単なる実装の詳細です。Killianのメインメッセージを損なうことはありません。つまり、ファクトリーは、呼び出し元がインスタンス化する具体的なクラスを決定する必要をなくすということです。詳細に夢中にならないでください。
ロバートハーベイ

1
質問に決して答えない素晴らしい声明。
マーティンマート

1
@ R.Schmitzあなたの仮定は間違っていると思います。多くの人がOPからこの質問を見逃したと思います:「将来、他のクラスを追加したい場合、ShapeFactoryを変更する必要はありませんか?」明らかに、新しい機能を追加するには既存のコードを変更する必要があるため、OPはこのパターンがOCPに違反していると考えて混乱しています。この質問に対する正しい答えは私の答えにあります。簡単な答え:そのコードをそのままにして、既存の機能をEXTEND(変更しない)に抽象ファクトリパターンを適用します。このため、Kilianの回答は質問に対応していません。
hfontanez

4

この例では、最も単純であるため、おそらく条件ステートメントを使用します。より複雑な実装では、マップまたは構成を使用するか、クラスを自分で登録できる何らかのレジストリを使用する場合があります。ただし、クラスの数が少なく、頻繁に変更されない場合、条件を使用しても問題はありません。

将来、条件を拡張して新しいサブクラスのサポートを追加することは、厳密に言えば、オープン/クローズの原則に違反することになります。「正しい」解決策は、同じインターフェースを持つ新しいファクトリーを作成することです。ただし、O / C原則の遵守は、KISSやYAGNIなどの他の設計原則と常に比較検討する必要があります。

とはいえ、表示されるコードは明らかに工場の概念を示すために設計されたサンプルコードであり、それ以外は何もありません。たとえば、例のようにnullを返すのは本当に悪いスタイルですが、より複雑なエラー処理ではポイントがわかりにくくなります。サンプルコードは製品品質のコードではありません。


マップ/構成/レジストリがどのように機能するか説明していただけますか?
アーモンサファイ

@ArmonSafai:以下に例を示します:jkfill.com/2010/12/29/self-registering-factories-in-c-sharp
JacquesB

自己登録ファクトリは、未使用の(つまり、odrで使用された)グローバル変数がツールチェーンによって破棄されるため、静的ライブラリ内のC ++では不可能です。
void.pointer

@ArmonSafaiは、goo.gl
RYuNSMの

2

パターン自体は、Open / Closed Principle(OCP)に違反しません。ただし、パターンを誤って使用すると、OCPに違反します。

この質問に対する簡単な答えは次のとおりです。

  1. Factory Method Pattern を使用して基本機能を作成します。
  2. Abstract Factoryパターンを使用して機能を拡張する

提供された例では、基本機能は円、長方形、正方形の3つの形状をサポートしています。将来、三角形、五角形、六角形をサポートする必要があるとします。これを行うにはなし OCPに違反し、あなたの新しい形を(と呼ばれるのを聞かせてサポートするための追加の工場作成する必要がありますAdvancedShapeFactory使用した後)とAbstractFactoryあなたが必要な形状何でも作成するために作成するために必要なものを工場決めることを。


基本的に私たちはあなたの提案から何を得るそうでないので、私はずっと、(最高である真に構成IoCコンテナの不在で)自己の登録工場のソリューションを好む工場の工場、とのこと、物事はちょうど過度に複雑得るとき。
Nom1fan

1

Abstract Factoryパターンについて話している場合、意思決定は多くの場合、ファクトリ自体ではなく、アプリケーションコードで行われます。インスタンス化する具体的なファクトリを選択し、ファクトリによって生成されたオブジェクトを使用するクライアントコードに渡すのは、そのコードです。Javaの例の最後を参照してください:https : //en.wikipedia.org/wiki/Abstract_factory_pattern

意思決定は必ずしもif陳述を意味するものではありません。構成ファイルから具体的なFactoryタイプを読み取り、マップ構造などから派生させることができます。



呼び出し元の私がインスタンス化する具体的なクラスを決定している場合、なぜ抽象ファクトリーに悩まされるのですか?
ロバートハーヴェイ

「発信者」を定義してください。私の答えで説明したように、グローバルアプリケーションコードがあり、次にファクトリを使用してオブジェクトを生成する必要があるコードがあります。後者は実際にインスタンス化する具象クラスを知らないである必要がありますが、いくつかの他のコンテキストのコードがあり ...それについて知っているとアップそれ新しい
guillaume31

0

このファクトリを使用してクラスレベルで開閉を考える場合、たとえば、1つのShapeを取得して面積を計算する他のクラスがある場合(典型的な例)、このクラスはOpenCloseです。変更せずに新しいタイプの形状の面積を計算できます。次に、シェイプを描画する別のクラス、N個のシェイプを取り、より大きいシェイプを返す別のクラスがあります。一般に、シェイプを処理するシステム内の他のクラスは(少なくともシェイプについて)Open-Closeであると考えることができます。設計を見ると、工場はシステムの残りの部分を開閉でき、もちろん工場自体は開閉できません。

もちろん、何らかのファクトリーを動的にロードすることで、このファクトリーを開閉することもできます。システム全体を開閉することができます(例えば、クラスパスにいくつかのjarをドロップする新しい図形を追加できます)。あなたが評価する必要があるのは、この余分な複雑さは構築しているシステムに依存する価値があることです。すべてのシステムがプラグ可能な機能を必要とするわけではなく、すべてのシステムが完全に開閉する必要はありません。


0

Liskov置換の原則としてのオープンクローズド原則は、クラスツリー、継承階層に適用されます。この例では、ファクトリクラスはインスタンス化するクラスのファミリーツリーにないため、これらのルールに違反することはできません。GetShape(またはより適切な名前のCreateShape)がShape基本クラスに実装されている場合、違反が発生します。


-2

それはすべて、どのように実装するかによって異なります。std::mapオブジェクトを作成する関数への関数ポインターを保持するために使用できます。その場合、オープン/クローズの原則に違反しません。またはスイッチ/ケース。

とにかく、ファクトリパターンが気に入らない場合は、常に依存性注入を使用できます。


6
スイッチ/ケースは条件式よりもどのように優れていますか?実際に異なる実装のレジストリが必要な場合(たとえば、DIコンテナ実装など)、データとしてコードを表すためにmap / dict / tableを使用するのは良いことです。しかし、ほとんどの工場では、同じタイプの異なるコールバックを持つ必要はありません!あなたがそれを提案している理由はよくわかりません。また、多くのDIコンテナはファクトリオブジェクトの観点から実装されているため、ファクトリの代わりにDIを使用することを提案するのは少し循環的です。
アモン

1
@amon工場ではなく、他の種類のDIを使用するつもりでした。
BЈовић


1
工場はどのポインターを使用するかをどのように決定しますか?最終的にあなたは決断を下さなければなりません。
-whatsisname

@armonSafai ???
BЈовић
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.