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


63

今日、誰かと議論がありました。

私は貧血領域モデルとは対照的に、豊富な領域モデルを持つことの利点を説明していました。そして、次のような単純なクラスでポイントをデモしました。

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はソリッドではなく、関数型プログラミングが天国への唯一の方法です。

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

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

あなたは何を言ったでしょうか?

更新:この例はguntbertによってgeneしみなく更新され、メソッドがより現実的になり、基礎となる議論に集中できるようになりました。


19
このクラスはSRPに違反しています。データとロジックが混在しているためではなく、凝集度が低いためです-潜在的な神オブジェクト
-gnat

1
従業員に休日を追加するのはどのような目的ですか?おそらく、カレンダークラスまたは休日があるものがあるはずです。あなたの友達は正しいと思います。
ジェームスブラック

9
「私はXを信じている」と言う人の言うことを決して聞かないでください。
スティグヘマー

29
これがSRPに違反しているかどうかではなく、それが良いモデリングであるかどうかということではありません。私が従業員だとします。週末にスキーに行っても大丈夫かとマネージャーに尋ねると、マネージャーは休日を追加しません。ここのコードは、モデル化しようとしている現実と一致しないため、疑わしいものです。
エリックリッパー

2
関数型プログラミングが天国への唯一の方法であるために+1。
エリップ

回答:


68

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

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


オブジェクト指向プログラミングが作成され、実際のオブジェクトとクラス(アクション+属性)をモデル化することを目的としている場合、複数の責任(アクション)を持つコードクラスを作成してみませんか?実世界のオブジェクトには複数の責任があります。たとえば、ジャーナリストは新聞に社説を書き、テレビ番組で政治家にインタビューします。現実のオブジェクトに対する2つの責任!クラスジャーナリストを作成する場合はどうなりますか?
user1451111

41

単一の責任の原則は、1つのコード(OOPでは通常クラスについて話している)が1つの機能に対する責任を負うかどうかにのみ関係します。あなたの友人は、関数とデータが混同できないと言って、その考えを本当に理解していなかったと思います。場合Employeeも、彼の彼の車が行くどのくらいの速職場、及び食品の種類、彼の犬は食べるの情報が含まれていたし、我々は問題を抱えていると思います。

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

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


20

私の考えでは、このクラスは、Employeeとの両方を表す場合、SRPに違反する可能性がありEmployeeHolidaysます。

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


同意する。コードがここで提供されているように単純であれば、おそらくスライドさせます。しかし、私の考えでは、私は従業員が自分の休日を処理する責任を負わないはずです。責任の所在については大したことではないように思えるかもしれませんが、次のように考えてください。holiday-logicについては、個人的にEmployeeエンティティを最初から見ることはありません。
ニクラスH

1
@NiklasH同意しました。個人的に、私はランダムに見て、スタジオで単語「Holiday」を検索するクラスを試してみて、それがどのクラスに登場したかを見てみません。:)
ニコライダンテ

4
本当です。しかし、この新しいシステムでは「休日」と呼ばれず、「休暇」または「自由時間」であるとしたらどうでしょう。しかし、私は同意する、あなたは通常それを単に検索する能力を持っているか、同僚に尋ねることができます。私のコメントは、主にOPが責任を精神的にモデル化することであり、ロジックの最も明白な場所は次のとおりです:
Niklas H

最初の声明に同意します。ただし、ピアレビューの場合、SRPに違反するのは滑りやすい斜面であり、これは多くの壊れたウィンドウの最初の可能性があるため、そうするつもりはありません。乾杯。
ジムスピーカー

20

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

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

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

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

噛むべきことは次のとおりです。システムが従業員の給与を追跡する必要がある場合、従業員クラスは変更されますか?住所?緊急連絡先情報 それらの2つに「はい」(および「発生する可能性が高い」)と言った場合、クラスはまだコードがなくてもSRPに違反しています。SOLIDは、コードだけでなくプロセスとアーキテクチャにも関係しています。


9

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

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

実装は内部です。プライベート変数とロジックが必要になる場合があります。これは、クラスに複数の責任があることを意味しません。


興味深い考え方、共有してくれてありがとう
-tobiak777

ニース-OOPの目的を表現する良い方法。
user949300

5

ロジックとデータを何らかの形で混在させることは常に間違っているという考えは非常にばかげているので、議論に値することすらありません。ただし、この例では明確に単一の責任に違反していますが、それはプロパティDaysOfHolidaysと関数があるためではありませんAddHolidays(int)

従業員のアイデンティティが休日管理と混ざり合っているためです。これは悪いことです。従業員の身元は、休暇、給与、残業を追跡し、誰がどのチームに所属しているかを示し、パフォーマンスレポートにリンクするなどの複雑なものです。社員。従業員は、ASCIIスペルやユニコードスペルなど、自分の名前の複数のスペルを持っている場合もあります。人は0〜n個の名と姓を使用できます。管轄区域によって名前が異なる場合があります。従業員の身元を追跡することで十分な責任があり、休暇や休暇の管理を第2の責任と呼ばずに上に追加することはできません。


「従業員の身元を追跡するだけで十分な責任があり、休暇や休暇の管理を第2の責任と呼ばずに追加することはできません。」+従業員はいくつかの名前などを持っているかもしれません...モデルのポイントは、目前の問題について現実世界の関連する事実に集中することです。このモデルが最適な要件があります。これらの要件では、従業員は休日を変更できる範囲でのみ興味深いものであり、実際の生活の他の側面を管理することにあまり関心がありません。
tobiak777

