web-dev-qa-db-ja.com

単一責任原則を使用する場合、「責任」とは何ですか?

「単一の責任原則」が「1つのことだけを行う」という意味ではないことは明らかです。それがメソッドの目的です。

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

ボブ・マーティンは、「クラスには変更する理由が1つだけあるべきだ」と述べています。しかし、SOLIDを初めて使用するプログラマーであれば、これを理解するのは困難です。

私は 別の質問への回答 を書きました。責任は役職のようなものであると提案し、レストランのメタファーを使用して私の主題を説明することで主題の周りを踊りました。しかし、それでも、誰かがクラスの責任を定義するために使用できる一連の原則を明確に示していません。

それで、どうやってそれを行うのですか?各クラスに必要な責任をどのように決定し、SRPのコンテキストで責任をどのように定義しますか?

201
Robert Harvey

これに頭を悩ます1つの方法は、将来のプロジェクトでの潜在的な要件の変更を想像し、それを実現するために何が必要かを自問することです。

例えば:

新しいビジネス要件:カリフォルニアにいるユーザーには特別割引が適用されます。

「良い」変更の例:割引を計算するクラスのコードを変更する必要があります。

不適切な変更の例:Userクラスのコードを変更する必要があります。その変更は、割引とは関係のないクラスなど、Userクラスを使用する他のクラスにカスケード効果をもたらします。登録、列挙、および管理。

または:

新しい非機能要件:SQL Serverの代わりにOracleの使用を開始します

適切な変更の例:DTOでデータを永続化する方法を決定するデータアクセス層の単一のクラスを変更する必要があるだけです。

悪い変更:SQL Server固有のロジックが含まれているため、すべてのビジネスレイヤークラスを変更する必要があります。

アイデアは、将来の潜在的な変更のフットプリントを最小限に抑え、コードの変更を変更領域ごとに1つのコード領域に制限することです。

少なくとも、クラスは論理的な問題と物理的な問題を分離する必要があります。 System.IO名前空間には、一連の優れた例があります。そこには、さまざまな種類の物理ストリーム(例:FileStreamMemoryStream、またはNetworkStream)があります。 )および論理レベルで機能するさまざまなリーダーおよびライター(BinaryWriterTextWriter)。これらをこのように分離することで、組み合わせ爆発を回避します。FileStreamTextWriterFileStreamBinaryWriterNetworkStreamTextWriterNetworkStreamBinaryWriterMemoryStreamTextWriter、およびMemoryStreamBinaryWriter、ライターとストリームを接続するだけで、必要なものを手に入れることができます。その後、メモリ、ファイル、およびネットワーク用に個別に再実装する必要なく、たとえばXmlWriterを追加できます。

116
John Wu

実際には、責任は可能性が高いであるものによって制限されます。したがって、科学的または公式な方法はありません残念ながら責任を構成するもので。それは判断の呼びかけです。

あなたの経験が何を変更する可能性が高いかについてです。

私たちは、原理の言語を、双曲的で文字通りの熱烈な怒りで適用する傾向があります。クラスを分割する傾向があるのは、それらがcould変化するため、または単に問題の分解に役立つ線に沿っているためです。 (後者の理由は本質的に悪くはありません。)しかし、SRPはそれ自体のためには存在しません。 maintainable software。の作成に役立っています

繰り返しになりますが、部門がlikelyの変更によって駆動されない場合、それらはSRPにサービスを提供しているtrulyではありません。1 YAGNIがより適切な場合。 両方同じ最終的な目的を果たします。そして両方は判断の問題です-うまくいけば季節付け判断。

ボブおじさんがこれについて書いているとき、彼は私たちが「誰が変更を求めているのか」という点で「責任」を考えることを提案します。つまり、パーティーBが変更を要求したため、パーティーAが職を失うことを望まないのです。

