オブジェクト指向ソフトウェア設計

最近、プロジェクトの開発プロセスでいくつかの問題に遭遇しました。反復的な開発プロセスの各段階で、以前のコードを修正する必要があることがよくあります。そのような状況は通常のことですが、新しい要件には必然的に新しい機能や新しい設計がもたらされます。 . 前のコードに影響を与えます。次のようなジョークを見た覚えがあります。

「プログラマーを殺すのに銃を使う必要はありません。要件を 3 回変更するだけです。」

実際、要件設計は 1 つの側面であり、さらに、設計者や開発者として、コード設計が合理的であるかどうか、元のコードに新しい機能を拡張することがなぜそれほど難しいのか、コードがなぜそうであるのかを考える必要があることがあります。不安定です 体全体を一度に動かしますか?
  私はプログラマーであること、少なくとも愚かな人ではないと思います。機能を完成させたいなら、いつでもそれを達成する方法を見つけることができます(そうでなければあなたはクビになっていたでしょう〜)、しかし、それを達成する方法が良いか、悪いですが、アイデアは導くことができると思います。ソフトウェア開発は始まったばかりではなく、しばらく前から存在していました。GOF の「デザイン パターン: 再利用可能の基礎」のように、先人から経験や教訓を吸収して自分自身を改善することができます。オブジェクト指向ソフトウェア」を参照してください。多くの問題に対する解決策がまとめられています。この間、私はオブジェクト指向設計のいくつかのアイデアを学ぶのにも時間を費やし、自分の理解の一部についても話しました。
  デザインパターンに関しては、この分野の本を読んだことがある人も多いと思いますが、読んだときは理解できても、うまく組み込めないという私と同じような混乱を抱えている人がいるかどうかはわかりません。実際の開発では、後でゆっくり学習します。恥ずかしいことに、私たちはある模様の表面しか見ていないのかもしれませんが、その模様の裏側に隠された「真実」はまだ発見されていないのかもしれません。実際、パターン設計の背後には、特定の設計原則に従う必要があります。

「デザインパターンよりも重要なのはデザイン原則である」

オブジェクト指向設計の概念は誰もが知っていますが、その設計目標は、ソフトウェア システムが次のことを実行できるようにすることです。

  • 拡張性: 元のシステムに影響を与えることなく、既存のシステムに新しい機能を簡単に追加できます。
  • 変更可能: コードの特定の部分を変更しても、他の無関係な部分には影響しません。
  • 代替可能: システム内のコードの特定の部分を同じインターフェイスを持つ他のクラスに置き換えても、既存のシステムには影響しません。

これらのいくつかは、ソフトウェア システムが合理的に設計されているかどうかを検出するために使用でき、保守と拡張が容易なソフトウェア システムを設計する方法は、従うことができる設計原則です。ロバート C. マーティンは、5 つの基本的なオブジェクト指向設計原則を提案しまし。 (個体):

  • S - 単一責任の原則
  • O - オープンクローズの原則
  • L - リスコフ置換原理
  • I-界面分離原理
  • D - 依存関係逆転の原則

オブジェクト指向設計を行うときは、これらの原則を念頭に置く必要があります。これにより、より優れた設計開発者になれます。少なくとも、コードはそれほど悪くなくなります。これらの原則を簡単に理解しましょう。

単一責任の原則: 単一責任の原則

クラスには 1 つの責任があり、クラスを変更する理由も 1 つだけあります。

簡単に言うと、クラスは 1 つのことしかうまくやれず、自分たちに関係のないことには関心がありません。犬やネズミは自分たちの仕事に口出しします。中心となるのは分離と高い凝集です。この原則は非常に単純に見えます。コードを書くときにこの原則を知らなくても、この方向に近づき、比較的単一の関数を持つクラスを作成します。しかし、この原則は簡単に破られます。関数は単一です。クラスは、より粒度の細かい責任 1 と責任 2 に絞り込む必要があるため、各反復プロセスで、リファクタリング前に書かれたコードを再編成し、異なる責任を異なるクラスまたはモジュールにカプセル化する必要がある場合があります。
栗を取ります:

 

@interface DataTransfer : NSObject
-(void)upload:(NSData *)data; //上传数据
-(void)download(NSString*)url;  //根据URL下载东西
@end

DataTransfer にはアップロード機能とダウンロード機能が含まれています。慎重に検討した結果、これは 2 つの機能を実装するのと同じであることがわかります。1 つはアップロードの関連ロジックを担当し、もう 1 つはダウンロードのロジックを担当します。これら 2 つの機能は相対的に重要です。 1 つの機能が変更された場合、たとえば、以前は AFNetworking を使用していましたが、現在はアップロードとダウンロードを他のサードパーティまたは nsurlconnection に切り替えたいと考えています。

  • アップロード方法の変更は DataTransfer の変更につながります
  • ダウンロード方法の変更はデータ転送の変更につながります

