62

私は、単一責任の原則をかなり真剣に受け止めているプロジェクトに参加しています。少人数制のクラスが多く、物事はとてもシンプルです。ただし、貧血ドメイン モデルがあります。どのモデル クラスにも動作はありません。それらは単なるプロパティ バッグです。これは私たちの設計に対する不満ではありません - 実際にはうまく機能しているようです

設計レビュー中に、新しい動作がシステムに追加されるたびに SRP が提示されるため、通常、新しい動作は新しいクラスになります。これにより、物事を非常に簡単に単体テストできますが、関連する場所から動作を引き離すように感じるので、時々当惑します。

SRP を適切に適用する方法についての理解を深めようとしています。SRP は、同じコンテキストを共有するビジネス モデリング動作を 1 つのオブジェクトに追加することに反対しているように思えます。そのオブジェクトは必然的に、複数の関連することを行うか、1 つのことを行うが形を変える複数のビジネス ルールを知っているためです。その出力の。

もしそうなら、最終結果はAnemic Domain Modelのように感じられます.これは確かに私たちのプロジェクトに当てはまります. しかし、Anemic Domain Model はアンチパターンです。

この 2 つの考えは共存できますか?

編集:コンテキスト関連のリンクのカップル:

SRP - http://www.objectmentor.com/resources/articles/srp.pdf
貧血ドメイン モデル - http://martinfowler.com/bliki/AnemicDomainModel.html

私は、預言者を見つけて、彼らの言うことを福音として従うのが好きなような開発者ではありません。したがって、「これがルールだ」と述べる方法としてこれらへのリンクを提供するのではなく、2 つの概念の定義のソースとして提供します。

4

7 に答える 7

13

Rich Domain Model (RDM) と Single Responsibility Principle (SRP) は必ずしも相反するものではありません。RDM は、SRP の非常に特殊化されたサブクラス (「データ Bean + コントローラー クラス内のすべてのビジネス ロジック」(DBABLICC) を提唱するモデル) とより対立しています。

Martin のSRP の章を読むと、彼のモデムの例が完全にドメイン層にあることがわかりますが、DataChannel と Connection の概念は別のクラスとして抽象化されています。クライアント コードの抽象化に役立つため、彼はモデム自体をラッパーとして保持します。単なる階層化よりも、適切な(リ) ファクタリングが重要です。結束と結合は、依然として設計の基本原則です。

最後に、次の 3 つの問題があります。

  • Martin 自身も指摘しているように、さまざまな「変化の理由」を理解するのは必ずしも容易ではありません。YAGNI やアジャイルなどの概念そのものが、将来の変更理由の予測を思いとどまらせます。「時期尚早で予想される変更の理由」は、SRP を適用する際の実際のリスクであり、開発者が管理する必要があると考えています。

  • 前に加えて、SRP の正しい(しかし不要なアナル) アプリケーションでさえ、望ましくない複雑さをもたらす可能性があります。あなたのクラスを維持しなければならない次の貧しい人々について常に考えてみてください。些細な動作を独自のインターフェイス、基底クラス、および 1 行の実装に勤勉に抽象化することは、単に単一のクラスであるべきだったものを彼が理解するのに本当に役立つでしょうか?

  • 多くの場合、ソフトウェア設計は、競合する力の間で最善の妥協点を得ることです。たとえば、レイヤード アーキテクチャはほとんどが SRP の優れたアプリケーションですが、たとえばビジネス クラスのプロパティをブール値から列挙型に変更すると、すべてのレイヤーに波及効果があるという事実についてはどうでしょうか。 - データベースからドメイン、ファサード、Web サービス、GUI まで? これは悪い設計を示していますか?必ずしもそうではありません: それは、あなたのデザインが変化の 1 つの側面を別の側面に有利にするという事実を示しています。

于 2010-06-03T08:28:53.687 に答える
9

「はい」と言わざるを得ませんが、SRP を適切に実行する必要があります。同じ操作が 1 つのクラスにのみ適用される場合、それはそのクラスに属しますね。同じ操作が複数のクラスに適用される場合はどうなりますか? その場合、データと動作を結合する OO モデルに従いたい場合は、操作を基本クラスに入れますよね?