ソフトウェアモジュールを作成する場合、変更が要求されたときに、それらの変更が1人から、または狭く定義された1つのビジネス機能を表す1つの密結合グループからのみ発生できることを確認する必要があります。組織全体の複雑さからモジュールを分離し、各モジュールがその1つのビジネス機能のみのニーズに対応(対応)するようにシステムを設計したいとします。 ( ボブおじさん-単一責任の原則

優れた経験豊富な開発者は、どのような変更がありそうかを理解します。そして、そのメンタルリストは、業界や組織によって多少異なります。

特定の組織での特定のアプリケーションでの責任を構成するのは、最終的にはseasoned判断の問題です。何が変わるかについてです。そして、ある意味では、それはwhoの所有者モジュールの内部ロジックについてです。


1.明確にするために、それはそれらがbad除算であることを意味するものではありません。それらは、コードの可読性を劇的に向上させるgreat分割である可能性があります。それは、それらがSRPによって駆動されていないことを意味します。

76
svidgen

私は「クラスは変更する理由が1つだけあるべきです」に従います。

私にとって、これは私の製品所有者が思いつく可能性のある明確なスキームを考えることを意味します(「モバイルをサポートする必要がある!」、「クラウドに行く必要がある!」、「中国語をサポートする必要がある!」)。優れた設計は、これらのスキームの影響をより小さな領域に制限し、それらを比較的簡単に達成できるようにします。悪い設計とは、多くのコードに行き、危険な変更をたくさん行うことを意味します。

私が見つけたのは、クレイジーなスキームの可能性を適切に評価できる唯一の経験です。1つを簡単にすると、他の2つを難しくする可能性があるためです。そして、デザインの良さを評価します。経験豊富なプログラマは、コードを変更するために何をする必要があるか、お尻に噛み付くために何が横たわっているのか、どんなトリックが物事を簡単にするかを想像できます。経験豊富なプログラマーは、製品の所有者がクレイジーなものを要求したとき、自分がどれだけねじ込まれているかについて良い直感を持っています。

実際には、単体テストはここで役立ちます。コードに柔軟性がない場合、テストが難しくなります。モックやその他のテストデータを挿入できない場合は、そのSupportChineseコードを挿入できない可能性があります。

別の大まかな指標は、エレベーターのピッチです。従来のエレベーターピッチは、「投資家と一緒にエレベーターにいた場合、アイデアで彼を売ることができますか?」です。スタートアップは、彼らが何をしているか、彼らの焦点は何であるかについての簡単で短い説明が必要です。同様に、クラス(および関数)には、それらが何であるかを簡単に説明する必要がありますdo 「このクラスは、これらの特定のシナリオで使用できるように、いくつかのfubarを実装しています」ではありません。別の開発者に伝えることができるもの:「このクラスはユーザーを作成します」。他の開発者にそれを伝えることができない場合は、バグを取得するためにgoingです。

29
Telastyn

誰も知らない。または、少なくとも、1つの定義に同意することはできません。それがSPR(および他のSOLID原則))をかなり物議を醸すものにしています。

責任があるかどうかを理解できることは、ソフトウェア開発者がキャリアの過程で学ばなければならないスキルの1つであると私は主張します。記述およびレビューするコードが多いほど、何かが単一の責任か複数の責任かを判断するために必要な経験が多くなります。または、単一の責任がコードの別々の部分に分かれている場合。

SRPの主な目的は難しいルールではないと主張します。それは、コード内の凝集に注意し、どのコードが凝集性であり、何が凝集性でないかを判断することに常に意識的な努力を払うことを思い出させることです。

26
Euphoric

「責任」という言葉は比喩として有用だと思います。これは、ソフトウェアを使用して、ソフトウェアがどの程度うまく構成されているかを調査できるからです。特に、2つの原則に焦点を当てます。

  • 責任は権限に見合ったものです。
  • 2つのエンティティが同じことに責任を持つべきではありません。

これらの2つの原則は、相互に作用し合うため、責任を有意義に果たすことができます。もしあなたが何かのために何かをするためにコードの一部に権限を与えているなら、それはそれが何をするかについて責任を持つ必要があります。これは、クラスを拡大しなければならない可能性があるという責任を引き起こし、「変更する1つの理由」をますますより広いスコープに拡大します。しかし、物事をより広くすると、当然、複数のエンティティが同じことを担当する状況に遭遇し始めます。これには実際の責任の問題が山積しているため、確かにコーディングの問題でもあります。その結果、責任が重複しない区画に細分されるため、この原則によりスコープが狭くなります。