@reddy「従業員は休日を変更できる範囲でのみ興味深い」-つまり、従業員を正しく識別する必要があるということです。従業員がいるとすぐに、結婚や離婚のためにいつでも姓を変更できます。また、名前と性別を変更することもできます。他の従業員の名前と一致するように姓が変更された場合、従業員を解雇しますか?この機能のすべてを今すぐ追加するわけではありません。代わりに、必要なときに追加します。これは良いことです。実装の程度に関係なく、識別の責任
ピーター

3

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

他の人のように、私はこれに反対します。

クラスで複数のロジックを実行している場合、SRPに違反していると言えます。単一のロジックを実現するためにクラス内に保存する必要のあるデータの量は無関係です。


番号!SRPは、複数のロジック、複数のデータ、または2つの組み合わせによって違反されません。唯一の要件は、オブジェクトがその目的に固執することです。その目的は、潜在的に多くの操作を伴う可能性があります。
マーティンマート

@MartinMaat:多くの操作、はい。結果として1つのロジック。私たちは同じことを言ってますが異なる用語でいると思う(と私はしばしばこのようなものを勉強しないと、あなたが正しいものであることを前提として幸せ)
モニカと明度レース

2

最近、単一の責任または単一の変更理由を構成するものと構成しないものについて議論することは有益ではありません。私は代わりに最小悲嘆の原則を提案します:

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

どのようだ?ロケット科学者にこれがなぜメンテナンスコストの削減に役立つのかを理解するために連れて行くべきではありません。トレードオフのバランスをとりながら検討する必要があります。

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

  1. 良好なテスト(信頼性の向上)。
  2. 特定のことを行うために必要な最低限のコードのみを含む(これには求心性結合の低減が含まれる場合があります)。
  3. コードを実行するだけでコードをバカにするだけです(「バダスの原理を作る」を参照)。

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

Badass Principleの作成:多くの場所で使用されているクラスは素晴らしいはずです。品質などに関係する場合、信頼性が高く効率的でなければなりません。

また、Bassassの原則は、彼らが何をするのかを口にするものよりも変更が必要になる可能性が低いため、Bassassの原則は最小悲嘆の原則と結びついています。

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

SRPの観点からは、ほとんど何も行わないクラスには、変更する理由が1つ(ゼロの場合もあります)しかありません。

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

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

私がそれがバダスの原則を作ることの露骨な違反であることを示唆することを除いて。また、まったく価値がありません。ほんの少ししか役に立たないことは、悪いことを期待することはできません。情報が少なすぎます(TLI)。そして当然、TLIであるものがあると、カプセル化された情報でなくても、本当に意味のあることは何もできません。したがって、他の誰かが実際に意味のある悪いことをすることを期待して、それを外部に漏らさなければなりません。そして、その漏れはデータを集約することだけを目的としており、それ以上のことは何もありませんが、そのしきい値は「データ」と「オブジェクト」の違いです。

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

そのため、設計するオブジェクトの粒度または粗さに関して、バランスをとる行為があります。私は、あなたがどれだけ多くの情報を外の世界に漏らさなければならないか、そしてそれがどれだけ意味のある機能を実行できるかによって、しばしば判断します。私はしばしば、Bassass Principleを作成し、Minimum Grief Principleと組み合わせながらバランスを見つけるのに役立ちます。


1

それどころか、私にとっては、AnemicドメインモデルはOOPの主要な概念(属性と動作を結び付ける)を壊しますが、アーキテクチャの選択に基づいて避けられない場合があります。貧血領域は考えやすく、有機的でなく、連続的です。

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

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

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

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

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

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


ご意見をお寄せいただきありがとうございます。重要なのは、リッチドメインがある場合、サービスレイヤーは必要ないということです。動作を担当するクラスは1つだけであり、それはドメインエンティティです。他のレイヤー(Webレイヤー、UIなど)は通常、DTOとViewModelを扱いますが、これで問題ありません。ドメインモデルは、UIの機能を実行したり、インターネット経由でメッセージを送信したりするのではなく、ドメインをモデル化することです。あなたのメッセージは、この一般的な誤解を反映しています。これは、人々が単にOOPを設計に組み込む方法を知らないという事実から来ています。そして、私はこれが非常に悲しいと思う-彼らにとって。
tobiak777

リッチドメインOOPについて誤解しているとは思いません。そのように多くのソフトウェアを設計しましたが、メンテナンス/進化に本当に適しているのは事実です。しかし、これは特効薬ではないことを伝えて申し訳ありません。
ビンス

私はそうではありません。コンパイラを書くためにはおそらくそうではありませんが、ほとんどの基幹業務/ SaaSアプリケーションでは、あなたが提案するものよりもはるかに芸術/科学的ではないと思います。ドメインモデルの必要性は数学的に証明できます。あなたの例では、OOPの制限の欠陥ではなく、議論の余地のある設計を考えるようになっています
-tobiak777

0

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

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


0

単一の責任原則に違反することはできません。なぜなら、それは単なる美的基準であり、自然のルールではないからです。科学的に聞こえる名前と大文字に惑わされないでください。


1
これはほとんど本当の答えではありませんが、質問へのコメントとしては素晴らしいでしょう。
ジェイエルストン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.