あなたの説明から、基本的に操作のバッグであるクラスになってしまうのではないかと思うので、基本的に C スタイルのコーディングを再作成しました: 構造体とモジュールです。

リンクされた SRP の論文から: 「SRP は最も単純な原則の 1 つであり、正しく理解するのが最も難しいものの 1 つです。

于 2009-09-09T11:42:14.400 に答える
7

SRP 論文からの引用は非常に正しいです。SRP を正しく理解するのは困難です。この 1 つと OCP は SOLID の 2 つの要素であり、プロジェクトを実際に完了するためには、少なくともある程度緩和する必要があります。いずれかを過度に適用すると、非常に迅速にラビオリ コードが生成されます。

「変更の理由」が具体的すぎる場合、SRP は実際にばかげた長さになる可能性があります。POCO/POJO の「データ バッグ」でさえ、フィールドのタイプの変更を「変更」と見なすと、SRP に違反していると考えることができます。常識的には、フィールドの型の変更は「変更」の必要な許可であると言うと思いますが、組み込みの値の型のラッパーを備えたドメイン層を見てきました。ADM をユートピアのように見せる地獄。

多くの場合、読みやすさや望ましい結束レベルに基づいて、現実的な目標を設定することをお勧めします。「このクラスに 1 つのことをさせたい」と言うときは、それを行うために必要な以上のことも、それ以下のこともすべきではありません。この基本的な考え方で、少なくとも手続き上のまとまりを維持できます。「このクラスに請求書のすべてのデータを保持させたい」は、通常、オブジェクトの責任に基づいて小計を合計したり、消費税を計算したりして、フィールドの正確で内部的に一貫した値を与える方法を知るビジネスロジックを可能にします。を含む。

個人的には、「軽量」ドメインに大きな問題はありません。「データ エキスパート」という 1 つの役割を持つだけで、ドメイン オブジェクトは、クラスに関連するすべてのフィールド/プロパティ、すべての計算フィールド ロジック、明示的/暗黙的なデータ型変換、および場合によってはより単純な検証規則のキーパーになります。 (つまり、必須フィールド、値の制限、許可された場合にインスタンスを内部的に破壊するもの)。おそらく加重平均またはローリング平均の計算アルゴリズムが変更される可能性がある場合は、アルゴリズムをカプセル化し、計算フィールドでそれを参照します (これはちょうど良い OCP/PV です)。

私は、そのようなドメイン オブジェクトが「貧血」であるとは考えていません。この用語に対する私の認識は、「データ バッグ」であり、外界の概念がまったくないフィールドの集まりであり、それらが含まれているということ以外のフィールド間の関係さえありません。私もそれを見てきましたが、オブジェクトが問題であることを知らなかったオブジェクトの状態の不一致を追跡するのは楽しいことではありません。過度に熱心な SRP は、データ オブジェクトはビジネス ロジックに責任を負わないと述べることでこれにつながりますが、一般的に常識が最初に介入し、オブジェクトはデータの専門家として、一貫した内部状態を維持する責任を負わなければならないと言います。

繰り返しますが、個人的な意見ですが、私は Active Record よりも Repository パターンを好みます。1 つの責任を持つ 1 つのオブジェクトであり、そのレイヤーより上のシステム内の他のオブジェクトは、それがどのように機能するかについて何も知る必要はありません。Active Record では、ドメイン層が永続化メソッドまたはフレームワークに関する特定の詳細 (各クラスの読み取り/書き込みに使用されるストアド プロシージャの名前、フレームワーク固有のオブジェクト参照、ORM 情報でフィールドを装飾する属性など) を知る必要があります。 )、したがって、デフォルトですべてのドメインクラスに変更する2番目の理由を挿入します.

私の0.02ドル。

于 2010-08-31T15:44:11.110 に答える
5

確かな原則に従うことで、実際に DDD の豊富なドメイン モデルから遠ざかったことがわかりました。もっと言えば、何らかのファサードについて話している場合を除いて、ドメイン モデルの論理概念とどの言語のクラスも 1 対 1 でマッピングされていないことがわかりました。

これは、構造体とモジュールを持つ C スタイルのプログラミングであるとは言えませんが、おそらくより機能的なものになるでしょう。スタイルは似ていますが、詳細は大きな違いを生むと思います。私のクラスインスタンスは、高階関数、部分関数適用、遅延評価関数、または上記のいくつかの組み合わせのように動作することがわかりました。私には言いようのないことですが、TDD + SOLID に従ってコードを書いていると、OO と関数型のハイブリッド スタイルのように動作するようになりました。