これは単一責任の原則に違反するため、それぞれの責任を担うために、異なる機能を 2 つの異なるクラスに分解する必要があります。ただし、この分解の粒度は人によって異なる場合があります。分解する必要がない場合もあります。デザインのためのデザインにはならないでください。

 

単一責任

 

  私たちのプロジェクトでは、この原則に違反する多くのコードがよく見られますが、その違反はより明白です。多くのクラスは豊富な関数のスーパー コレクションであり、クラス全体が肥大化して理解しにくくなります。現時点では、次のことが必要です。意識的にリファクタリングすること。

オープン・クローズド原則: オープン・クローズド原則

オープンクローズ原則の定義は、クラス、モジュール、関数などのソフトウェア エンティティは拡張にはオープンであるが、変更にはクローズである必要があるというもので、具体的には、元のコードを変更するのではなく、拡張機能を通じて変更を実装する必要があります。原則はオブジェクト指向設計の最も基本的な原則です。
  プロジェクトで要件を変更する必要がある場合は常に、コードに大幅な変更を加える必要があることが多いと前に述べました。これは主に、この原則の理解が十分に徹底されていないためです。
  オープンとクローズの原則の鍵は抽象化にあります。変更されないもの、または基本的に変更されないものを抽象化する必要があります。これらのものは比較的安定しており、そこで変更が閉じられます (これは、変更ができないという意味ではありません)変更しやすい部分についてはカプセル化も行っていますが、この部分は動的に変更できる部分であり、そこから拡張機能が開発されます。この原則 (今のモードは「もっと知覚的に理解する~」になるはずです)。

例: オブジェクトをデータベースに保存する必要があります。save() に似た保存メソッドがあります。この部分は変更しないでください。インターフェースは比較的安定していますが、特定の保存の実装は異なる可能性があります。 Sqlite データベースを、将来自己実装データベースに保存したい場合は、同じインターフェイスを持つ拡張クラスを実装して追加するだけで済みます。これは拡張機能に対してオープンであり、以前のコードを変更すると、新しいデータベースに保存する機能が実現され、システムの安定性が確保されます。

 

開閉原理

 

開閉の原則を実現するための指針となるイデオロギーは次のとおりです。

  • 比較的安定したインターフェイスを抽象化します。この部分は変更しない、またはほとんど変更しないでください。
  • パッケージ変更

しかし、ソフトウェア開発の過程においては、最初からオープン・クローズの原則に完全に従うことは難しく、継続的な反復リファクタリングの過程で改善し、予測可能な変更の範囲内で設計を進めていくことが多くなります。

リスコフ置換原理: リスコフ置換原理

この原則の定義: 基本クラスへのすべての参照は、そのサブクラスのオブジェクトを透過的に使用できなければなりません。簡単に言うと、基本クラスのコードが使用されているすべての場所がサブクラス オブジェクトに置き換えられても正常に実行できる場合、この原則は満たされます。そうでない場合は、継承関係に問題があり、2 つの間の継承関係は次のようになります。この原則は、オブジェクトの継承関係が合理的かどうかを判断するために使用できます。
たとえば、クジラのクラスがあり、クジラに魚から継承させ、魚には呼吸機能を持たせます。

 

クジラは魚から受け継いだもの

そして、水の中にいる間、魚は呼吸することができます。

 

if(isInwater){
    //在水中了,开始呼吸
    fish.breath();
}

クジラのサブオブジェクトを元の基本クラスの魚オブジェクトに置き換えると、クジラは水中で呼吸し始めますが、その後問題が発生します。クジラは哺乳類であり、水中で呼吸することはできません。常に水中にある場合は GG、スメクタであるため、これは原則に違反しており、クジラが魚から継承するのは不合理であると判断でき、再設計する必要があります。
  通常、設計時には継承よりも合成を優先します。これは、継承によってコードが削減され、コードの再利用性が向上しますが、親クラスとサブクラスの結合が強くなり、カプセル化が壊れてしまうためです。

インターフェイス分離の原則: インターフェイス分離の原則

この原則の定義: ユーザーは、使用しないインターフェイスに依存することを強制できません。簡単に言えば、クライアントにどのようなインターフェイスを提供するか、インターフェイスが肥大化しないように他の冗長なインターフェイスを提供しないようにする必要があります。そうしないと、オブジェクトの未使用のメソッドが変更されたときに、オブジェクトも影響を受けます。インターフェイスの設計は最小インターフェイスの原則に従う必要がありますが、実際、これは高い凝集性の表れでもあります。言い換えれば、巨大なインターフェイスを使用するよりも、単一の機能で凝集性の高い複数のインターフェイスを使用する方が良いのです。 。
  簡単な例を挙げると: たとえば、自転車のインターフェイスがあります。このインターフェイスには、GPS 位置測定やギアのシフト方法など、多くのメソッドが含まれています。

 

