25

SRPでは、「責任」は通常「変更する理由」として説明されるため、各クラス(またはオブジェクト?)には、誰かがそこに行って変更する必要がある理由が1つだけある必要があります。

しかし、これを非常にきめ細かくすると、2つの数値を足し合わせたオブジェクトは責任であり、変更する可能性のある理由であると言えます。したがって、オブジェクトには他のロジックが含まれていてはなりません。これは、変更の別の理由を生み出すためです。

少し客観的ではない単一責任の原則である「スコーピング」の戦略を持っている人がいるのではないかと思います。

4

6 に答える 6

30

それはあなたがモデル化しているものの文脈に帰着します。私はSOLIDの原則について広範囲にわたる執筆とプレゼンテーションを行いました。特に、単一責任についての私の議論であなたの質問に答えます。

以下は、CodeMagazineの2010年1月/2月号に最初に掲載されたもので、「SOLID Software Development、One StepataTime」からオンラインで入手できます。


単一責任の原則では、クラスには変更する理由が1つだけあるべきであるとされています。

これは、最初は直感に反しているように見えるかもしれません。クラスが存在する理由は1つだけであると言うのは簡単ではないでしょうか。実際、存在する理由は、善よりも害をもたらすような極端なものに簡単にとらえることはできません。それを極端に考えて、存在する理由が1つあるクラスを構築すると、クラスごとに1つのメソッドしか存在しなくなる可能性があります。これにより、最も単純なプロセスでもクラスが大量に発生し、システムの理解と変更が困難になります。

クラスに存在する理由が1つではなく、変更する理由が1つある必要がある理由は、システムを構築しているビジネスコンテキストです。2つの概念が論理的に異なる場合でも、それらが必要とされるビジネスコンテキストでは、それらが1つで同じになる必要がある場合があります。クラスをいつ変更するかを決定する重要なポイントは、概念の純粋に論理的な分離に基づくのではなく、概念に対するビジネスの認識に基づいています。ビジネスの認識とコンテキストが変更された場合、クラスを変更する理由があります。単一のクラスが持つべき責任を理解するには、まず、そのクラスによってカプセル化される概念と、その概念の実装の詳細が変更されると予想される場所を理解する必要があります。

たとえば、車のエンジンについて考えてみます。エンジンの内部動作は気になりますか?ピストン、カムシャフト、燃料インジェクターなどの特定のサイズがあることを気にしますか?それとも、車に乗ったときにエンジンが期待どおりに動作することだけを気にしますか?もちろん、答えは、エンジンを使用する必要があるコンテキストに完全に依存します。

あなたが自動車店で働いている整備士であるならば、あなたはおそらくエンジンの内部の働きを気にします。エンジンの特定のモデル、さまざまな部品サイズ、およびその他の仕様を知る必要があります。この情報がない場合は、エンジンを適切に整備できない可能性があります。ただし、A地点からB地点までの移動のみが必要な平均的な日常の人であれば、そのレベルの情報は必要ないでしょう。個々のピストン、スパークプラグ、プーリー、ベルトなどの概念は、ほとんど意味がありません。あなたが運転している車がエンジンを持っていて、それが正しく機能することだけを気にします。

エンジンの例は、単一責任原則の核心に直結します。車を運転することとエンジンを整備することの文脈は、単一の概念であるべきものとすべきでないものの2つの異なる概念を提供します-変化の理由。エンジンの整備の文脈では、すべての個々の部品は別々である必要があります。それらを単一のクラスとしてコーディングし、それらがすべて個別の仕様に準拠していることを確認する必要があります。しかし、車を運転するという文脈では、エンジンは単一の概念であり、これ以上分解する必要はありません。この場合、Engineと呼ばれる単一のクラスがある可能性があります。いずれの場合も、コンテキストによって、責任の適切な分離が何であるかが決まります。

于 2010-03-17T14:52:40.917 に答える
4

私は「変化の理由」よりも、ビジネス要件の「変化の速度」の観点から考える傾向があります。

問題は、実際に、変更できるかどうかではなく、一緒に変更される可能性がどの程度あるかということです。

