web-dev-qa-db-ja.com

単一責任の原則-私はそれを使いすぎていませんか?

参考のために- http://en.wikipedia.org/wiki/Single_responsibility_principle

アプリケーションの1つのモジュールで元帳エントリの作成を担当するテストシナリオがあります。実行できる3つの基本的なタスクがあります-

  • 既存の元帳エントリを表形式で表示します。
  • 作成ボタンを使用して、新しい元帳エントリを作成します。
  • テーブル内の元帳エントリ(最初のポインタで説明)をクリックして、次のページで詳細を表示します。このページで元帳エントリを無効にすることができます。

(各ページにはさらにいくつかの操作/検証がありますが、簡潔にするために、これらに限定します)

そこで、3つの異なるクラスを作成することにしました-

  • LedgerLandingPage
  • CreateNewLedgerEntryPage
  • ViewLedgerEntryPage

これらのクラスはそれらのページで実行できるサービスを提供し、Seleniumテストはこれらのクラスを使用して、アプリケーションを特定のアサーションを作成できる状態にします。

同僚と一緒にレビューしていたとき、彼は圧倒されて、みんなのために単一のクラスを作るように頼みました。私のデザインはまだすっきりしていると感じていますが、単一責任の原則を使いすぎているかどうかは疑問です

13
Tarun

@YannisRizosの引用 ここ

これは本能的なアプローチであり、変更する可能性がより高いのは元帳を管理するビジネスロジックであるため、おそらく正しいでしょう。その場合、3つのクラスを維持するよりも1つのクラスを維持する方がはるかに簡単です。

少なくとも30年のプログラミング経験の後で、私は少なくとも今は同意しません。小さいクラスの私見は維持する方が良い ほとんどの場合、私は考えることができます このような場合。 1つのクラスに入れる関数が多いほど、構造が少なくなり、コードの品質が低下します-毎日少しずつ。 Tarunを正しく理解したとき、これら3つの操作のそれぞれ

LedgerLandingPage

CreateNewLedgerEntryPage

ViewLedgerEntryPage

はユースケースです。これらの各クラスは、関連するタスクを実行する複数の関数で構成されており、それぞれを個別に開発およびテストできます。そのため、それぞれのクラスを作成すると、一緒に属しているものが一緒にグループ化され、何かが変更される場合に変更されるコードの部分を特定するのがはるかに簡単になります。

これら3つのクラスに共通の機能がある場合、それらは共通の基本クラス、Ledgerクラス自体(少なくともCRUD操作用に1つあると想定)、または別のヘルパークラスに属しています。

多くの小さなクラスを作成するためにさらに引数が必要な場合は、 Robert Martinの本「Clean Code」 を確認することをお勧めします。

19
Doc Brown

単一責任原則やその他の原則の明確な実装はありません。何を変更する可能性が高いかに基づいて決定する必要があります。

@Karpieが 以前の回答 と書いているように:

必要なクラスは1つだけで、そのクラスの単一の責任は元帳の管理です。

これは本能的なアプローチであり、変更する可能性がより高いのは元帳を管理するビジネスロジックであるため、おそらく正しいでしょう。その場合、3つのクラスを維持するよりも1つのクラスを維持する方がはるかに簡単です。

しかし、それは検討され、ケースごとに決定されるべきです。変更の可能性が非常に小さいことを心配して、変更の可能性が高いものに原則を適用することに集中すべきではありません。元帳を管理するロジックがマルチレイヤープロセスであり、レイヤーが個別に変更される傾向がある場合、または原則として分離する必要がある懸念事項(ロジックとプレゼンテーション)である場合は、個別のクラスを使用する必要があります。それは確率のゲームです。

設計原則の使いすぎについての同様の質問です。興味深いと思います: 設計パターン:いつ使用するか、いつパターンを使用してすべてをやめるか

11
yannis

これらのクラスを調べてみましょう:

  • LedgerLandingPage:このクラスの役割は、元帳のリストを表示することです。アプリケーションが大きくなるにつれて、他のデータソースからのデータを並べ替え、フィルタリング、ハイライト表示し、透過的にFuseするメソッドを追加する必要があるでしょう。

  • ViewLedgerEntryPage:このページには、元帳が詳細に表示されます。かなり簡単そうです。データが単純である限り。ここで元帳を無効にすることができます。また、修正することもできます。または、コメントを付けるか、ファイル、レシート、外部予約番号などを添付してください。そして、編集を許可したら、必ず履歴を表示したいとします。

  • CreateLedgerEntryPage:ViewLedgerEntryPageに完全な編集機能がある場合、「空のエントリ」を編集することにより、このクラスの責任を果たすことができます。これは意味があるかもしれませんし、意味がないかもしれません。

これらの例は少しずさんに思われるかもしれませんが、要点は、UXからここで取り上げている機能です。単一の機能は、明らかに異なる単一の責任です。なぜなら、その機能の要件は変化する可能性があり、2つの異なる機能の要件は2つの反対方向に変化する可能性があるため、最初からSRPを使い続けていたはずです。
しかし、逆に、考えられるあらゆる理由で単一のインターフェースを持つ方が便利な場合は、ファサードを3つのクラスにスラップすることができます。


