元の定義では、この原則は次のように述べています。
クラスに変更する理由は1つだけです。
最初に、責任の概念を定義し、この概念を上記の言葉で結び付けてみましょう。 ソフトウェアコンポーネントには、それが書かれた理由があります。 それらは要件と呼ぶことができます。 実装されたロジックがコンポーネントに課された要件に従うことを保証することは、コンポーネント責任と呼ばれます。 要件が変わると、コンポーネントのロジックが変わるため、その責任が変わります。 したがって、原則の最初の定式化は、クラスが1つの責任、1つの目的のみを持つべきであるという事実と同等です。 次に、その変更の理由が1つあります。
最初に、原則違反の例を示し、これがどのような結果をもたらすかを確認します。 長方形の面積を計算し、グラフィカルインターフェイスに表示できるクラスを考えます。 したがって、クラスは、次のように定義できる2つの責任(および2つのグローバルな変更理由)を組み合わせます。
- クラスは、その両側の長方形の面積を計算できる必要があります。
- クラスは、長方形を描画できる必要があります。
次にサンプルコードを示します。
#using UI; class RectangleManager { public double W {get; private set;} public double H {get; private set;} public RectangleManager(double w, double h) { W = w; H = h; // Initialize UI } public double Area() { return W*H; } public void Draw() { // Draw the figure on UI } }
上記のコードでは、UI名前空間に実装されているサードパーティのグラフィックコンポーネントが描画に使用されることに注意してください。
このクラスを使用する2つのクライアントプログラムがあるとします。 そのうちの1つは計算を行うだけで、2つ目はユーザーインターフェイスを実装しています。
プログラム1:
#using UI; void Main() { var rectangle= new RectangleManager(w, h); double area = rectangle.Area(); if (area < 20) { // Do something; } }
プログラム2:
#using UI; void Main() { var rectangle= new RectangleManager(w, h); rectangle.Draw(); }
この設計には、次の欠点があります。
- プログラム1は、それを必要としないという事実にもかかわらず、外部UIコンポーネントに依存することを強制されます(ディレクティブ#using UI)。 この依存関係は、Drawメソッドに実装されたロジックによるものです。 その結果、これによりコンパイル時間が長くなり、クライアントマシンでプログラムの問題が発生する可能性があります。このようなUIコンポーネントは単にインストールされない場合があります。
- 描画ロジックが変更された場合は、RectangleManagerコンポーネント全体を再テストする必要があります。再テストしないと、面積計算ロジック、したがってProgram1が故障する可能性があります。
この場合、特に脆弱性(接続性が高いために変更を加えると簡単に壊れる)、および相対的な不動(UIに不必要に依存するためにプログラム1でクラスを使用するのが困難になる可能性があります)の悪いデザインの兆候があります。
この問題は、元のRectangleManagerコンポーネントを次の部分に分割することで解決できます。
- Rectangleクラス。面積を計算し、長方形の辺の長さを提供します。
- 四角形の描画を実装するRectanglePresenterクラス。
Rectangleクラスの責任は複雑であることに注意してください。つまり、辺の長さを提供する要件と面積を計算する要件の両方が含まれています。 したがって、責任はコンポーネントの契約、つまりその操作(メソッド)のセットを反映していると言えます。 この契約自体は、顧客の潜在的なニーズによって決まります。 私たちの場合、これは長方形の幾何学的パラメーターの提供です。 コードでは、次のようになります。
public class Rectangle { public double W {get; private set;} public double H {get; private set;} public Rectangle(double w, double h) { W = w; H = h; } public double Area() { return W*H; } } public class RectanglePresenter() { public RectanglePresenter() { // Initialize UI } public void Draw(Rectangle rectangle) { // Draw the figure on UI } }
行われた変更に応じて、クライアントプログラムコードは次の形式を取ります。
プログラム1:
void Main() { var rectangle= new Rectangle(w, h); double area = rectangle.Area(); if (area < 20) { // Do something } }
プログラム2:
#using UI; void Main() { var rectangle = new Rectangle(w, h); var rectPresenter = new RectanglePresenter(); rectPresenter.Draw(rectangle); }
これは、プログラム1がグラフィカルコンポーネントに依存しなくなったことを示しています。 さらに、不要な依存関係がなくなったという原則に従って、コードはより構造化され信頼性が高まりました。
ほとんどの場合、責任の一意性の原則は、コンポーネントの接続性を減らし、コードを読みやすくし、単体テストの記述を簡素化します。 ただし、これは単なる一般的な推奨事項であり、その適用に関する決定は特定の状況に基づいて行う必要があることを常に覚えておく必要があります。 責任の分担は意識的でなければなりません。 これを行うべきではない場合の例を次に示します。
- 既存のクラスを分割すると、クライアントコードが簡単にクラッシュする可能性があります。 ロジックが高品質の単体テストで十分にカバーされていない場合、および/または手動/自動テストが不十分な場合、開発およびテスト段階でこれに気付くことは困難です。 そのような故障は、会社のお金、評判などを犠牲にすることがあります。
- クライアントコードとコンポーネントの開発者はすべてに満足しているため(原則の存在を認識している間)、責任を分離する必要はありません。 要件は実質的に変更されていません。 さらに、これは既存のクラスと、まだ作成されていないが設計段階にあるクラスの両方に適用されます。
- 他のケースでは、分離の利点がそれからの害よりも小さい場合。
ただし、原則の知識と理解により、開発者の視野が改善され、作成されたソリューションをより効果的に設計および保守できるようになります。