これら2つに加えて、3番目の原則は妥当なようです。

  • 責任を委任することができます

作りたてのプログラムを検討してください...白紙の状態。最初は、全体としてプログラムであるエンティティが1つだけあります。それは...すべてに責任があります。当然、ある時点で、関数またはクラスに責任を委任し始めます。この時点で、最初の2つのルールが有効になり、その責任のバランスをとるようになります。マネージャーがチームの生産性を担当するのと同じように、トップレベルのプログラムは依然として全体の出力を担当しますが、各サブエンティティには責任が委任されており、その責任を実行する権限が委任されています。

追加のボーナスとして、これによりSOLIDは特に、企業のソフトウェア開発と互換性が必要になる可能性があります。地球上のすべての企業には、責任を委任する方法の概念があります。同意します。自社の委任を連想させる方法でソフトウェア内の責任を委任する場合、将来の開発者にとって、この会社での作業方法を理解するのがはるかに容易になります。

5
Cort Ammon

この会議 でイエールで、 ncle Bob はこれにfunnyの例を示します:

Enter image description here

彼は、Employeeには変更する3つの理由、変更要件の3つのソースがあると述べており、これを滑稽で舌のようなもの、しかし、それでも実例となる説明:

  • CalcPay()メソッドにエラーがあり、会社に数百万ドルの費用がかかる場合、CFOがあなたを解雇します

  • ReportHours()メソッドにエラーがあり、会社に数百万ドルの費用がかかる場合、COOがあなたを解雇します

  • WriteEmmployee()メソッドには、大量のデータの消去を引き起こし、会社に数百万米ドルの費用がかかるエラーがありますCTOがあなたを解雇します

したがって、3つの異なるCレベルのexecがあり、同じクラスでコストのかかるエラーが発生する可能性がある場合は、クラスの責任が多すぎることを意味します。

彼はSRPの違反を解決するこのソリューションを提供しますが、ビデオに示されていないDIPの違反を解決する必要があります。

Enter image description here

5

「単一責任の原則」は、おそらく紛らわしい名前です。 「変更する理由は1つだけ」の方が原則をよりよく説明できますが、それでも誤解しやすいものです。実行時にオブジェクトの状態が変化する原因については触れていません。開発者が将来コードを変更しなければならない原因について考えています。

バグを修正しない限り、変更は新規または変更されたビジネス要件によるものです。あなたはコード自体の外で考える必要があり、外部の要因が要件を変更する原因となる可能性があることを想像してください独立して。いう:

  • 政治的決定により、税率は変化します。
  • マーケティングは、すべての製品の名前を変更することを決定します
  • UIをアクセス可能にするには再設計する必要があります
  • データベースが混雑しているため、最適化を行う必要があります
  • モバイルアプリに対応する必要があります
  • 等々...

理想的には、独立した要因が異なるクラスに影響を与えることを望みます。例えば。税率は製品名とは関係なく変更されるため、変更が同じクラスに影響することはありません。そうしないと、税制変更の導入で製品の命名にエラーが発生するリスクが生じます。これは、モジュラーシステムで回避したい密結合の一種です。

したがって、何が変わる可能性があるかだけに焦点を当てるのではなく、anything将来的に変更される可能性があります。何が独立して変化するかに焦点を当てます。異なるアクターによって引き起こされた変更は、通常、独立しています。

役職の例は正しい方向に進んでいますが、文字通りもっと理解する必要があります!マーケティングによってコードが変更され、財務によって他の変更が発生する可能性がある場合、これらの変更は同じコードには影響しません。これらは文字どおり異なる役職であり、変更は独立して行われるためです。

ボブおじさんを引用するには 用語を発明した人:

ソフトウェアモジュールを作成する場合、変更が要求されたときに、それらの変更が1人から、または狭く定義された1つのビジネス機能を表す1つの密結合グループからのみ発生できることを確認する必要があります。組織全体の複雑さからモジュールを分離し、各モジュールがその1つのビジネス機能のみのニーズに対応(対応)するようにシステムを設計したいとします。

つまり、「責任」は単一のビジネス機能に対応することです。複数のアクターがクラスを変更しなければならない場合、クラスはおそらくこの原則を破ります。

3
JacquesB

「変更する理由」よりも細分化するより良い方法は、2つ(またはそれ以上)のアクションを実行する必要のあるコードに、個別のオブジェクト参照を保持する必要があることを要求することが理にかなっているかどうかという観点から考えることから始めることです各アクションについて、および1つのアクションを実行できるが他のアクションは実行できないパブリックオブジェクトがあると便利かどうか。

両方の質問に対する答えが「はい」の場合、そのアクションは別のクラスで実行する必要があることを示唆しています。両方の質問に対する答えが「いいえ」である場合、それは公共の観点からは1つのクラスがあるべきであることを示唆しています。そのためのコードが扱いにくい場合、内部的にプライベートクラスに細分される可能性があります。最初の質問に対する答えが「いいえ」で、2番目が「はい」の場合、アクションごとに個別のクラスが必要ですplus他のインスタンスへの参照を含む複合クラス。

レジのキーパッド、ブザー、数値読み取り、レシートプリンター、キャッシュドロワーの個別のクラスがあり、完全なレジの複合クラスがない場合、トランザクションを処理するはずのコードが誤って呼び出され、 1台のマシンのキーパッドから入力を受け取り、2台目のマシンのブザーからノイズを生成し、3台目のマシンのディスプレイに数字を表示し、4台目のマシンのプリンターにレシートを印刷し、5台目のマシンのキャッシュドロワーをポップする方法。これらのサブ関数はそれぞれ個別のクラスで処理すると便利ですが、それらを結合する複合クラスも必要です。複合クラスは可能な限り多くのロジックを構成要素クラスに委譲する必要がありますが、実際には、構成要素に直接アクセスするクライアントコードを要求するのではなく、構成要素の機能をラップする必要があります。

各クラスの「責任」は、実際のロジックを組み込むか、そうする他の複数のクラスに共通のアタッチメントポイントを提供することですが、重要なことは、何よりもまずクライアントコードがクラスをどのように表示するかに焦点を当てることです。クライアントコードが何かを単一のオブジェクトとして見ることが理にかなっている場合、クライアントコードはそれを単一のオブジェクトとして見る必要があります。

3
supercat

SRPを正しく理解するのは困難です。ほとんどの場合、コードに「ジョブ」を割り当て、各部分に明確な責任があることを確認することです。現実と同様に、作業を人の間で分割することは非常に自然な場合もありますが、特にその作業(または作業)を知らない場合は、非常に難しい場合があります。

私は常に最初に機能する単純なコードを記述するを推奨し、次に少しリファクタリングします。しばらくすると、コードが自然にクラスター化する方法を確認する傾向があります。コード(または人)と実行する作業を知る前に責任を強制するのは間違いだと思います。

あなたが気づく一つのことは、モジュールがあまりにも多くを開始し、デバッグ/メンテナンスが難しい場合です。これがリファクタリングの瞬間です。コアジョブは何である必要があり、別のモジュールにどのようなタスクを割り当てることができますか?たとえば、セキュリティチェックやその他の作業を処理する必要がありますか、それとも最初に他の場所でセキュリティチェックを実行する必要がありますか、それともコードがより複雑になりますか?

他の原則と同様に、これはKISS、YAGNIなどの他の原則と競合します。すべてはバランスの問題です。

3

SOLIDプログラミングの原則を説明し、この原則に従ったコードとそうでないコードの例を示す優れた記事は https://scotch.io/bar-talk/solid-the -first-five-principles-of-object-oriented-design

SRPに関連する例では、いくつかの形状クラス(円と正方形)の例と、複数の形状の総面積を計算するように設計されたクラスを示しています。

