「C++ 高度なプログラミング」読書メモ (5、6: オブジェクト指向設計と再利用可能なコードの設計)

1. 参考文献

2. 始めるには、「21 Days to Learn C++」という本を読むことをお勧めします。ノートのリンクは次のとおりです。

1. プロセス思考

  • 手続き型言語 (C など) はコードを小さなチャンクに分割し、それぞれが (理論上) 単一のタスクを実行します。C にプロシージャがないと、すべてのコードが main() に集中し、コードが読みにくくなります。
  • コンピューターは、コードが main() 内にあるか、わかりやすい名前とコメントが付いた小さなチャンクに分割されているかどうかを気にしません。プロシージャは、プログラマやコードを読んだり保守したりする人を支援するために存在する抽象概念です。この概念は、「プログラムは何をするのか?」というプログラムに関する基本的な質問に基づいています。この質問に言葉で答えるのがプロセス思考です

2. オブジェクト指向の考え方

  • 「プログラムは何をするのか」という質問に基づく手続き型アプローチとは異なり、オブジェクト指向アプローチでは、「どの実際のオブジェクトがシミュレートされるのか」という別の質問が行われます。OOP (オブジェクト指向プログラミング) の基本概念は、プログラムをタスクに分割するのではなく、自然オブジェクトのモデルに分割する必要があるということです。これは最初は抽象的に見えるかもしれませんが、クラスのコンポーネント、プロパティ、動作の観点から実際のオブジェクトを考慮すると、アイデアがより明確になります。

2.1クラス

  • クラスはオブジェクトをその定義から区別します。
  • クラスは、オブジェクトのクラスを定義するために使用される情報を単純にカプセル化します。
  • すべてのオブジェクトは特定のクラスに属し、オブジェクトはクラスのインスタンスです

2.2 コンポーネント

  • 基本的にコンポーネントはクラスに似ていますが、より小さく、より具体的です。
  • 複雑な実際のオブジェクトを考えてみると、それが多くの小さなコンポーネントで構成されていることがわかります。

2.3 プロパティ

  • プロパティは、あるオブジェクトを他のオブジェクトから区別します。
  • クラス属性はすべてのクラス メンバーによって共有されますが、クラスのすべてのオブジェクトはオブジェクト属性を持ちますが、値は異なります。
  • 属性は、オブジェクトの特性を記述し、「なぜこのオブジェクトは異なるのか?」という質問に答えるために使用されます。

2.4 動作

  • 行動は、「オブジェクトは何をするのか」と「オブジェクトに対して何ができるのか」という 2 つの質問に答えます。
  • オブジェクト指向プログラミングでは、関数コードの多くがプロシージャからクラスに移動されます。OOP は、特定の動作を持つオブジェクトを確立し、オブジェクトがどのように相互作用するかを定義することにより、コードとそのコードが操作するデータをリンクするためのより豊富なメカニズムを提供します。
  • クラスの動作はクラスメソッドによって実装されます

3. オブジェクト間の関係

3.1 関係が「ある」

  • 「has-a」関係または集約関係のスキーマは、A が B を持つ、または A が B を含むというものです。このタイプの関係では、オブジェクトは別のオブジェクトの一部であると見なされます。コンポーネントは他のオブジェクトを構成するオブジェクトを表すため、前に定義したコンポーネントは通常、「ある」関係を表します。

3.2 「である」関係 (継承)

  • 「である」関係はオブジェクト指向プログラミングにおける非常に基本的な概念であるため、派生、サブクラス化、拡張、継承などの多くの名前があります。クラスは、現実世界にプロパティと動作を備えたオブジェクトが含まれているという事実をモデル化し、継承は、これらのオブジェクトが通常は階層形式で編成されているという事実をモデル化します。「is one」はこの階層関係を示しています。基本的に、継承のパターンは次のとおりです。A は B である、または A は実際には B に非常に似ています。
  • Animal クラスは、すべての動物に共通するプロパティ (サイズ、居住エリア、食べ物など) と行動 (歩行、食事、睡眠) をカプセル化するように定義できます。特定の動物 (サルなど) は、Animal のサブクラスになります。これは、サルには動物のすべての特徴が含まれており、また、それをユニークにする他の特徴も含まれているためです。