違いは微妙ですが、私を助けます。レポートエンジンに関するウィキペディアの例を考えてみましょう。

  • レポートのコンテンツとテンプレートが同時に変更される可能性が高い場合、それらは明らかに関連しているため、1つのコンポーネントである可能性があります。(2つにすることもできます)

  • ただし、テンプレートなしでコンテンツが変更される可能性が重要な場合は、関連性がないため、2つのコンポーネントである必要があります。(持っていると危険です)

しかし、私はそれがSRPの個人的な解釈であることを知っています。

また、私が好きな2番目のテクニックは、「クラスを1つの文で説明する」です。通常、明確な責任があるかどうかを特定するのに役立ちます。

于 2010-03-16T16:44:10.387 に答える
4

私は、2つの数字を足し合わせるようなタスクを実行することを責任として見ていません。責任にはさまざまな形や大きさがありますが、それらは確かに、単一の機能を実行するよりも大きなものと見なされるべきです。

これをよりよく理解するには、クラスが担当するものとメソッドが実行するものを明確に区別することがおそらく役立ちます。メソッドは「1つのことだけを行う」必要があります(たとえば、ほとんどの目的では「+」はすでにそれを行うメソッドですが、2つの数値を追加します)が、クラスはそのコンシューマーに単一の明確な「責任」を提示する必要があります。責任は方法よりもはるかに高いレベルにあります。

リポジトリのようなクラスには、明確で特異な責任があります。保存や読み込みなどの複数の方法がありますが、Personエンティティに永続性のサポートを提供する明確な責任があります。クラスは、依存クラスの責任を調整および/または抽象化することもでき、これも他の消費クラスに対する単一の責任として提示します。

肝心なのは、SRPの適用が、そのメソッドの機能をクラスにラップすることだけを目的としている単一メソッドクラスにつながる場合、SRPは正しく適用されていないということです。

于 2010-03-16T17:07:10.217 に答える
3

私が使用する簡単な経験則は、責任のレベルまたは粒度が問題の「エンティティ」のレベルまたは粒度と一致する必要があるということです。明らかに、メソッドの目的は、クラス、サービス、またはコンポーネントの目的よりも常に正確です。

責任のレベルを評価するための良い戦略は、適切な比喩を使用することです。あなたがしていることを現実の世界に存在する何かに関連付けることができれば、それはあなたが解決しようとしている問題の別の見方を与えるのに役立ちます-適切なレベルの抽象化と責任を特定できることを含みます。

于 2010-03-16T20:29:21.347 に答える
3

@Derick bailey:すばらしい説明
いくつかの追加:
SRPの適用がコンテキストベースであることは完全に受け入れられます。
問題はまだ残っています:特定のクラスがSRPに違反しているかどうかを定義する客観的な方法はありますか?

一部の設計コンテキストは非常に明白ですが(Derickによる車の例のように)、それ以外の場合、クラスの動作を定義する必要があるコンテキストは、何度もあいまいなままです。

このような場合、ファジークラスの動作を分析して、その責任を異なるクラスに分割し、分割によって発生した新しい動作および構造の関係の影響を測定すると役立つ場合があります。

分割が完了するとすぐに、分割された責任を維持する理由、またはそれらを単一の責任にバックマージする理由がすぐに明らかになります。

私はこのアプローチを適用しましたが、これは私にとって良い結果をもたらしました。

しかし、「クラスの責任を定義する客観的な方法」を探す私の探求はまだ続いています。

于 2013-12-13T12:59:01.487 に答える
2

上記のクリス・ニコラが「クラスはその消費者に単一の明確な「責任」を提示する必要がある」と言ったとき、私は敬意を表して同意しません

SRPは、クラスの顧客ではなく、クラス内で優れたデザインを実現することを目的としていると思います。

私には、責任が何であるかはあまり明確ではありません。そして、この概念が生じる質問の数が証明されます。

「変更する単一の理由」

また

「説明に「and」という単語が含まれている場合は、分割する必要があります」

質問につながります:制限はどこにありますか?最後に、2つのパブリックメソッドを持つクラスには、変更する理由が2つありますね。

私にとって、真のSRPはFacadeパターンにつながります。このパターンでは、呼び出しを他のクラスに委任するだけのクラスがあります。

例えば:

class Modem
  send()
  receive()

Refactors to ==>

class ModemSender
class ModelReceiver

+

class Modem
  send() -> ModemSender.send()
  receive()  -> ModemReceiver.receive()

意見は大歓迎です

于 2011-05-30T11:32:47.263 に答える