SRP overuseのようなものがあります。ファイルの内容を読み取るために12のクラスが必要な場合、それだけを行っていると想定する方が安全です。

SRPを反対側から見ると、実際には、単一の責任は単一のクラスで処理する必要があると述べています。ただし、責任は抽象化レベル内にのみ存在することに注意してください。したがって、より高い抽象化レベルのクラスが、より低いレベルのコンポーネントに作業を委任することによってその責任を実行することは完全に理にかなっており、これは 依存関係の逆転 によって最適に達成されます。

4
back2dos

これらのクラスに変更する理由は1つだけですか?

それが(まだ)わからない場合は、投機的な設計/エンジニアリングトラップを超えている可能性があります(クラスが多すぎる、複雑な設計パターンが適用されている)。あなたは満足していません [〜#〜] yagni [〜#〜] 。ソフトウェアのライフサイクルの初期段階では、一部の要件が明確でない場合があります。したがって、変更の方向は現在のところ見えません。それがわかった場合は、1つまたは最小限のクラスに保持してください。ただし、これには責任が伴います。要件と変更の方向が明確になり次第、現在の設計を再考し、変更の理由を満たすためにリファクタリングを行う必要があります。

変更の3つの異なる理由があり、それらがこれら3つのクラスにマッピングされることをすでに知っている場合は、適切な量のSRPを適用しただけです。繰り返しになりますが、これにはいくらかの責任が伴います。間違っている場合や将来の要件が予期せず変更された場合は、現在の設計を再考し、変更の理由を満たすためにリファクタリングを行う必要があります。

重要な点は次のとおりです。

  • ドライバーの変化に注目してください。
  • リファクタリング可能な柔軟な設計を選択しました。
  • コードをリファクタリングする準備をします。

この考え方を持っている場合は、投機的な設計や過剰なエンジニアリングを恐れることなく、コードを形作ることができます。

ただし、常に要因時間があります。多くの変更では、必要な時間がありません。リファクタリングには時間と労力がかかります。設計を変更しなければならないことはよく知っていましたが、時間の制約のために延期する必要がありました。これは発生し、回避できません。設計されたコードよりも設計されたコードの方がリファクタリングする方が簡単であることがわかりました。 [〜#〜] yagni [〜#〜] は、適切なガイドラインを提供します。疑わしい場合は、クラスの数とデザインパターンの適用を最小限に抑えてください。

2
Theo Lenndorff

クラスを分割する理由としてSRPを呼び出す理由は3つあります。

  • 要件の将来の変更により、一部のクラスが変更されないようにすることができます
  • バグをより迅速に特定できるようにするため
  • クラスを小さくする

クラスの分割を拒否する理由は3つあります。

  • 要件の将来の変更によって、複数のクラスで同じ変更が行われないようにするため
  • バグの発見に間接の長いパスの追跡が含まれないように
  • 関連する1つのクラスでのみ必要なときに情報やメソッドを全員に公開する、カプセル化を解除する手法(プロパティ、フレンドなど)に抵抗する

私があなたのケースを見て、変更を想像すると、それらのいくつかは複数のクラスに変更を引き起こします(たとえば、元帳にフィールドを追加し、3つすべてを変更する必要があります)が、多くはそうしません-たとえば、元帳のリストにソートを追加しますまたは、新しい元帳エントリを追加するときに検証ルールを変更します。同僚の反応は、バグを探す場所がわからないためと考えられます。元帳のリストに問題があるように見える場合、それは誤って追加されたためですか、それともリストに問題がありますか?概要と詳細の間に不一致がある場合、どちらが間違っていますか?

また、3つのクラスすべてを変更する必要があることを意味する要件の変更は、1つだけを変更する必要がない場合よりもはるかに可能性が高いと同僚が考える可能性もあります。それはとても役立つ会話になるかもしれません。

1
Kate Gregory

元帳がdbのテーブルである場合、これらのタスクを1つのクラス(DAOなど)に置くことをお勧めします。元帳にロジックが多く、dbのテーブルではない場合、これらのタスクを実行するためにさらにクラスを作成することをお勧めします(2つまたはthressクラスの場合があります)そして顧客のためだけに行うファサードクラスを提供します。

0
Mark xie

私見あなたはまったくクラスを使うべきではありません。ここにはデータの抽象化はありません。元帳は具体的なデータ型です。それらを操作および表示するためのメソッドは、関数とプロシージャです。日付のフォーマットなど、共有する一般的に使用される関数は確かにたくさんあります。表示および選択ルーチンのクラスでデータを抽象化および非表示にする場所はほとんどありません。

元帳の編集のためにクラスの状態を非表示にする場合があります。

SRPを悪用しているかどうかにかかわらず、より根本的な何かを悪用しています。つまり、抽象化を使いすぎています。これは典型的な問題であり、神話の概念OOは健全な開発パラダイムです。

プログラミングは90%具体的です。データ抽象化の使用はまれであり、注意深く設計されている必要があります。一方、機能の抽象化はより一般的です。言語の詳細とアルゴリズムの選択は、操作のセマンティクスから分離する必要があるためです。

0
Yttrill