彼の最初の例では、面積計算クラスを作成し、出力をHTMLとして返します。その後、JSONとして表示することを決定し、面積計算クラスを変更する必要があります。

この例の問題は、彼の面積計算クラスが形状の面積の計算とその面積の表示を担当していることです。次に、領域を表示するために特別に設計された別のクラスを使用して、これを行うためのより良い方法を説明します。

これは簡単な例です(コードスニペットがあるため、記事を読んでより簡単に理解できます)が、SRPのコアアイデアを示しています。

2
blitz1616

私の頭では、頭に浮かぶSRPに最も近いのは使用フローです。特定のクラスの明確な使用フローがない場合は、おそらくあなたのクラスにデザインの匂いがあります。

使用フローは、期待される(したがってテスト可能な)結果を与える、指定されたメソッド呼び出しの連続です。基本的には、IMHOを使用したユースケースでクラスを定義します。そのため、すべてのプログラム方法論は実装よりもインターフェースに重点を置いています。

0
Arthur Havlicek

これは、複数の要件の変更を実現するためのものであり、コンポーネントを変更する必要はありません

しかし、幸運なことに、一見すると、SOLIDについて最初に聞いたときにそれを理解しています。


[〜#〜] srp [〜#〜][〜#〜] yagni [〜#〜]は、互いに矛盾する可能性があるというコメントがたくさんありますが、 [〜#〜] yagn [〜#〜]私はTDDによって実施されました(GOOS、London School)は私に考えることを教えましたクライアントの視点からコンポーネントを設計します。私は自分のインターフェースの設計をどのクライアントでも最低限実行してほしいこと、つまり最低限必要なことで設計を開始しました。そして、その演習はTDDの知識がなくても実行できます。

私はボブおじさんが説明したテクニックが好きです(悲しいことに、どこからか思い出せません)。

このクラスは何をしているのですか?

回答にAndまたはOrのいずれかが含まれていましたか

もしそうなら、答えのその部分を抽出し、それはそれ自身の責任です

この手法は絶対的なものであり、@ svidgenが言ったように、[〜#〜] srp [〜#〜]は判断の呼び出しですが、何か新しいことを学ぶときは、絶対的なものが最善であり、 alwaysだけで何かを行います。切り離さない理由を確認してください。教育を受けた見積もり、そしてnot方法がわからないため。これは芸術であり、経験が必要です。


[〜#〜] srp [〜#〜]について話すとき、多くの回答がデカップリングの議論のように思われると思います。

[〜#〜] srp [〜#〜]notで、変更がディペンデンシーグラフに反映されないようにします。

理論的には、[〜#〜] srp [〜#〜]がなければ、依存関係はありません...

1つの変更でアプリケーションの多くの場所が変更されることはありませんが、そのための他の原則があります。 [〜#〜] srp [〜#〜]Open Closed Principleを改善します。この原則は抽象化に関するものですが、抽象化が小さいほど再実装が容易になります

したがって、SOLID全体として教えるときは、[〜#〜] srp [〜#〜]change要件が変更されたときにコードが少なくなり、実際には、より少ないnewコードを記述できるようになります。

0
Chris Wohlert

SRPに続くコードを書くために私がしようとすること:

  • 解決する必要がある特定の問題を選択します。
  • それを解決するコードを記述し、すべてを1つのメソッド(例:main)で記述します。
  • コードを慎重に分析し、ビジネスに基づいて、実行されているすべての操作に表示される責任を定義してみてください(これは、ビジネス/プロジェクト/顧客にも依存する主観的な部分です)。
  • すべての機能がすでに実装されていることに注意してください。次はコードの編成のみです(このアプローチでは、今後、追加の機能やメカニズムは実装されません)。
  • 前のステップで定義した責任(ビジネスと「変更する1つの理由」の考えに基づいて定義されたもの)に基づいて、それぞれに個別のクラスまたはメソッドを抽出します。
  • このアプローチではSPRのみが考慮されることに注意してください。理想的には、他の原則を順守しようとする追加のステップがここにあるはずです。

例:

問題:ユーザーから2つの数値を取得し、それらの合計を計算して、結果をユーザーに出力します。

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

次に、実行する必要があるタスクに基づいて責任を定義します。これから、適切なクラスを抽出します。

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

次に、リファクタリングされたプログラムは次のようになります。

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

注:この非常に単純な例では、SRPの原則のみを考慮しています。他の原則(例:「L」-コードは具体化ではなく抽象化に依存する必要があります)を使用すると、コードにより多くの利点がもたらされ、ビジネスの変更に対してより保守しやすくなります。

0
Emerson Cardoso

それに対する明確な答えはありません。質問は狭いですが、説明はそうではありません。

私にとっては、オッカムのカミソリのようなものです。現在のコードを測定するのに理想的な場所です。単純明快な言葉でそれを突き止めるのは難しい。別のメタファーは、「単一の責任」と同じくらい抽象的な、つまり把握しにくい「1つのトピック」です。 3番目の説明は、「1つのレベルの抽象化での対処」です。

それは実際にはどういう意味ですか?

最近、主に2つのフェーズで構成されるコーディングスタイルを使用しています。

フェーズIは、創造的な混乱として最もよく説明されています。このフェーズでは、思考が流れるように、つまり生の醜いコードを書き留めます。

フェーズIIは完全に反対です。ハリケーン後の片付けのようなものです。これには最も多くの作業と規律が必要です。そして、デザイナーの観点からコードを見ていきます。

私は主にPythonで作業しています。これにより、後でオブジェクトとクラスを考えることができます。最初にフェーズI-関数のみを記述し、ほぼランダムにそれらを拡散します異なるモジュールフェーズIIでは、物事を進めた後、どのモジュールがソリューションのどの部分を処理するかを詳しく調べます。そして、モジュールをざっと見て、topics =は私にとって非常に重要です。いくつかの関数は主題的に関連しています。これらはclassesの良い候補です。そして、関数をクラスに変換した後-インデントとselfを追加して、 python;)のパラメータリスト-OccamのRazorのようにSRPを使用して、機能を他のモジュールやクラスに取り除きます。

現在の例は、先日小さなエクスポート機能を書くかもしれません。

csvExcel、および結合されたExcelシートが必要でした。

単純な機能はそれぞれ3つviews(= functions)で行われました。各関数は、フィルターを決定するための共通の方法と、データを取得するための2番目の方法を使用しました。次に、各関数でエクスポートの準備が行われ、サーバーからの応答として配信されました。

抽象化のレベルが多すぎて混同されていました:

I)着信/発信要求/応答の処理

II)フィルターの決定

III)データの取得

IV)データの変換

簡単な手順は、1つの抽象化(exporter)を使用して、最初の手順でレイヤーII〜IVを処理することでした。

残ったのは、トピック要求/応答の扱いだけでした。同じ抽象化レベルではリクエストパラメータの抽出で問題ありません。だから私はこれに対してview 1つの「責任」を持っていました。

次に、私が見たように、少なくとも3つの他の抽象化レイヤーで構成されているエクスポーターを分割する必要がありました。

フィルター条件の決定と実際の検索は、ほぼ同じレベルの抽象化です(フィルターは、データの適切なサブセットを取得するために必要です)。これらのレベルはデータアクセス層のようなものに入れられました。

次のステップで、実際のエクスポートメカニズムを分けました。一時ファイルへの書き込みが必要な場合、それを2つの「責任」に分けました。1つはディスクへの実際のデータの書き込み、もう1つは実際のフォーマットを処理する部分です。

クラスとモジュールの形成に沿って、物事はより明確になりました。そして、常に潜在的な質問、クラスやりすぎかどうか。

各クラスに必要な責任をどのように決定し、SRPのコンテキストで責任をどのように定義しますか?

従うべきレシピを与えるのは難しい。もちろん、不可解な"1レベルの抽象化"を繰り返すこともできます。

ほとんどの場合、それは現在のデザインにつながる一種の「芸術的直観」です。アーティストが粘土を彫刻したり、絵を描いたりするようなコードをモデル化します。

私をCoding Bob Ross;)として想像してください。

0
Thomas Junk

Robert C. Martinsの本 Clean Architecture:A Craftsman's Guide to Software Structure and Design から、2017年9月10日に発行され、Robertは62ページで次のように書いています。

歴史的に、SRPは次のよ​​うに説明されています。

モジュールには、変更する理由が1つだけ必要です

ソフトウェアシステムは、ユーザーと利害関係者を満足させるために変更されます。これらのユーザーと利害関係者「変更する理由」です。原則が話していること。実際、これを次のように言い換えることができます。

モジュールは、ユーザーまたは利害関係者の1つだけに責任を負う必要があります

残念ながら、「ユーザー」と「利害関係者」という言葉は、ここで使用するのに適切な言葉ではありません。システムを正常な方法で変更することを希望する複数のユーザーまたは利害関係者が存在する可能性があります。代わりに、実際にはグループを指します-その変更を必要とする1人以上の人々。そのグループをactorと呼びます。

したがって、SRPの最終バージョンは次のとおりです。

モジュールは1つだけのアクターに対して責任を負う必要があります。

したがって、これはコードに関するものではありません。 SRPは、要件とビジネスニーズのフローを制御するためのものであり、1つのソースからのみ取得できます。

0
Benny Skogberg

まず第一に、あなたが持っているものは実際には2つですseparate問題:クラスにどのメソッドを置くかという問題と、インターフェースの膨張の問題です。

インターフェース

あなたはこのインターフェースを持っています:

_public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}
_