ここに画像の説明を挿入

  • クラス間に「is-a」関係がある場合、目標の 1 つは、他のクラスが拡張できる共通の機能を基本クラスに組み込むことです。すべてのサブクラスに類似または同一のコードがある場合は、コードの一部またはすべてを基本クラスに入れることを検討する必要があります。こうすることで、必要な変更を 1 か所で行うことができ、将来のサブクラスはこれらの共有機能を「無料」で利用できるようになります。
3.2.1 継承技術
  • 機能の追加
    • 派生クラスは、基本クラスに機能を追加できますたとえば、猿は木にぶら下がることができる動物です。動物のすべての動作に加えて、サルには木の間を移動する動作もあります。つまり、Monkey クラスにはスイングFromTrees() メソッドがあり、この動作は Monkey クラスにのみ存在します。
  • 置換関数
    • 派生クラスは、親クラスの動作を完全に置き換えたりオーバーライドしたりできますたとえば、ほとんどの動物は歩くので、Animal クラスには歩行をシミュレートする移動動作が含まれる可能性があります。ただし、カンガルーは歩くのではなくジャンプすることで移動する動物です。Animal 基本クラスの他のプロパティと動作は引き続き適用され、カンガルー派生クラスは移動動作の仕組みを変更するだけで済みます。

    もちろん、基底クラスのすべての機能を置き換える場合、基底クラスが抽象基底クラスでない限り、継承方法がまったく正しくないことを意味する可能性があります。抽象基底クラスは、すべてのサブクラスにすべてのメソッドを実装することを強制します。実装されていますが、抽象基本クラスのインスタンスを作成できません

  • 属性を追加
    • 基本クラスからのプロパティの継承に加えて、派生クラスは新しいプロパティを追加できますペンギンはくちばしの大きさ(くちばしの大きさ)の属性に加えて、動物のあらゆる属性を備えています
  • 属性を置換する
    • メソッドのオーバーライドと同様に、C++ にはプロパティをオーバーライドするためのメソッドが用意されていますただし、そうすることは、基本クラスからプロパティを隠すことになるため、通常は不適切です。たとえば、基本クラスが特定の名前を持つプロパティに 1 つの値を割り当て、派生クラスがそのプロパティに別の値を割り当てる可能性があります。
3.2.2 ポリモーフィズムとコードの再利用
  • ポリモーフィズムとは、標準のプロパティとメソッドを持つオブジェクトを互換的に使用できることを意味します
  • 動物園をシミュレートする場合、プログラムで動物園内のすべての動物を走査し、各動物を 1 回ずつ移動させることができます。すべての動物は Animal クラスのメンバーであるため、移動方法を知っています。特定の動物は移動動作をオーバーライドしますが、そこが光る点です。コードは各動物に移動するよう指示するだけで、動物の種類は知りませんし、気にすることもありません。すべてが独自の方法で移動します。
  • ポリモーフィズム以外に継承を使用する理由はもう 1 つあります。通常、それは単に既存のコードを利用するためですたとえば、エコー効果のある音楽再生クラスが必要で、同僚が音楽を再生するが他の効果を持たないクラスを作成した場合、この既存のクラスを拡張して、新しいエコー関数を追加できます。

3.3 関係ではない

  • クラス間の関係を考えるときは、実際にクラス間に関係があるかどうかを考慮する必要があります。問題は、実際のものの間に明らかな関係があるが、コード内には関係がない場合に発生しますOO (オブジェクト指向) 階層では、機能的な関係を人為的に作成するのではなく、モデル化する必要があります。以下の図に示されている関係は、一連の概念または階層としては意味がありますが、コード内で意味のある関係を表すものではありません。
    ここに画像の説明を挿入

不必要な継承を避ける最善の方法は、最初に大まかな設計を行うことです。各クラスおよび派生クラスに設定する予定のプロパティと動作を書き出します。特定のクラスに固有のプロパティやメソッドがないことがわかった場合、または特定のクラスのすべてのプロパティやメソッドが派生クラスによってオーバーライドされていることがわかった場合は、そのクラスが前述の抽象基本クラスでない限り、次のことを再考する必要があります。デザイン

