肥大化したドメインオブジェクトの回避


12

DDDアプローチを使用して、肥大化したサービスレイヤーからドメインレイヤーにデータを移動しようとしています。現在、私たちのサービスには多くのビジネスロジックがありますが、それはいたるところに分散しており、継承の恩恵は受けません。

ほとんどの作業の中心である中央ドメインクラスがあります-貿易。Tradeオブジェクトは、それ自体の価格設定方法、リスクの推定方法、それ自体の検証方法などを知っています。その後、条件をポリモーフィズムに置き換えることができます。例えば、SimpleTradeはそれ自体の価格を設定しますが、ComplexTradeはそれ自体の価格を設定します。

ただし、これによりTradeクラスが膨張するのではないかと心配しています。本当に独自の処理を担当する必要がありますが、機能が追加されるにつれてクラスのサイズは指数関数的に増加します。

選択肢があります:

  1. Tradeクラスに処理ロジックを配置します。処理ロジックは現在、取引のタイプに基づいてポリモーフィックですが、取引クラスには複数の責任(価格、リスクなど)があり、大規模です
  2. TradePricingServiceなどの他のクラスに処理ロジックを配置します。Trade継承ツリーとポリモーフィックではなくなりましたが、クラスは小さくなり、テストが容易になりました。

推奨されるアプローチは何ですか?


問題ありません-移行を受け入れさせていただきます!

1
逆に注意してください:martinfowler.com/bliki/AnemicDomainModel.html
TrueWill

1
「より多くの機能が追加されると、クラスのサイズは指数関数的に増加します」-プログラマーは、そのように「指数関数的に」という言葉を誤用するよりもよく知っている必要があります。
マイケルボルグワード

@Piskvorそれはちょうど愚かです
アーニスラプサ

@Arnis L .:思慮深いコメントをありがとう。これは、引用から、「stackoverflow.com 11月22日22:19に移行された」ことに加えて、私からのコメントであることに注意してください。「これは、programmers.SEの方が良い」というコメントを削除しました。今、追加するものはありますか、それがあなたが表現したい唯一のアイデアでしたか?
Piskvorは

回答:


8

ドメインドリブンの場合は、Tradeクラスを集約ルートとして扱い、その責任を他のクラスに分割することを検討してください。

価格とリスクの組み合わせごとにTradeサブクラスになりたくないため、TradeにはPriceおよびRiskオブジェクト(組成)が含まれる場合があります。PriceおよびRiskオブジェクトは実際の計算を行いますが、Trade以外のクラスには表示されません。新しいクラスを外部に公開することなく、トレードのサイズを小さくすることができます。

大きな継承ツリーを避けるために構成を使用してください。継承が多すぎると、実際にはモデルに合わない振る舞いをしようとする状況につながる可能性があります。これらの責任を新しいクラスに引き入れるほうがよいでしょう。


同意する。問題をモデル化するのではなく、ソリューション(価格設定、RiskAssessment)を形成する振る舞いの観点からオブジェクトを考えることで、これらのモノリシッククラスを回避できます。
ギャレットホール

また同意します。構成、特定の単一の責任を持つ多数の小規模なクラス、プライベートメソッドの使用の制限、多くの幸せなインターフェイスなど
イアン

4

あなたの質問は間違いなく戦略パターンについて考えさせます。次に、あなたが呼んでいるものに似た、さまざまな取引/価格設定戦略に切り替えることができますTradePricingService

ここで得られるアドバイスは、継承ではなく構成を使用することです。


2

同様のケースで使用した解決策の1つは、アダプターの設計パターンです(参照ページには多くのサンプルコードが含まれています)。おそらく、メインメソッドに簡単にアクセスできるように、委任デザインパターンと組み合わせて使用します。

基本的に、トレーダー機能をいくつかの分離した領域に分割します。たとえば、価格、リスク、検証の処理はすべて異なる領域になる可能性があります。その後、各エリアに対して、必要なさまざまなバリアントでその正確な機能を処理する個別のクラス階層を実装できます。すべてのエリアで共通のインターフェースです。メインのTraderクラスは、必要なときに構築できる最も基本的なデータといくつかのハンドラオブジェクトへの参照に縮小されます。お気に入り

interface IPriceCalculator {
  double getPrice(ITrader t);
}
interface ITrader {
  IPriceCalculator getPriceCalculator();
}
class Tracer implements ITrader {
  private IPriceCalculator myPriceCalculator = null;
  IPriceCalculator getPriceCalculator() {
    if (myPriceCalculator == null)
      myPriceCalculator = PriceCalculatorFactory.get(this);
    return myPriceCalculator;
  }
}

このアプローチの主な利点の1つは、たとえば価格とリックの可能な組み合わせが完全に分離されているため、必要に応じて組み合わせることができることです。ほとんどのプログラミング言語のシングルスレッド継承では、これはかなり困難です。どの組み合わせを使用するかの決定は、非常に遅く計算することさえできます:-)

私は通常、アダプタクラス(IPriceCalculator上記のサブクラスなど)をステートレスにしようとします。つまり、これらのクラスには、作成する必要のあるインスタンスの数を減らすために、可能であればローカルデータを含めないでください。そのため、通常は、getPrice(ITrader)上記のように、すべてのメソッドの引数として主な適合オブジェクトを提供します。


2

ドメインについてあまり語ることはできませんが、

ほとんどの作業の中心である中央ドメインクラスがあります-貿易。

...それは私にとって臭いです。クラスのさまざまな責任をスケッチし、最終的にそれをさまざまな集計に分解しようとするでしょう。次に、関連する利害関係者/ドメインの専門家の役割や視点に基づいて、集計が設計されます。価格とリスクが同じ動作/ユースケースに関係している場合、それらはおそらく同じ集合体に属しているでしょう。しかし、それらが分離されている場合、それらは別々の集合体に属している可能性があります。

たぶんRiskEvaluationはあなたのドメインの別のエンティティであり、最終的には特定のライフサイクルを持つかもしれません(私は本当に推測することはできません...あなたのドメインを知っている、私は知らない)が、鍵は暗黙的な概念を作ることです明示的であり、振る舞いによって駆動されるのではなく、レガシーデータカップリングのみによって駆動されるカップリングを回避します。

一般に、予想される動作、および関連するコンポーネントのさまざまなライフサイクルについて考えます。グループ化されたデータの上に動作を追加するだけで、肥大化したオブジェクトが作成されます。しかし、データは既存のデータ駆動型設計に従ってグループ化されているため、それに固執する必要はありません。

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