web-dev-qa-db-ja.com

このクラスの設計は単一責任の原則に違反していますか?

今日、私は誰かと議論しました。

貧血ドメインモデルではなく、リッチドメインモデルを使用する利点を説明しました。そして、私はそのような単純なクラスで私のポイントをデモしました:

public class Employee
{
    public Employee(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastname;
    }

    public string FirstName { get private set; }
    public string LastName { get; private set;}
    public int CountPaidDaysOffGranted { get; private set;}

    public void AddPaidDaysOffGranted(int numberOfdays)
    {
        // Do stuff
    }
}

彼が貧血モデルのアプローチを擁護したとき、彼の議論の1つは次のとおりです。「私は [〜#〜] solid [〜#〜] を信じています。あなたは 単一の責任の原則 (SRP)あなたは同じクラスでデータを表現し、ロジックを実行しているからです。」

この主張に従って、この主張は本当に意外であることがわかりました。1つのプロパティと1つのメソッドを持つクラスはSRPに違反しているため、OOPは一般にSOLIDではなく、 関数型プログラミング は天国への唯一の道です。

私は彼の多くの議論に答えないことに決めました、しかし私はコミュニティがこの質問についてどう思うか興味があります。

私が返信した場合、私は上記のパラドックスを指摘することから始めて、次にSRPが考慮したい粒度のレベルに大きく依存していることを示し、十分にそれをとると、複数のクラスを含むクラスプロパティまたは1つのメソッドが違反しています。

何と言いましたか?

Update:この例は、メソッドをより現実的にし、根底にある議論に集中できるように、guntbertによって寛大に更新されています。

63
tobiak777

単一の責任は、システム内の論理タスクの抽象化として理解されるべきです。クラスは、1つの特定のタスクを実行する(必要なすべてのことを実行する)単一の責任を持つ必要があります。これは実際、責任が何であるかに応じて、適切に設計されたクラスに多くをもたらすことができます。たとえば、スクリプトエンジンを実行するクラスには、スクリプトの処理に関連する多くのメソッドとデータを含めることができます。

あなたの同僚は間違ったことに集中しています。問題は、「このクラスにはどのメンバーがいるのか」ではありません。しかし、「このクラスはプログラム内でどのような便利な操作を実行しますか?」それが理解されれば、ドメインモデルは問題なく見えます。

68
Mason Wheeler

単一の責任の原則は、コードの一部(OOPでは通常、クラスについて話している)が機能の一部に対して責任を持っているかどうかにのみ関係しています)。機能とデータを混在させることができないと言っているあなたの友人は、その考えを本当に理解していなかったと思います。 Employeeに彼の職場、彼の車の移動速度、および彼の犬がどのような種類の食べ物を食べるかについての情報も含めるとしたら、問題が発生します。

このクラスはEmployeeのみを扱うため、露骨にSRPに違反していないと言っても差し支えないと思いますが、人々は常に自分の意見を持っています。

私たちが改善する可能性がある1つの場所は、休暇から従業員情報(名前、電話番号、電子メールなど)を分離することです。私の考えでは、これはメソッドとデータが混在できないことを意味するものではありません、それは単に休暇機能が別の場所にある可能性があることを意味します。

41
Frank Bryce

SRPは論理機能に関する抽象的な概念であることを指摘する素晴らしい回答はすでにありますが、追加する価値があると思う微妙な点があります。

SOLIDの最初の2文字、SRPとOCPはどちらも、要件の変化に応じてコードがどのように変化するかを示しています。 SRPの私のお気に入りの定義は、次のとおりです。「モジュール/クラス/関数には、変更する理由が1つだけあるべきです。」コードが変更される可能性のある理由について議論することは、コードがSOLIDであるかどうかについて議論することよりもはるかに生産的です。

従業員クラスを変更しなければならない理由はいくつありますか?あなたがそれを使用しているコンテキストがわからないので、私にはわかりません。また、未来も見えません。私ができることは、私が過去に見たものに基づいて考えられる変化をブレインストーミングすることであり、あなたはそれらがどのくらいの可能性であるかを主観的に評価することができます。 「合理的に可能性がある」と「私のコードがその正確な理由ですでに変更されている」の間に複数のスコアがある場合、その種類の変更に対してSRPに違反しています。ここに1つあります。3つ以上の名前を持つ誰かがあなたの会社に参加します(またはアーキテクトが この優れたW3C記事 を読みます)。もう1つあります。あなたの会社は休日の割り当て方法を変更しています。

