割引モデルを実装するための既知の設計パターンはありますか?
割引モデルとは、以下を意味します。
顧客が製品X、製品Y、製品Zを購入すると、10%または100ドルの割引が適用されます。
顧客が製品Xを100ユニット購入すると、15%または500ドルの割引が適用されます。
顧客が昨年10万ドル以上持参した場合、20%の均一割引が適用されます。
顧客が製品Xを2ユニット購入した場合、製品X(または製品Y)を1ユニット無料で入手できます。
...
上記のシナリオをすべて処理するために適用できる一般的なパターンはありますか?いくつかのモデルを考えていますが、一般的なモデルに出くわすことはできません。
問題が複数の割引を適用する必要がある場合は、特定の状況下で Chain of Responsibility パターンを検討することをお勧めします。
簡単に言えば、処理したい情報を最初のプロセッサーに渡し、それから結果を返す前に、それを他のプロセッサーに渡すかどうかを決定します。
これにより、呼び出しコードを変更することなく、プロセッサの構造とシーケンスを変更できます。
戦略、デコレータ、および状態のパターンは、潜在的な開始点として私に際立っています。 2は注文の状態に依存し、3は一定期間内の顧客の状態に依存するため、状態は2または3に特に役立ちます。ストラテジーとデコレーターは他の製品よりも際立っています。ストラテジーを使用して複数の注文価格計算アルゴリズムを実装し、デコレーターを使用して注文に新しい割引を追加できるためです。
ただし、デザインパターンは単なるモデルであることを忘れないでください。適用される単一のパターンではなく、パターンのシステムがある場合があります。また、記述されたモデルを修正して、ソリューションにより適合するようにすることも検討してください。必ずしもパターンがあると言えるようにするためだけに役立つとは限らないパターンを強制するよりも、良いデザインを持っている方が良いです。
まあ、私は割引モデルを「前提条件」と「割引」のペアとして設計します。「前提条件」はメソッドを持つクラスです
_ bool IsFulfilled(Customer c);
_
または
_ bool IsFulfilled(Customer c, Order o);
_
そして、Discountにはメソッドvoid ApplyTo(Customer c)
があります。これにより、あらゆるタイプの前提条件をあらゆるタイプの割引と組み合わせることができます(これは「ブリッジパターン」の一種だと思います)。
固定数の前提条件がある場合、特定のサブタイプ(戦略パターン)を構築することで問題を解決できます。ただし、AND、ORおよびNOTのような論理ステートメントを使用して、前提条件が非常に複雑になることが許可されている場合は、条件に何らかの種類のルールインタープリターを実装することをお勧めします。ルールはプレーン単純な「ドメイン固有言語」で書かれたテキスト文字列。
同じことが「割引」クラスにも当てはまります。さまざまなタイプの割引のサブタイプを使用するか、割引ルールがテキスト形式で提供され、通訳によって評価される一般的な方法を使用できます。
異なる割引はすべて同じものであるため、IDiscountインターフェイスが必要になる可能性があります。概念的には、一般的な割引として扱います。
「この顧客の注文」クラスには、割引のコレクションがおそらく必要です。リスト?ハッシュ?リンクリスト?まだ気にしないで。割引は顧客ではなく購入に適用されます!
割引インスタンスの構築は、顧客、ショッピングカート、履歴などとは別にしてください。@ jfrankcarrが指摘したように、状況は大きく変化します。
おそらく、各割引のアルゴリズムとパラメーターは大きく異なり、予測できないため、各割引のクラスは異なります。
割引の計算がショッピングカートの変更に対応し、逆も同様であるため、多くのイベント処理が見られます。
デザインパターンアプリケーション
strategy pattern
。 IDiscountは、さまざまな割引アルゴリズムを実装するためのインターフェイスです。factory
が表示されます。確かに本格的なabstract factory pattern
、しかし分析のこの時点では単一のクラス。合理的には、適用する割引を決定して作成するための十分なコンテキストがある単一の場所が必要です。 1つのクラス。マーケティング部門のキノコパーティーが原因で、割引を適用するためのルールが後で爆発した場合でも、追加の割引構築ロジックは、このベースファクトリクラスで合体する必要があると思います。
私は見えます Chain of Responsibility
。これは、factory
のアイデアと相互に排他的ではありません。割引コレクションを繰り返す代わりに、各割引は次の人を呼び出します。この場合、「顧客の注文」クラスは割引のコレクションを保持しません。
Chain of Responsibilityの "hmmmm ...."要素は、各割引に次への参照があることだと思います。意味は順序が重要であることです。そうではありません。また、CoRの概念は、1つのオブジェクトがrequestを処理できないため、「次のより高い権限まで」渡されるという概念です。モデルが違います。唯一のリクエストは計算することです。すべての割引がこれを行います。出力または効果はnullになる可能性がありますが、すべての割引が計算されます。私は本能的に、より高い実世界の忠実度に傾いています。
仮定
何が変わり、何が変わらないのですか?
割引は非常に異なります。各ルールを構成するパラメーターの数と種類が異なります。
ショッピングカートが変更されると、割引の対象となる引数が変更されます。
利用可能な割引数の変更
この顧客の割引は、ショッピングカートの変更に伴う変更の対象となります。
この購入のコンテキストでは、ショッピング履歴は変更されません
総コストは、購入ラインと適用される割引の関数として動的に変化します。
最初のアプリケーションの後、たとえば購入数量が変わると、割引の出力が変わる場合があります。
論理的には、割引モデルはanythingになる可能性があるため、すべてのケースを事前にプログラムできるとは限りません。また、あなたの質問に答える人が、実際に何が必要かを完全に確信することもできません。しかし、あなたが現実の世界で見られる通常の種類の割引を受けると仮定すると...
大きな問題は、割引がプログラムされるのか、それともユーザーに割引を入力してほしいのかということです。上記のように、neverをプログラムすることはできませんが、通常は、すべてをプログラミングするのではなく、一般的なケースのように、より多くのデータエントリを作成することを目標としています。これは、プログラマーがすべての割引を作成するために使用されている場合でも、ある程度当てはまります。
Martin Fowlerは、「分析パターン:再利用可能なオブジェクトモデル」の「個別インスタンスメソッド」について、会計システムの「投稿ルール」を実装する方法の一部として言及していますが、ルールはあなたのルールとかなり似ているようです。詳細をお伝えしますが、著作物であり、
ユーザーインターフェイスの場合、かなり単純なユースケースを考え出すか、インタープリターとクエリビルダーを構築する必要があります。おそらく両方、1つは単純なケース、もう1つは高度なケースです。インタープリターを作成する場合、パーサージェネレーターに比べてコーディングが比較的簡単であり、遅い解析時間はおそらく実際には問題にならないので、インタープリターパターンを使用するのにこれはかなり良いケースと思われます。 (パーサージェネレーターを使用するのが好きなら、私に止めさせないでください)。
ただし、インタプリタを使ってすべてを実行しようとしないでください。ある時点では、独自の卑劣な言語でプログラミングしているだけなので、実際の言語を使用することもできます。インタプリタ言語が関数をサポートしている場合(おそらく、関数の呼び出しをサポートする必要があります-関数の定義は疑わしいです)、それらを実際の言語でコーディングできます。必要以上にこの道を下らないでください。
あなたが何をしようとも、最終的に誰かがプロモーションの30営業日以内に購入したかどうかに基づいて割引を求めます-営業日は、店舗の郵便番号またはクライアントの郵便番号によって定義された地域に休日がない場合にのみカウントされます郵便番号。したがって、事前に完璧なシステムを設計しようとしないでください。新しい割引のコードを作成し、それに応じて設計する必要がある場合があると想定してください。
これに有用なパターンがあるかどうかを尋ねている点はありますか?どのタイプのパターンが必要ですか-構造的または行動的ですか?
理想的には、このためのソフトウェアを作成する場合、必要なのはアルゴリズムだけです。次のように合計割引を計算する単純な関数:
cart.calculateDiscount(productVector);
これ以上何も必要ありません!
明確にするために:私は多くのルールがあることを理解しています-そのような表現の最も基本的なものはルールベース(データ属性のセットとその結果の割引)の形式である必要があり、上記のような関数はそれを反復して計算します。ルールが追加または削除されても、コードを変更する必要はありません。ルールベースを変更するだけです。
異なるオブジェクトが互いのAPIにアクセスする必要がある場合、またはタスクを配置するために通信する必要がある場合にのみ、パターンが必要になります。
PS:考えてみてください-ファイアウォールがパケットを処理し、パケットを通過または拒否(または変更)するとき-どのような設計パターンを使用しますか?答えは上記のどれもありません!