私は現在、eコマースアプリケーションの設計の初期段階にあります。私はこれまでかなり簡単だったクラスのいくつかのデザインを作成していますが、Circle/Square問題に関連していると思われる問題に遭遇しました。次の(簡略化された)クラス図は、これまでに設計したものの概要を示しています。
製品バンドルは、困難が生じている場所です。物理的な製品のバンドルには、それを構成するすべての製品の合計出荷重量である出荷重量、在庫が最も少ない構成製品のレベルに等しい在庫レベルなどが含まれます。ダウンロード可能な製品のバンドルバンドルを構成するすべてのファイルの合計ファイルサイズ、バンドル内のすべてのファイルで構成されるファイル名リストなどがあります。
しかし、物理的な製品とデジタル製品の両方を含むバンドルはどうでしょうか?たとえば、誰かがDVDで同じ映画を購入したときに映画のデジタルダウンロードを提供したい場合や、ツールドフランスに関する電子書籍を自転車で提供したい場合、バンドルを入れるプロパティが混在しているとします。物理的な製品グループもデジタル製品のグループ化もありません。
私は次の解決策を考えましたが、どれも理想的ではないようです:
両方に適用可能なすべてのプロパティをスーパークラスに配置します
Productクラスに抽象メソッドを実装し、サブクラスでそれらをオーバーライドして、必要に応じて何かを実行し、そうでない場合は例外をスローします
上記と同じですが、メソッドが適切でない場合はデフォルト値を返します(デジタル製品の重量の場合は0、物理製品のファイルサイズの場合は0など)
物理プロパティ/メソッドを物理製品クラスに実装し、ファイルプロパティ/メソッドをデジタル製品クラスに実装します。両方とも、それらをすべて区別するためのインターフェイスを備えたバンドルクラスに実装します(物理は1つを実装し、デジタルは別を実装し、バンドルは両方を実装します)
上記のアプローチのほとんどは、構成要素のタイプを決定するために追加の作業を行うためにBundleクラスを必要とします。
基本的に、これらの問題に対処するために好ましい上記のアプローチがありますか、またはこれらから、機能が多すぎて基本クラスを過負荷にしない優れた設計を取得するために適用できる他のアプローチがありますか?そして、他の場所で対処する必要があるかなりの特別なケースを残しませんか?どんな入力でも大歓迎です。
あなたのユースケースは何ですか?
ドメイン駆動設計は、(部分的に)一般的なモデルではなく特定のモデルを実装することです。特定のビジネス問題を本当にうまく解決するモデルは、多くの問題を解決するモデルよりもはるかに価値があります。
私には、現在のデザインの最も一般的な部分(つまり、一度に最も多くのものになろうとしている部分)はProductBundle
のように見えます。それでは、ユースケースを検討して、その責任を減らしてみましょう。
たとえば、ショッピングカートシステムを設計している場合は、カート内のアイテムのフラットリストを蓄積し、ですべての計算(バンドル、割引など)を実行するモデルの方が適している可能性があります。おそらく何らかのルールエンジンを使用したチェックアウト。 ProductBundle
はこの設計に引き続き表示される場合がありますが、割引エンジンの実装の詳細としてのみ表示されます。
一方、さらにアイテムを注文するタイミングを通知する在庫/倉庫管理システムを実装している場合は、出荷時間や数量などを使用して、製品を個別のSKUとしてモデル化できます。 ProductBundle
はこのデザインに引き続き表示される場合がありますが、純粋に助言的な役割です(「XはYとバンドルされているため、一緒に注文する必要があります」)。
システムが両方のユースケースを満たす必要がある場合は、アプリケーションの進化に伴う摩擦の可能性を評価する必要があります。 (すでにそのような摩擦を経験しているようです。)アプリケーションを制限されたコンテキストに分割することで、この痛みを軽減できます。
バンドルクラスには2つのリストがあると思います。ダウンロード可能な製品と物理的な製品は、2つの異なるキーセットを持つ2つの異なるテーブルにあると想定しています。したがって、2つの別々のリストを持つことができます。
製品バンドルには、合計デジタルアイテム、合計デジタルサイズ、合計物理アイテム、合計物理重量を含むオブジェクトを返すメソッドがあります。0は完全に正当な回答であり、さまざまな表示目的で除外できます。ただし、必要に応じて、値ごとに1つのメソッドを使用できます。
トレードオフを行う方法に応じて、これにアプローチする方法はいくつかあります。
そのようなアプローチの1つは、Product
を受け入れ、それとは異なる情報を提供するクラスのセットを持つことです。たとえば、ShippingWeightViewAdapter
またはサブクラスから出荷重量を取得する方法を知っているProduct
は、必要に応じて集計値を提供します。
これは、一部のMVCフレームワークと同様のアプローチです。 Eclipse。あるオブジェクトに関する情報を提供するウィンドウと、それをビューに適合させる別のオブジェクトを作成できます。たとえば、選択した内容に基づいてコンパイラエラーをフィルタリングします。コンパイルされていないオブジェクト(プレーンテキストファイルなど)を選択すると、何も表示されません。
ここでも同じ原則が当てはまります。出荷重量のビューアダプタは、製品に重量がないことを確認し、ゼロと見なすことができます。その商品は、が出荷重量を持っている別の製品とバンドルされている可能性があり、ビューでは、物理的な製品の出荷重量のみを合計することでそれを処理できます。
このアプローチにはいくつかの利点があります。
Key-Valueアプローチもあります。各タイプの製品には、データベースに個別のKey-Valueアイテムとして格納された独自のプロパティセットがあります。したがって、クラスの一部としてshippingWeightプロパティはありません。むしろ、必要に応じて「shippingWeight」値を要求します。一部のeコマースシステムでは、柔軟性のためにこれを使用しています(Magento、私は信じています)。欠点は、PITAを使用できることです。しかし、私はあなたが提示したリストに追加するために言及する価値のあるオプションを考えました。
個人的には、他の方法をとる本当に正当な理由がなければ、物事を単純にするためにフィールドを親クラスに入れます。ストレージの増加が実際的な問題になる可能性はほとんどありません。ただし、配送計算などの機能の多くは、@ snowmanが説明しているような他のクラスに移動します。