AddHolidaysメソッドを削除しても、これらの理由は同様に有効であることに注意してください。多くの貧血ドメインモデルがSRPに違反しています。それらの多くは単なるコード内のデータベーステーブルであり、データベーステーブルが変更する理由が20以上あることは非常に一般的です。

システムが従業員の給与を追跡する必要がある場合、従業員クラスは変更されますか?アドレス?緊急連絡先?それらのうちの2つに「はい」(そして「起こりそう」)と言った場合、クラスにまだコードがなくても、クラスはSRPに違反しています。 SOLIDはプロセスとアーキテクチャに関するものであり、コードに関するものです。

20
Carl Leth

私の考えでは、このクラスがEmployeeEmployeeHolidaysの両方を表し続けると、SRPに違反する可能性があります。

現状、そしてピアレビューのために私に来たなら、私はおそらくそれを通過させたでしょう。従業員固有のプロパティとメソッドが追加され、休日固有のプロパティが追加された場合、SRPとISPの両方を引用して、分割することをお勧めします。

19
NikolaiDante

クラスがデータを表すことはクラスの責任ではなく、プライベートな実装の詳細です。

クラスには、従業員を表す1つの責任があります。これは、従業員を扱うために必要な機能を提供するいくつかのパブリックAPIを提供することを意味します(AddHolidaysが良い例であるかどうかは議論の余地があります)。

実装は内部です。たまたま、これにはプライベート変数とロジックが必要です。これは、クラスが複数の責任を持つようになったという意味ではありません。

9
RemcoGerlich

論理とデータを何らかの方法で混合することは常に間違っているという考えは、ばかげているので、議論する価値すらありません。ただし、この例では確かに単一の責任に対する明確な違反がありますが、プロパティDaysOfHolidaysと関数AddHolidays(int)があるためではありません。

それは、従業員のアイデンティティが休日の管理と混在しているからです。従業員のIDは、休暇、給与、残業の追跡、誰がどのチームに所属しているか、パフォーマンスレポートへのリンクなどを行うために必要な複雑なものです。従業員は、姓、名、またはその両方を変更して、同じままにすることもできます。従業員。従業員は、ASCIIやユニコードスペルなど)の名前の複数のスペルを使用することもできます。人々は、0からnまでのファーストネームまたはラストネームを持つことができます。異なる管轄区域では、異なる名前を持つ場合があります。- 従業員のIDを追跡することは、休日または休暇の管理を2番目の責任と呼ばずに上に追加することができないという十分な責任です。

5
Peter

「私はSOLIDを信じています。同じクラスでデータを表現し、ロジックを実行しているため、単一責任原則(SRP)に違反しています。」

他の人のように、私はこれに同意しません。

クラスで1つ以上のロジックを実行している場合、SRPに違反していると言えます。単一のロジックを実現するためにクラス内に格納する必要のあるデータの量は関係ありません。

何をするか、何をしないかを議論することは、今日、単一の責任または変更する単一の理由を構成するのに役に立たないと思います。その代わりに、最低の悲嘆の原則を提案します。

最小の悲しみの原則:コードは、変更が必要になる可能性を最小限に抑えるか、変更の容易さを最大化する必要があります。

どのようだ?なぜこれがメンテナンスコストの削減に役立つのかをロケット科学者に理解させるべきではありません。うまくいけば、それは無限の議論のポイントになるべきではありませんが、SOLIDと同様に、一般的にはどこにでも盲目的に適用することは、トレードオフのバランスを取る際に考慮すべきことです。

変更が必要になる可能性については、次のようになります。

  1. 優れたテスト(信頼性の向上)。
  2. 特定のことを実行するために必要な最小限のコードのみを使用します(これには求心性結合を減らすことが含まれます)。
  3. 何をするかでコードを悪用するだけです(悪意の原則の作成を参照)。

変更を加えることの難しさに関しては、それは遠心性カップリングと一緒になります。テストでは遠心性カップリングが導入されますが、信頼性が向上します。うまくできていれば、それは一般的に害よりも効果があり、完全に受け入れられ、最小悲嘆原則によって促進されます。

Badass Principleを作成します。多くの場所で使用されるクラスはすばらしいはずです。品質などに関連している場合、信頼性と効率性が高くなければならない.

そして、悪意のあるものは変更を必要とする可能性が彼らが何をするものよりも低いので、Made Badass PrincipleはMinimum Grief Principleに関連付けられています。

私は上記のパラドックスを指摘することから始めて、次に、SRPが考慮したい粒度のレベルに大きく依存していることを示し、十分にそれをとると、複数のプロパティまたは1つのメソッドを含むクラスが違反することを示します。それ。

SRPの観点からは、ほとんど何もしないクラスは、変更する理由が1つ(場合によっては0)しかないはずです。