おそらく、CustomerCRUDインターフェースに準拠する複数のクラス(そうでなければインターフェースは不要)と、準拠するオブジェクトを取り込むいくつかの関数do_crud(customer: CustomerCRUD)があります。しかし、あなたはすでにSRPを破っています。これら4つの異なる操作を結び付けました。

後でデータベースビューを操作するとします。データベースビューにはonlyで使用できるReadメソッドがあります。しかし、本格的なテーブルまたはビューのいずれかで演算子を透過的に実行する関数do_query_stuff(customer: ???)を作成したいとします。結局のところ、これはReadメソッドのみを使用します。

だからインターフェースを作る

publicインターフェースCustomerReader {public Customer Read(customerID:int)}

CustomerCrudインターフェイスを次のように因数分解します。

_public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}
_

しかし、終わりはありません。作成できるが更新できないオブジェクトなどがある可能性があります。このウサギの穴は深すぎます。単一責任の原則を順守する唯一の健全な方法は、すべてのインターフェースに1つのメソッドのみを含めるにすることです。 Goは実際、私が見たところからこの方法論に従っており、大多数のインターフェースには単一の関数が含まれています。 2つの関数を含むインターフェースを指定したい場合は、その2つを組み合わせた新しいインターフェースをぎこちなく作成する必要があります。すぐに、インターフェースの組み合わせが爆発的に増加します。

この混乱から抜け出す方法は、インターフェース(名目上のサブタイプの一種)ではなく、構造的なサブタイプ(OCamlなどで実装)を使用することです。インターフェースは定義していません。代わりに、単純に関数を書くことができます

_let do_customer_stuff customer = customer.read ... customer.update ...
_

それは私たちが好きなメソッドを呼び出します。 OCamlは型推論を使用して、これらのメソッドを実装する任意のオブジェクトを渡すことができるかどうかを判断します。この例では、customerの型が_<read: int -> unit, update: int -> unit, ...>_であると判断されます。

クラス

これはinterfaceの混乱を解決します。ただし、複数のメソッドを含むクラスを実装する必要があります。たとえば、CustomerReaderCustomerWriterという2つの異なるクラスを作成する必要がありますか?テーブルの読み取り方法を変更したい場合(たとえば、データをフェッチした後、応答をredisにキャッシュするようにした場合)はどうなりますか? this論理的結論への推論の連鎖に従うと、関数型プログラミングに密接につながります:)

0
gardenhead