私は現在、Robert Martinsの本「Agile Software Development」を読んでいますが、彼のモデムの例がどのように役立つかを理解するのに苦労しています。
彼はSRPに違反するインターフェースを持っていると言います
interface Modem
{
public void dial(String pno);
public void hangup();
public void send(char c);
public char recv();
}
そこで彼は、モデムの実装が接続インターフェースとデータチャネルインターフェースに依存するように分割することにしました。ただし、その後、モデムが接続およびデータチャネルインターフェイスを実装するとき、彼のクラスモデムは、メソッドを個別に実装する必要があります。
私には、これは正しくないようです。私は誤解していると思いますし、誰かが私が間違っているところを指摘してくれることを期待しています。ここに私の理解を表すScalaでの私の実装があります。
誰かが私が間違っているところを指摘してくれるといいのですが。注Scalaの場合、Traitはインターフェースのと似ています。この例のすべての意図について、その特性は「インターフェース」の単なる別の単語であり、戻り値の型「ユニット」は「Void」と同じ。
trait IDataChannel {
def send(message: String)
def receive(): String
}
trait IConnection {
def dial(number: String)
def hangup()
}
trait IModem extends IConnection with IDataChannel{}
class ModemImpl extends IModem {
def hangup(): Unit = {}
def receive() = { "hi" }
def send(message: String): Unit = {}
def dial(number: String): Unit = {}
}
彼の執筆が読者側のデザインの知識をもう少し推測しているので、あなたが混乱している理由がわかります。
責任は、ドメインとソフトウェアの進化に依存します(「変更する理由」。開発者だけが想像したのではなく、コードベースの変更を推進する外部の力によって、ビジネスニーズによって(おそらく時間の経過とともに)確立されます。保守可能なコードを作成するには、これらの種類の変更をサポートするために、ユーザー(およびチーム)がこれらを把握し、ソフトウェアを長期にわたって構築する必要があります。 ( 最初に であることに注意してください、設計を行っても明らかな方法で成果が得られないため、経験の浅い開発者がそのユーティリティを確認するのは簡単ではありません。その上、「過剰設計」のリスクがあります。 。しかし、後でそれを考慮しないと、問題が発生します。)
したがって、彼は、接続管理とデータ通信が2つの異なる責任であることが何らかの方法で確立されているという仮定から始めます。つまり、変更要求を見ると、通常は一緒に変更されないという意味です。
次に、彼はインターフェース自体の責任について話します-インターフェースを変更するさまざまな理由として読み取ることができます。実装がなくても、他のコードが依存する静的なコードピースであるため、これは興味深いものです。変更が依存関係の矢印に沿って逆方向に伝播するため、設計における主な懸念事項の1つは、依存関係の方向と構造を制御することです(そのため、DIPはその伝播を停止する手法であり、ある時点で矢印を逆にします)。
彼は、インターフェイスを分離することで、2つのインターフェイスを呼び出すクライアントコードの独立した進化可能性をどのように実現(または、向上)できるかについてはあまり触れていません。しかし、私が空白を埋める必要がある場合:これは、開発者の規律と設計知識に依存しています。これは、クライアントコードでこれを活用して、これらの呼び出しの「オーケストレーション」を分離できるという意味です(「オーケストレーション」) 、呼ばれるものの論理といつ-クライアントの責任である)。これは、2つの異なるクライアントクラスのように単純な場合もあれば、クライアントが別々のDLLに存在する場合はさらに複雑な場合もあります。その上、クライアントは、インターフェイスが単一のクラス、2つの別個のクラス、またはサブシステム全体によって実装されているかどうかを知りません。そのため、インターフェースの背後にある実装を変更する柔軟性が得られます。
彼はまた、これら2つが個別に変更されない場合(まったく変更されないため、またはそれらの変更が強く相関しているため)も、これは重要であると述べていますそれだけのためにSRPまたはISPを適用します。
変更軸は、変更が発生した場合にのみ変更軸になります。症状がない場合は、SRPやその他の原則を適用することは賢明ではありません。
ですから、一度デザインをすることはありません-はい、最初は何かを思いつきますが、時間をかけてそれを開発し、再考します。
実装でのSRP違反については、これまでに読んだテキストのすぐ下の段落で、彼はそれを認めて書いています:
[...]私はModemImplementationクラスに両方の責任を持たせていることに注意してください。これは望ましくありませんが、必要な場合があります。多くの場合、ハードウェアまたはオペレーティングシステムの詳細に関係しているため、結合したくないものを結合する必要があります。ただし、インターフェイスを分離することで、アプリケーションの残りの部分に関する概念を分離しました。
ModemImplementationクラスは、kludgeまたはいぼとして見ることができます。ただし、すべての依存関係がそこから流れ出ることに注意してください。このクラスに依存する必要はありません。 main以外の人は、それが存在することを知る必要はありません。したがって、私たちは醜いビットをフェンスの後ろに置きました。その醜さは、リークしてアプリケーションの残りを汚染する必要はありません。
したがって、ドメインにはかなりの深さがあります-ドメインの理解、実際的な考慮事項の理解、トレードオフの理解などです。
SRPは実際には 単一の責任の原則 の略です。マーティン氏は、Modem
インターフェースがSRPに違反していることは正しいです。 ModemImpl
はSRPに違反しています。 Connection
とData
の責任を分離することもできません。マーティン氏はインターフェースを修正していた。彼はModem
クラスを修正していません。これらの原則について理解しておくべきことの1つは、コードを改善するにつれて段階的に適用できることです。
通常、SRPがインターフェースに焦点を当てている場合、 インターフェース分離の原則 が言及されます。これが実行されると、システムの残りの部分は、それを使用する必要がある方法で使用できる限り、実装クラスがどのように混乱しているかを知らず、気にしません。
インターフェイスが修正され、おそらくそれを使用するコードが修正されたので、実装を簡単に修正できます。または、少なくともこのSRP違反が周囲に広がっていないという知識で、より重要な作業に取り掛かっている間は無視できます。コードベース。
それを理解するには、使用するコードが何であるかをよりよく想像してください。データが入ってくると、接続がない場合は待つ必要があります。データをすぐに送信できない場合は、バッファリングするか破棄する必要があります。これは、データを送信する動作を接続状態に結び付けます。 4つの方法すべてにアクセスできる場合、それをすべて管理することは非常に困難です。
ただし、考えたいのが送受信だけの場合は、Data
インターフェースに焦点を当てて、接続の問題を他の問題にするのは非常に便利です。
UDPは送信パケットを忘却の中に送信することを完全に喜んでいます。彼らがそこに着いてもいなくてもかまいません。このようなことをしているのなら、なぜ他のメソッドにアクセスしたいのでしょうか?モデムが接続されていることの確認が別の仕事である場合は、それを処理する必要があるように見せないでください。そうしないと、野心的なバグハンターが誤って処理する必要があると考え、コードベースでSRP違反が広がっています。 。考える必要のないことを考えさせないでください。
SRPは変更の理由、そして人々についてです。
read4bytes
およびwrite4bytes
)send
やreceive
などの上位レベルのセマンティクスを提供および提供する責任があります。trait IModem extends IConnection with IDataChannel{}
これはIModem
がSRPに違反しているので、そうはしません。あなたのModemImpl
クラスはそれらの特性を消費するだけです。
そこで彼は、モデムの実装が接続インターフェースとデータチャネルインターフェースに依存するように分割することにしました。ただし、その後、モデムが接続およびデータチャネルインターフェイスを実装するとき、彼のクラスモデムはメソッドを個別に実装する必要があります。
特性/インターフェースはソフトウェアの他の部分が消費するものであるため、インターフェースは問題ありません。 SRP。実装はあなた次第であり、システムの他の部分からそれらの詳細を隠します。 SRPはカップリングを排除しないため、実装でそれを解決する必要がありますが、概念的には特性がクリーンです。