継承が悪い言葉であるということについては、Java/C# のような言語では継承が十分に細分化されていないという事実が原因だと思います。他の言語では、それほど問題ではなく、より便利です。

于 2009-09-14T20:38:21.627 に答える
1

SRP の次のような定義が気に入っています。

「クラスを変更するビジネス上の理由は 1 つだけです」

したがって、動作が単一の「ビジネス上の理由」にグループ化できる限り、それらが同じクラスに共存しない理由はありません。もちろん、「ビジネス上の理由」を定義するものは議論の余地があります (すべての利害関係者によって議論されるべきです)。

于 2009-10-29T20:12:47.827 に答える
1

暴言に入る前に、私の意見を一言で言うと、どこかですべてが一緒にならなければならない...そしてそこを川が流れている.

私はコーディングに悩まされています。

=======

貧血データモデルと私...まあ、私たちはたくさん仲良くしています。ビジネス ロジックがほとんど組み込まれていない小規模から中規模のアプリケーションの性質によるものかもしれません。多分私は少し遅れています。

ただし、ここに私の2セントがあります:

エンティティ内のコードを取り出して、インターフェイスに結び付けることができませんでしたか?

public class Object1
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }

    private IAction1 action1;

    public Object1(IAction1 action1)
    {
        this.action1 = action1;
    }

    public void DoAction1()
    {
        action1.Do(Property1);
    }
}

public interface IAction1
{
    void Do(string input1);
}

これは何らかの形で SRP の原則に違反していますか?

さらに、コードを消費する以外に互いに結び付けられていない多数のクラスを配置することは、実際には SRP のより大きな違反であり、レイヤーを押し上げているのではないでしょうか?

Object1 に関連する何かを行う方法を理解しようとしているクライアント コードを書いている人を想像してみてください。彼があなたのモデルを操作する必要がある場合、彼は Object1、データ バッグ、およびそれぞれが 1 つの責任を持つ一連の「サービス」を操作します。それらすべてが適切に相互作用することを確認するのは彼の仕事です。そのため、彼のコードはトランザクション スクリプトになり、そのスクリプト自体に、その特定のトランザクション (または作業単位) を適切に完了するために必要なすべての責任が含まれます。

さらに、「いや、彼がする必要があるのは、サービス層にアクセスすることだけです。それは Object1Service.DoActionX(Object1) のようなものです。簡単なことです」と言うことができます。では、ロジックはどこにあるのでしょうか。その1つの方法ですべてですか?あなたはまだコードをプッシュしているだけで、何があっても、データとロジックが分離されてしまいます。

したがって、このシナリオでは、特定の Object1Service をクライアント コードに公開し、DoActionX() を基本的にドメイン モデルの別のフックにしないでください。これにより、次のことを意味します。

public class Object1Service
{
    private Object1Repository repository;

    public  Object1Service(Object1Repository repository)
    {
        this.repository = repository;
    }

    // Tie in your Unit of Work Aspect'ing stuff or whatever if need be
    public void DoAction1(Object1DTO object1DTO)
    {
        Object1 object1 = repository.GetById(object1DTO.Id);
        object1.DoAction1();
        repository.Save(object1);
    }
}

Object1 から Action1 の実際のコードを取り出しましたが、すべての集中的な目的のために貧血でない Object1 を用意します。

アトミックにして独自のクラスに分けたい 2 つ (またはそれ以上) の異なる操作を表すために Action1 が必要だとします。アトミック操作ごとにインターフェイスを作成し、DoAction1 内に接続するだけです。

それが私がこの状況にアプローチする方法です。しかし、繰り返しになりますが、私は SRP が何であるかをよく知りません。

于 2012-03-14T23:06:30.510 に答える
0

すべてのドメイン オブジェクトに共通の基本クラスを使用して、プレーンなドメイン オブジェクトをActiveRecordパターンに変換します。基本クラスに共通の動作を配置し、必要に応じて派生クラスの動作をオーバーライドするか、必要に応じて新しい動作を定義します。

于 2010-06-03T08:34:42.977 に答える