web-dev-qa-db-ja.com

クラス構造の決定

私は現在、eコマースアプリケーションの設計の初期段階にあります。私はこれまでかなり簡単だったクラスのいくつかのデザインを作成していますが、Circle/Square問題に関連していると思われる問題に遭遇しました。次の(簡略化された)クラス図は、これまでに設計したものの概要を示しています。

  • システムには2つのクラスの販売可能な製品があります
    • 実世界に存在するオブジェクトを表す物理的な製品(自転車、レゴ、CDなど)
    • Webで購入できるものを表すダウンロード可能な製品(MP3、eBookなど)
  • 物理的な製品には、出荷重量などの属性があります
  • ダウンロード可能な製品には、ファイルの種類やサイズなどの属性があります
  • 製品自体は、製品(コンポーネント)で構成できます。アルバムはトラックで構成され、バイクはフレーム、ホイール、ハンドルバー、ギアなどで構成され、それぞれがそれ自体で製品です。
  • 3番目のタイプの製品である製品バンドルは、他の点では無関係な製品をグループ化して1つのユニットとして販売するためのものです(自転車をポンプやヘルメットなどにバンドルしたり、電子書籍のコレクションをバンドルしたりできます)。 。

Class Diagram

製品バンドルは、困難が生じている場所です。物理的な製品のバンドルには、それを構成するすべての製品の合計出荷重量である出荷重量、在庫が最も少ない構成製品のレベルに等しい在庫レベルなどが含まれます。ダウンロード可能な製品のバンドルバンドルを構成するすべてのファイルの合計ファイルサイズ、バンドル内のすべてのファイルで構成されるファイル名リストなどがあります。

しかし、物理的な製品とデジタル製品の両方を含むバンドルはどうでしょうか?たとえば、誰かがDVDで同じ映画を購入したときに映画のデジタルダウンロードを提供したい場合や、ツールドフランスに関する電子書籍を自転車で提供したい場合、バンドルを入れるプロパティが混在しているとします。物理的な製品グループもデジタル製品のグループ化もありません。

私は次の解決策を考えましたが、どれも理想的ではないようです:

  • 両方に適用可能なすべてのプロパティをスーパークラスに配置します

    • Bundleクラスは、スーパークラスで定義されたメソッドを簡単にオーバーライドして、複合値を計算できるようにします(すべての製品の重量を合計するなど)。
    • これは本当に貧弱なカプセル化のようです
    • デジタル製品にのみ実際に適用できる物理的な製品に関する方法があり、その逆もあります。
    • スーパークラスは「神オブジェクト」になり始めます
    • オブジェクトのストレージ要件は、オブジェクトの実際のタイプに適用されない属性を常に保存する必要があることを意味します(ダウンロードの場合は常に出荷重量0、物理的な製品の場合はファイルサイズ0などを保存します)
  • Productクラスに抽象メソッドを実装し、サブクラスでそれらをオーバーライドして、必要に応じて何かを実行し、そうでない場合は例外をスローします

    • カプセル化の問題を実際には解決しません
    • 製品を扱うコードは、処理されるオブジェクトの実際のクラスを決定したり、例外を処理したりするために準備する必要があります。
    • リスコフの置換原則に違反する可能性はありますか?
  • 上記と同じですが、メソッドが適切でない場合はデフォルト値を返します(デジタル製品の重量の場合は0、物理製品のファイルサイズの場合は0など)

    • コードは例外やクラスを処理する必要がなくなりました
    • 不適切な方法で製品を処理しようとすると、予期しない論理エラーが発生する可能性があります
  • 物理プロパティ/メソッドを物理製品クラスに実装し、ファイルプロパティ/メソッドをデジタル製品クラスに実装します。両方とも、それらをすべて区別するためのインターフェイスを備えたバンドルクラスに実装します(物理は1つを実装し、デジタルは別を実装し、バンドルは両方を実装します)

    • より良いカプセル化
    • ボイラープレートコードのかなりの部分を追加します(実装されるインターフェイスなど)
    • Bundleクラスは、そのコンポーネントクラスがどのタイプであるかを認識する必要があります。
    • 特定の状況に適さないメソッドが残っている場合に、Bundleクラスが何をすべきかという問題が残っています。バンドルの製品がすべてデジタルの場合、getShippingWeightは0を返すか、例外をスローする必要がありますか?
  • まだ私には起こらなかった他の解決策
    • ????

上記のアプローチのほとんどは、構成要素のタイプを決定するために追加の作業を行うためにBundleクラスを必要とします。

基本的に、これらの問題に対処するために好ましい上記のアプローチがありますか、またはこれらから、機能が多すぎて基本クラスを過負荷にしない優れた設計を取得するために適用できる他のアプローチがありますか?そして、他の場所で対処する必要があるかなりの特別なケースを残しませんか?どんな入力でも大歓迎です。

2
GordonM