3.4 階層

  • クラス A がクラス B の基本クラスになることができ、B がクラス C の基本クラスになることができるのと同様に、オブジェクト指向階層も同様のマルチレベルの関係をモデル化できます各派生クラスのコードを記述する場合、コードの多くは類似する可能性があります。この時点で、共通の親クラスを持たせることを検討する必要があります。

ここに画像の説明を挿入

  • 優れたオブジェクト指向階層では、次のことが可能になります。
    • クラス間の意味のある機能的な関係を可能にする
    • 共通の機能を基本クラスに配置することで、コードの再利用が可能になります
    • 親クラスが抽象クラスでない限り、サブクラスが親クラスの関数を書き換えすぎないようにします。

3.5 多重継承

  • 多重継承では、クラスは複数の基本クラスを持つことができます (多重継承は通常避けるべきです)

ここに画像の説明を挿入

4. 抽象化

4.1 インターフェースと実装

  • 抽象化の鍵は、インターフェイスと実装を効果的に分離することです
    • 実装はタスクを達成するために使用されるコードであり、インターフェイスは他のユーザーがコードを使用する方法です。
    • C では、ライブラリ関数を記述するヘッダー ファイルがインターフェイスです。オブジェクト指向プログラミングでは、クラスのインターフェイスはパブリック プロパティとメソッドのコレクションです。
    • 優れたインターフェイスにはパブリックな動作のみが含まれており、クラスのプロパティ/変数は決してパブリックであってはなりませんが、ゲッターとセッターと呼ばれるパブリック メソッドを通じて公開できます。

4.2 公開するインターフェイスの決定

  • クラスを設計するとき、他のプログラマがオブジェクトをどのように操作するかが問題になります。C++ では、クラスのプロパティとメソッドはパブリック (パブリック)、プロテクト (保護)、プライベート (プライベート) のいずれかになります。
    • public は、他のコードがアクセスできることを意味します
    • protected は、他のコードはこのプロパティまたは動作にアクセスできないが、サブクラスはアクセスできることを意味します。
    • private は、他のコードだけがこのプロパティや動作にアクセスできないだけでなく、サブクラスもアクセスできないことを意味します。
  • パブリック インターフェイスの設計とは、どれをパブリックにするかを選択することです
    • ユーザーを考慮します: 個人、チームの他のメンバー、ユーザーなど。
    • 検討目的:API、ユーティリティクラスまたはライブラリ、サブシステムインターフェース、コンポーネントインターフェース
    • 将来のことを考える: 将来の用途が不明な場合は、包括的なロギング クラスを設計しないでください。設計、実装、パブリック インターフェイスが不必要に複雑になるためです。

5. 再利用可能なコードを設計する方法

  • 再利用可能なコードには 2 つの主な目標があります。それは、コードが共通であることと、コードが使いやすいことです。

5.1 抽象化の使用

  • 抽象化を使用することは、コードを使用する自分自身とクライアントにとって有益です
    • クライアントは、実装の詳細について心配する必要がないため、コードが実際にどのように動作するかを理解する必要がなく、提供された機能を活用できるという利点があります。
    • インターフェイスを変更せずに基礎となるコードを変更できるため、利点が得られます。これにより、顧客が使用法を変更する必要なく (ダイナミック リンク ライブラリが使用されている場合は実行可能ファイルを再構築することさえ)、コードをアップグレードおよび改訂できるようになります。
  • 場合によっては、ライブラリでは、あるインターフェイスから返された情報を他のインターフェイスに渡すために、クライアント コードで保存する必要があります。この情報はハンドルと呼ばれることもあり、呼び出し時の状態を記憶する必要がある特定のインスタンスを追跡するためによく使用されます。
    • ライブラリの設計にハンドルが必要な場合は、ハンドルの内部を公開しないでくださいハンドルは不透明なクラスに配置でき、プログラマはこのクラスの内部データ メンバーに直接アクセスしたり、パブリック ゲッターやセッターを介してアクセスしたりすることはできません。
    • クライアント コードでハンドル内の変数を変更する必要はありません悪い設計の例としては、エラー ログを有効にするためにハンドルを不透明にする必要がある構造体の特定のメンバーを設定する必要があるライブラリが挙げられます。

5.2 理想的に再利用可能なコードの構築