class Float
{
public:
    explicit Float(float val);
    float get() const;
    void set(float new_val);
};

これには実質的に変更する理由はありません! SRPよりも優れています。 ZRPです!

それがMake Badass Principleの露骨な違反であることを私が示唆する場合を除いて。また、まったく価値がありません。ほとんど何もしないものは、ワルであると期待することはできません。情報(TLI)が少なすぎます。そして当然のことながら、TLIである何かを持っている場合、それがカプセル化した情報でさえも、それは本当に意味のあることは何もできません。そして、そのリーク性は、データを集約することだけを目的としているものには問題ありませんが、そのしきい値は、「データ」と「オブジェクト」の違いです。

もちろん、TMIというのも悪いです。ソフトウェア全体を1つのクラスに入れる場合があります。 runメソッドを1つだけ持つこともできます。そして、今や変更する非常に広い理由が1つあると主張する人もいるかもしれません。「このクラスは、ソフトウェアの改善が必要な場合にのみ変更する必要があります。」私はばかげていますが、もちろんそれに関するメンテナンスの問題はすべて想像できます。

したがって、設計するオブジェクトの細かさや粗さのバランスを取る必要があります。私はよく、外部に漏らさなければならない情報の量と、それが実行できる有意義な機能の量によって判断します。私はよく「バッドの原則を作る」が最小の悲嘆の原則と組み合わせながらバランスを見つけるのに役立ちます。

2
user204677

逆に、私にとって貧血ドメインモデルはいくつかのOOP主な概念(属性と動作を結び付ける))を破りますが、アーキテクチャの選択に基づいて必然的になる場合があります。貧血ドメインは考えるのが簡単で、有機的ではありませんそしてより順次。

多くのシステムは、複数のレイヤーが同じデータ(サービスレイヤー、Webレイヤー、クライアントレイヤー、エージェント...)で再生する必要がある場合に、これを行う傾向があります。

1つの場所でデータ構造を定義し、他のサービスクラスで動作を定義する方が簡単です。同じクラスが複数のレイヤーで使用された場合、これは大きくなる可能性があり、どのレイヤーが必要な動作を定義する責任があるか、誰がメソッドを呼び出すことができるかを尋ねます。

たとえば、すべての従業員の統計を計算するエージェントプロセスが有給日数の計算を呼び出すことができるよりも、良いアイデアではない場合があります。そして、従業員リストGUIは、この統計エージェントで使用される新しい集約ID計算(およびそれに付随する技術データ)をまったく必要としません。この方法でメソッドを分離すると、通常はデータ構造のみのクラスで終わります。

「オブジェクト」データ、またはそれらの一部、または別の形式(json)へのシリアライズ/デシリアライズは、オブジェクトの概念や責任を気にすることなく、簡単に行うことができます。ただし、データを渡すだけです。 2つのクラス(Employee、EmployeeVO、EmployeeStatistic…)間のデータマッピングはいつでも実行できますが、ここでEmployeeは実際には何を意味するのでしょうか。

つまり、ドメインクラスのデータとサービスクラスのデータ処理を完全に分離しますが、ここでは必要です。このようなシステムは、ビジネスの価値をもたらす機能的なシステムであり、適切な責任範囲を維持しながら必要な場所にデータを伝播する技術的なシステムでもあります(分散オブジェクトもこれを解決しません)。

動作スコープを分離する必要がない場合は、オブジェクトの見え方に応じて、サービスクラスまたはドメインクラスにメソッドを自由に配置できます。私はオブジェクトを「実際の」概念と見なす傾向があり、これは当然SRPを維持するのに役立ちます。したがって、あなたの例では、従業員の上司がPayDayAccountに付与する給与日を追加するよりも現実的です。従業員は会社によって雇われており、病気であるか、アドバイスを求められる可能性があり、彼はPaydayアカウントを持っています(ボスは彼またはPayDayAccountレジストリから直接それを取得する可能性があります...)しかし、集約されたショートカットを作成できます単純なソフトウェアであまり複雑にしたくない場合は、ここで簡単にしてください。

1
Vince

同じクラスでデータを表し、ロジックを実行しているため、単一責任原則(SRP)に違反しています。

それは私には非常に合理的に聞こえます。アクションを公開する場合、モデルにはパブリックプロパティがない場合があります。基本的には、コマンドとクエリの分離のアイデアです。コマンドは確かにプライベート状態になることに注意してください。

0
Dmitry Nogin

単一の責任の原則に違反することはできません。科学的に聞こえる名前と大文字に惑わされないでください。

0
ZunTzu