あなたのユースケースは何ですか?

ドメイン駆動設計は、(部分的に)一般的なモデルではなく特定のモデルを実装することです。特定のビジネス問題を本当にうまく解決するモデルは、多くの問題を解決するモデルよりもはるかに価値があります。

私には、現在のデザインの最も一般的な部分(つまり、一度に最も多くのものになろうとしている部分)はProductBundleのように見えます。それでは、ユースケースを検討して、その責任を減らしてみましょう。

たとえば、ショッピングカートシステムを設計している場合は、カート内のアイテムのフラットリストを蓄積し、ですべての計算(バンドル、割引など)を実行するモデルの方が適している可能性があります。おそらく何らかのルールエンジンを使用したチェックアウト。 ProductBundleはこの設計に引き続き表示される場合がありますが、割引エンジンの実装の詳細としてのみ表示されます。

一方、さらにアイテムを注文するタイミングを通知する在庫/倉庫管理システムを実装している場合は、出荷時間や数量などを使用して、製品を個別のSKUとしてモデル化できます。 ProductBundleはこのデザインに引き続き表示される場合がありますが、純粋に助言的な役割です(「XはYとバンドルされているため、一緒に注文する必要があります」)。

システムが両方のユースケースを満たす必要がある場合は、アプリケーションの進化に伴う摩擦の可能性を評価する必要があります。 (すでにそのような摩擦を経験しているようです。)アプリケーションを制限されたコンテキストに分割することで、この痛みを軽減できます。

2

バンドルクラスには2つのリストがあると思います。ダウンロード可能な製品と物理的な製品は、2つの異なるキーセットを持つ2つの異なるテーブルにあると想定しています。したがって、2つの別々のリストを持つことができます。

製品バンドルには、合計デジタルアイテム、合計デジタルサイズ、合計物理アイテム、合計物理重量を含むオブジェクトを返すメソッドがあります。0は完全に正当な回答であり、さまざまな表示目的で除外できます。ただし、必要に応じて、値ごとに1つのメソッドを使用できます。

0
bowlturner

トレードオフを行う方法に応じて、これにアプローチする方法はいくつかあります。

そのようなアプローチの1つは、Productを受け入れ、それとは異なる情報を提供するクラスのセットを持つことです。たとえば、ShippingWeightViewAdapterまたはサブクラスから出荷重量を取得する方法を知っているProductは、必要に応じて集計値を提供します。

これは、一部のMVCフレームワークと同様のアプローチです。 Eclipse。あるオブジェクトに関する情報を提供するウィンドウと、それをビューに適合させる別のオブジェクトを作成できます。たとえば、選択した内容に基づいてコンパイラエラーをフィルタリングします。コンパイルされていないオブジェクト(プレーンテキストファイルなど)を選択すると、何も表示されません。

ここでも同じ原則が当てはまります。出荷重量のビューアダプタは、製品に重量がないことを確認し、ゼロと見なすことができます。その商品は、出荷重量を持っている別の製品とバンドルされている可能性があり、ビューでは、物理的な製品の出荷重量のみを合計することでそれを処理できます。

このアプローチにはいくつかの利点があります。

  • それは拡張可能です。新しいプロパティを追加しますか?それに合わせて新しいビューアダプタを追加します。これが何らかのWebフレームワークにプラグインしている場合は、多くのコードさえ必要ないかもしれません。
  • 製品は 単一責任の原則 に固執することができます。それらにはデータが含まれており、データを集約する方法も知る必要はありません。
  • ビューアダプタには、単一のデータを集約するという単一の責任もあります。
  • さまざまな種類の製品がお互いを知る必要はありません。スーパークラスにデフォルトのメソッド実装を保持する必要はありません。また、サブクラスが持っていないプロパティに対して「0を返す」必要もありません。各クラスには独自のプロパティのみがあります。
  • ビューアダプタクラスは、他の任意のクラスにプラグインできます。それらには、 インターフェイス分離 (I in [〜#〜] solid [〜#〜] )があり、それらを維持するのに役立ちます。疎結合。
0
user22815

Key-Valueアプローチもあります。各タイプの製品には、データベースに個別のKey-Valueアイテムとして格納された独自のプロパティセットがあります。したがって、クラスの一部としてshippingWeightプロパティはありません。むしろ、必要に応じて「shippingWeight」値を要求します。一部のeコマースシステムでは、柔軟性のためにこれを使用しています(Magento、私は信じています)。欠点は、PITAを使用できることです。しかし、私はあなたが提示したリストに追加するために言及する価値のあるオプションを考えました。

個人的には、他の方法をとる本当に正当な理由がなければ、物事を単純にするためにフィールドを親クラスに入れます。ストレージの増加が実際的な問題になる可能性はほとんどありません。ただし、配送計算などの機能の多くは、@ snowmanが説明しているような他のクラスに移動します。

0
GrandmasterB