5.2.1 無関係な概念や論理的に独立した概念の組み合わせを避ける
  • コンポーネントを設計するときは、単一​​のタスクまたはタスクのグループ、つまりSRP (単一責任原則、単一責任原則) とも呼ばれる「高度な集約」に焦点を当てる必要があります。乱数ジェネレーターと XML パーサーなど、無関係な概念を組み合わせないでください。

    • このプログラミング戦略は、交換可能な独立した部品の現実世界の設計原則を模倣していますたとえば、エンジンのすべてのプロパティと動作を配置する Car クラスを作成できます。しかし、エンジンは独立したコンポーネントであり、車の他の部分とは結びついていません。エンジンはある車から取り外して別の車に取り付けることができます。合理的な設計は、エンジン関連のすべての機能を含む Engine クラスを追加することです。今後、Car インスタンスには Engine インスタンスが含まれます。
  • プログラムを論理サブシステムに分割する

    • サブシステムは、個別に再利用できる個別コンポーネント、つまり「低結合」として設計します。たとえば、オンライン ゲームを設計する場合、一方のコンポーネントを他方のコンポーネントに関与させることなく再利用できるように、ネットワークと GUI を別のサブシステムに配置する必要があります。ここでスタンドアロン ゲームを作成すると仮定すると、グラフィカル インターフェイス サブシステムは再利用できますが、ネットワーク機能は必要ありません同様に、ピアツーピアのファイル共有プログラムを設計することもできます。この場合、ネットワーク サブシステムは再利用できますが、GUI 機能は必要ありません。
  • クラス階層を使用して論理概念を分離する

    • プログラムを論理サブシステムに分割することに加えて、関連のない概念をグループ化することはクラス レベルで避けるべきです。たとえば、自動運転車用のクラスを作成するとします。最初に自動車の基本クラスを作成し、次にすべての自動運転ロジックをそこに直接組み込むことにします。しかし、プログラムに非自動運転車だけが必要な場合はどうなるでしょうか? この時点で、自動運転に関連するすべてのロジックが失敗し、プログラムは、ビジョン ライブラリや LIDAR ライブラリなど、回避可能なライブラリとリンクする必要があります。解決策は、自動運転車をクラス階層として作成することです。普通自動車の派生品

ここに画像の説明を挿入

  • 集約による論理概念の分離
    • 継承アプローチが適切でない場合、集約を使用して、無関係な機能、または関連しているが独立した機能を分離することができますたとえば、家族のメンバーを保存する Family クラスを作成するとします。明らかに、ツリー データ構造は、この情報を保存するのに理想的な構造です。ツリー データ構造のコードを Family クラスに統合する代わりに、別の Tree クラスを作成する必要があります。そうすれば、Family クラスに Tree インスタンスを含めて使用できるようになります。
5.2.2 汎用データ構造およびアルゴリズム用のテンプレートの使用
  • C++ テンプレートの概念により、型またはクラスの形式で汎用構造を作成できます。たとえば、整数の配列のコードを作成するとします。将来的に二重配列を使用したい場合は、すべてのコードを書き直して複製する必要があります。テンプレートの概念は、型を指定する必要があるパラメーターに変換するため、任意の型に対してコード本体を作成できます。テンプレートを使用すると、あらゆる型に適用されるデータ構造とアルゴリズムを作成できます。

    • 最も単純な例は、C++ 標準ライブラリの一部である std::vector クラスです。整数型のベクトルを作成するには std::vector<int> と記述し、double 型のベクトルを作成するには std::vector<double> と記述します。
  • テンプレートの問題

    • テンプレートは完璧ではありません。まず、構文は、特にテンプレートを使用したことがない人にとってはわかりにくいです。次に、テンプレートには同じタイプのデータ構造が必要であり、同じタイプのオブジェクトのみを構造に格納できます。
  • テンプレートと継承

    • 同じ機能を異なるタイプに提供する場合は、テンプレートを使用しますたとえば、任意の型で機能する汎用の並べ替えアルゴリズムを作成したい場合は、テンプレートを使用する必要があります。
    • 任意の型を保存できるコンテナーを作成したい場合は、 テンプレート を使用する必要があります。重要な概念は、テンプレート化された構造またはアルゴリズムがすべての型を同じ方法で扱うということです。ただし、必要に応じて、テンプレートを特定のタイプに特化して別の方法で処理することができます。
    • 関連する型の異なる動作を提供する必要がある場合は、継承を使用する必要がありますたとえば、キュ​​ーと優先キューなど、2 つの異なるが類似したコンテナーを提供する場合は、継承を使用する必要があります。
    • これで、2 つを組み合わせて、テンプレート化されたクラスの派生元となるテンプレートの基本クラスを作成できるようになりました。
  • スケーラビリティ

    • クラスは拡張可能に設計される必要があり、クラスから他のクラスを派生することによって拡張できますただし、適切に設計されたクラスは変更すべきではありません。つまり、その動作は実装を変更せずに拡張可能である必要がありますこれはオープン/クローズド原則 (OCP) と呼ばれます。