ISP の原則を満たしていません

 

 その後、通常の自転車でも GPS 測位機能とシフト機能を実装する必要があることがわかりました。これは明らかにインターフェイス分離の原則に違反しています。インターフェイスの最小化の原則に従って、次のように再設計します。

 

ISP の原則を満たす

 

  このように、各インターフェースの機能は比較的単一であり、一般的なインターフェースを使用するよりも、複数の特殊なインターフェースを使用する方が良いため、マウンテンバイクに GPS 測位の機能がない場合は、継承して実装する必要はありません。 iOS では、このような例が開発中に多数あります。たとえば、UITalbleView のプロキシには 2 つの異なるインターフェイスがあります。UITableViewDataSource は表示する必要があるコンテンツを担当し、UITableViewDelegate は一部のビューのカスタム表示を担当します。次に、原則として ISP を満たす複数のインターフェイスを継承します。

 

@interface ViewController () <UITableViewDataSource,UITableViewDelegate,OtherProtocol>

依存関係逆転の原理: 依存関係逆転の原理

この原則の定義: 高レベルのモジュールは低レベルのモジュールに依存すべきではなく、両方ともその抽象化に依存すべきであり、抽象化は詳細に依存すべきではなく、詳細は抽象化に依存すべきです。実際、これは「インターフェイスへのプログラミング」とよく言われることですが、ここでのインターフェイスは抽象化されており、プログラムする特定の実装に依存するのではなく、インターフェイスに依存する必要があります。
  Sqlite データベースに基づいて新しいデータベース システム AWEDatabase を開発する場合、Sqlite は最下位モジュールに相当し、AWEDatabase は上位モジュールに属し、AWEDatabase 開発ユーザーの観点から見ると、そのビジネス層は高レベルのモジュールと AWEDatabase は低レベルのモジュールになるため、モジュールのレベルは開発者の現在の観点から見る必要がありますが、DIP 原則は適切であり、さまざまな観点から観察する必要があります。高レベルのモジュールが低レベルのモジュールに直接依存している場合、その結果、低レベルのモジュールが変更されるたびに高レベルのモジュールが影響を受け、システム全体が不安定になり、これも規則に違反します。オープンクローズの原則。
  通常、この問題は中間層を導入することで解決します。この中間層は抽象インターフェイス層に相当します。高レベルのモジュールと低レベルのモジュールは両方ともこの中間層に依存して対話します。このようにして、中間抽象化が続く限り、レイヤーは変更されず、基礎となるモジュールは変更されますが、オープン性とクロージャーの原則を満たす上位モジュールには影響しません。また、上位モジュールと下位モジュールが同時に開発段階にある場合でも、中間抽象化層を使用すると、この抽象化層のインターフェイス用に各モジュールを同時に開発でき、高レベルのモジュールは、続行する前に基礎となるモジュールが開発されるまで待つ必要がありません。
  たとえば、私たちのプロジェクトでは IM に関連する機能があります。この IM モジュールは XMPP プロトコルで実装されています。クライアントはこのモジュールを使用してメッセージの送受信を実現します。しかし、後で他のプロトコルに切り替えたい場合は、 MQTT などのインターフェイス プログラミングを使用すると、モジュールの置き換えを簡単に実装できます。

 

インターフェイスに対するプログラミング

 

@protocol MessageDelegate <NSObject>
@required
-(void)goOnline;
-(void)sendMessage:(NSString*)content;
@end

//xmpp实现
@interface XMPPMessageCenter <MessageDelegate>
@end

//MQTT实现
@interface MQTTMessageCenter <MessageDelegate>
@end

//业务层
@interface BussinessLayer
//使用遵循MessageDelegate协议的对象,针对接口编程,以后替换也很方便
@property(nonatomic,strong)id<MessageDelegate> messageCenter;
@end

オブジェクト指向設計を行う際には、上記の原則を十分に考慮する必要があり、設計は最初は完璧ではないかもしれませんが、リファクタリングのプロセスで継続的に改善することができます。しかし実際には、デザインを考えるどころか、デザインプロセスを省略してモジュールを入手して直接コードを書く人も多く、プロジェクトにはそのような例がたくさんあります。もちろん、単純なモジュールの場合はあまり設計する必要がないかもしれませんが、比較的複雑なモジュールの場合は、コードを手書きする前に設計して考えることができ、この習慣を身につければ、コードの可読性、安定性、スケーラビリティは確実に向上します。コードは役に立ちます。

最も重要なソフトウェア開発ツールは、優れた設計の原則で訓練された精神です。

おすすめ

転載: blog.csdn.net/gp18391818575/article/details/112696680