5.3 便利なインターフェースの設計

5.3.1 使いやすいインターフェースを設計する
  • 使い慣れたアプローチを使用する

    • 使いやすいインターフェースを開発するための最良の戦略は、標準的で使い慣れた方法に従うことです。人は、過去に使用したインターフェースに似たインターフェースに遭遇すると、それをよりよく理解し、より簡単に採用し、誤って使用する可能性が低くなります。イノベーションはもちろん重要ですが、イノベーションはインターフェースではなく、基盤となる実装にあるべきです。
    • C++ に戻ると、この戦略では、開発されたインターフェイスはC++ プログラマに馴染みのある標準に従う必要があると述べています。
    • C++ には、オブジェクトの使いやすいインターフェイスの開発に役立つ演算子のオーバーロードと呼ばれる言語機能が用意されています。
  • 必要な機能を省略しない

    • まず、インターフェースにはユーザーが使用できるすべての動作が含まれている必要があります。
    • 次に、実装にできるだけ多くの機能を含めます。
  • クリーンなインターフェイスを提供します

    • インターフェースに冗長な機能を提供せず、インターフェースを簡潔に保ちます
  • ドキュメントとコメントを提供する

    • インターフェイスのドキュメントを提供するには、インターフェイス自体内のコメントと外部ドキュメントの2 つの方法があります。可能な限り、両方のタイプの文書を提供する必要があります。ほとんどの公開 API は外部ドキュメントのみを提供します。多くの標準 UNIX および Windows ヘッダー ファイルにはコメントがありません。UNIX では、ドキュメントは通常、マンページと呼ばれるオンライン マニュアルの形式です。Windows では、IDE には通常ドキュメントが付属しています。
5.3.2 共通インターフェースの設計
  • 同じ機能を実行する複数のメソッドを提供します

    • すべての「顧客」を満足させるために、同じ機能を実行する複数の方法が提供されることがあります。ただし、アプリケーションが多すぎるとインターフェイスが乱雑になりやすいため、このアプローチは注意して使用する必要があります。
  • カスタマイズを提供する

    • インターフェースの柔軟性を高めるためにカスタマイズが可能です。カスタマイズは、ユーザーがエラー ログをオンまたはオフにできるようにするのと同じくらい簡単です。カスタマイズの基本的な前提は、各顧客に同じ基本機能を提供し、ユーザーがそれをわずかに調整できるようにすることです。
    • 関数ポインターとテンプレート パラメーターを通じて、より高度なカスタマイズを提供します
5.3.3 共通性と使いやすさの調整
  • 複数のインターフェースを提供

    • 十分な機能を提供しながら複雑さを軽減するために、2 つの独立したインターフェイスが提供されています。これは、インターフェイス分離原則 (Interface SegregationPrinciple、ISP) と呼ばれます。たとえば、汎用ネットワーク ライブラリは 2 つの異なる方向で作成できます。1 つはゲーム用のネットワーク インターフェイスを提供し、もう 1 つはハイパーテキスト転送プロトコル (HTTP、Web ブラウジング プロトコル) 用のネットワーク インターフェイスを提供します。
  • 共通機能を使いやすくする

    • 共通のインターフェイスを提供する場合、一部の機能が他の機能よりも頻繁に使用されます。高度な機能のオプションを提供しながら、一般的な機能を使いやすくする必要があります

5.4 堅固な原則

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/qq_42994487/article/details/131093651