目次
VC++共通機能開発まとめ(コラム記事一覧、購読歓迎、継続更新…)https://blog.csdn.net/chenlycly/article/details/124272585 C++ソフトウェア例外トラブルシューティング入門から習得までシリーズチュートリアル(コラム記事)リスト、購読へようこそ、更新し続けます...) https://blog.csdn.net/chenlycly/article/details/125529931 C++ ソフトウェア分析ツール事例集 (更新中...) https://blog.csdn.net /chenlycly/category_12279968.html ソフトウェア設計原則は、私たちが毎日書くコードの品質に関係するだけでなく、ソフトウェア設計パターンを設計するときに従う原則にも関係します。つまり、設計原則は設計パターンの基礎です。ソフトウェア設計パターンを学ぶには、ソフトウェア アーキテクチャ設計の 7 つの原則を理解する必要があります。今日はソフトウェア設計原則に関わる内容についてお話します。
1。概要
7 つのソフトウェア設計原則には、オープンとクローズの原則、依存性逆転の原則、単一責任の原則、インターフェイス分離の原則、デメテルの原則、リスコフ置換の原則、構成再利用の原則が含まれます。
ソフトウェア開発では、ソフトウェア システムの保守性と再利用性を向上させ、ソフトウェア システムの拡張性と柔軟性を高めるために、プログラマは次の 7 つの原則に従ってプログラムを開発するように努める必要があります。設計原則に従った開発により、ソフトウェア開発効率が向上し、ソフトウェア開発および保守コストを節約できます。
これら 7 つの設計原則には、それぞれ異なる重点があります。その中で、オープンとクローズの原則は、拡張にはオープンで変更にはクローズであることを示す一般的な概要であり、リスコフ置換の原則は、継承システムを破壊しないことを示し、依存関係逆転の原則は、プログラムすることを示します。インターフェイスへの責任; 単一責任の原則は、クラス Single の責任を認識するように指示します; インターフェイス分離の原則は、インターフェイスを設計する際に簡潔かつ単純であるように指示します; ディミッターの法則は、結合度を減らすように指示します; の原則複合再利用は、組み合わせまたは集約関係の再利用の使用を優先し、継承関係の再利用の使用を減らすように指示します。
しかし、実際のソフトウェア開発現場では、すべてのコードが設計原則に従う必要はなく、人手、時間、コストを総合的に考慮し、意図的に完璧を追求することなく、適切なシナリオで適用可能な設計原則に従う必要があります。これはバランスをとる行為であり、より高品質でよりエレガントなコードを設計するのに役立ちます。
2. 7 つの設計原則
2.1. 開閉原理
2.1.1、開閉原理の定義
オープン クローズド原則 (OCP) は、Bertrand Meyer が 1988 年の著書『オブジェクト指向ソフトウェア構築』 (オブジェクト指向ソフトウェア構築) で提案したものです。ソフトウェア エンティティは拡張に対してオープンであり、変更に対してはクローズされている必要があります (ソフトウェア エンティティは拡張に対してオープンである必要がありますが、変更のため閉鎖されています)、これは開閉の原理の古典的な定義です。ここでのソフトウェア実体には、プロジェクト内で分割されたモジュール、クラス、インターフェース、メソッドの部分が含まれます。
アプリケーションの要件が変化した場合、ソフトウェア エンティティのソース コードやバイナリ コードを変更することなく、モジュールの機能を拡張して新しい要件を満たすことができます。
2.1.2、開閉原理の役割
オープンとクローズの原理は、オブジェクト指向プログラミングの究極の目標であり、これにより、ソフトウェア エンティティが安定性と継続性だけでなく、一定の適応性と柔軟性を持つことが可能になります。具体的には、次のように動作します。
1) ソフトウェアテストへの影響
ソフトウェアがオープンクローズ原則に準拠している場合、元のテストコードは正常に実行できるため、ソフトウェアテストでは拡張コードのみをテストする必要があります。
2) コードの再利用性を高めることができる
粒度が小さいほど再利用される可能性が高く、オブジェクト指向プログラミングではアトムと抽象化に基づいてプログラミングすることでコードの再利用性を高めることができます。
3) ソフトウェアの保守性を向上できる
オープン・クローズの原理に従ったソフトウェアは安定性が高く、継続性が強いため、拡張や保守が容易です。
2.1.3、開閉原理の実現方法
オープンとクローズの原理は、「抽象制約と変更のカプセル化」によって実現できます。つまり、インターフェイスまたは抽象クラスを通じてソフトウェア エンティティの比較的安定した抽象化層を定義し、同じ可変要素を同じ具象実装クラスにカプセル化します。抽象化は優れた柔軟性と幅広い適応性を備えているため、抽象化が合理的である限り、ソフトウェア アーキテクチャの安定性は基本的に維持されます。ソフトウェアの変更可能な内容は、抽象化から導出された実装クラスから拡張することができ、ソフトウェアに変更が必要な場合には、要件に応じて実装クラスを再導出して拡張するだけで済みます。
以下では、例として Windows のデスクトップ テーマを使用して、開閉の原理の適用を紹介します。
Windows テーマは、デスクトップの背景画像、ウィンドウの色、サウンドなどの要素を組み合わせたものです。ユーザーは好みに応じてデスクトップのテーマを変更したり、インターネットから新しいテーマをダウンロードしたりできます。これらのサブジェクトには共通の特性があり、抽象クラス (Abstract Subject) を定義でき、各特定のサブジェクト (Specific Subject) はそのサブクラスになります。ユーザー フォームは、元のコードを変更することなく、ニーズに応じて新しいテーマを選択または追加できるため、オープンとクローズの原則を満たしており、そのクラス図を図 1 に示します。
Windows デスクトップのテーマのクラス図は次のとおりです。
2.2. リスコフ置換原理
2.2.1. リスコフ置換原理の定義
リスコフ置換原則 (LSP) は、1987 年の「オブジェクト指向技術サミット」(OOPSLA) で MIT コンピューター サイエンス研究所のリスコフ氏によって発表された記事です。 データの抽象化と階層 (データの抽象化と階層) では、彼女は次のように提案しました。継承では、スーパークラスが所有するプロパティがサブクラスでも保持されることを保証する必要があります (継承では、スーパータイプ オブジェクトについて証明されたプロパティがサブタイプ オブジェクトにも保持されることを保証する必要があります)。
リスコフ置換原則は主に、継承に関するいくつかの原則、つまり、継承を使用する必要がある場合、継承を使用すべきでない場合、およびそれに含まれる原則を説明します。Liskov 置換は継承再利用の基礎であり、基本クラスとサブクラスの間の関係を反映し、オープンとクローズの原則を補足し、抽象化を実現するための具体的な手順の仕様です。
2.2.2. リスコフ置換原理の役割
リスコフ置換原理の主な機能は次のとおりです。
1) リスコフ置換原理は、開閉原理を実現するための重要な方法の 1 つです。
2) 継承時の親クラスの書き換えによる再利用性の低さという欠点を克服します。
3) アクションの正確性を保証します。つまり、クラスの拡張によって既存のシステムに新たなエラーが発生することはなく、コード エラーが発生する可能性が低くなります。
2.2.3. リスコフ置換原理の実現方法
一般に、リスコフ置換原則は、サブクラスが親クラスの機能を拡張できるが、親クラスの元の機能を変更することはできないことを意味します。つまり、サブクラスが親クラスを継承する場合、新しい関数を完成させるために新しいメソッドを追加するだけでなく、親クラスのメソッドを書き直さないようにしてください。親クラスのメソッドを書き換えることによって新しい関数が完成する場合、記述は簡単ですが、継承システム全体の再利用性は相対的に低くなり、特にポリモーフィズムが頻繁に使用される場合、プログラムの実行エラーが発生する確率が非常に高くなります。高い。
プログラムがリスコフ置換原則に違反している場合、継承されたクラスのオブジェクトでは、基本クラスが表示される箇所で実行エラーが発生します。このときの修正方法は、元の継承関係を解消し、両者の関係を再設計することになります。
リスコフ置換原理の最も有名な例は、「正方形は長方形ではない」です。もちろん、同様の例は世の中にもたくさんあり、例えばペンギン、ダチョウ、キウイなどは生物学的には鳥類に分類されますが、クラス継承の観点から見ると「鳥」としての機能を継承できないため、飛行することができます。したがって、「bird」のサブクラスとして定義することはできません。同様に、「バルーンフィッシュ」は泳げないので「フィッシュ」のサブクラスとして定義できず、「トイキャノン」は敵を爆破できないため「キャノン」のサブクラスとして定義できません。
リスコフ置換原則を説明するために、「キウイは鳥ではない」を例に挙げてみましょう。
ツバメなどの鳥は通常、時速約 120 キロメートルの速度で飛行します。しかし、ニュージーランドのキウイは羽が退化しているため飛ぶことができません。例を設計したい場合は、これら 2 羽の鳥が 300 キロメートルを飛ぶのにかかる時間を計算してください。明らかに、このコードを Swallow でテストすると、結果は正しく、所要時間も計算できますが、Kiwi でテストすると、結果は「ゼロ除算例外」または「無限大」となり、明らかに期待を満たしません。
関連するクラス図は次のとおりです。
プログラムコードは次のとおりです。
// 鸟类
class Bird
{
public:
void setSpeed(double speed)
{
flySpeed=speed;
}
double getFlyTime(double distance)
{
return(distance/flySpeed);
}
private:
double flySpeed;
};
// 燕子类
class Swallow : public Bird{};
// 几维鸟类
class BrownKiwi : public Bird
{
public:
void setSpeed(double speed)
{
flySpeed=0;
}
};
// 测试代码
Bird* pBird1=new Swallow();
Bird* pBird2=new BrownKiwi();
pBird1->setSpeed(120);
pBird2->setSpeed(120);
std::cout<<"如果飞行300公里:"<<endl;
std::cout<<"燕子将飞行"<<bird1.getFlyTime(300)<<"小时."<<endl;
std::cout<<"几维鸟将飞行"<<bird2.getFlyTime(300)<<"小时。"<<endl;
プログラムを実行した結果は次のようになります。
300 km 飛行する場合:
ツバメは 2.5 時間飛行し、
キウイは無限時間飛行します。
プログラムが誤動作する理由は、kiwi がbird の setSpeed(double Speed) メソッドを書き換えており、これがリスコフ置換原則に違反しているためです。正しい方法は、キーウィの元の継承関係をキャンセルし、鳥とキーウィのより一般的な親クラス (動物クラスなど) を定義することです。これらはどちらも実行する機能があります。キウイの飛行速度は0ですが、走る速度は0ではないので、300キロメートル走るのにかかる時間を計算できます。そのクラス図は次のとおりです。
2.3. 依存性逆転の原則
2.3.1. 依存性の定義が原理につながる
依存関係反転原則 (DIP) は、Object Mentor の社長である Robert C. Martin によって 1996 年に C++ Report に掲載された記事です。
依存関係逆転の原則の本来の定義は、高レベルのモジュールは低レベルのモジュールに依存すべきではなく、両方ともその抽象化に依存すべきであり、抽象化は詳細に依存すべきではなく、詳細は抽象化に依存すべきである(高レベルのモジュールは依存すべきではない)というものです。低レベルのモジュールに依存します。両方とも抽象化に依存する必要があります。抽象化は詳細に依存しないでください。詳細は抽象化に依存する必要があります。) 基本的な考え方は、実装指向のプログラミングではなく、インターフェイス指向のプログラミングを行うことです。
依存関係逆転の原理は、オープンとクローズの原理を実現する重要な方法の 1 つであり、クライアントと実装モジュール間の結合を軽減します。ソフトウェア設計では、詳細は変更可能ですが、抽象化層は比較的安定しているため、抽象化に基づいて構築されたアーキテクチャは、詳細に基づいて構築されたアーキテクチャよりもはるかに安定しているためです。ここでの抽象はインターフェイスまたは抽象クラスを指し、詳細は具体的な実装クラスを指します。インターフェイスまたは抽象クラスを使用する目的は、特定の操作を行わずに仕様と契約を作成し、詳細を提示するタスクをその実装クラスに任せることです。
2.3.2. 依存性の原理の役割
依存関係逆転原理の主な機能は次のとおりです。
1) 依存関係逆転の原理により、クラス間の結合を減らすことができます。
2) 依存性反転原理により、システムの安定性が向上します。
3) 依存関係逆転の原則により、並行開発によって引き起こされるリスクを軽減できます。
4) 依存性逆転の原則により、コードの読みやすさと保守性が向上します。
2.3.3. 依存性反転原則の役割
依存関係反転原則の目的は、インターフェイス指向プログラミングを通じてクラス間の結合を減らすことです。そのため、プロジェクト内でこのルールを満たすには、実際のプログラミングでは次の 4 つのポイントに従うだけで済みます。
1) 各クラスは、インターフェイスまたは抽象クラス、またはその両方を提供しようとします。
2) 変数の宣言型は可能な限りインターフェースまたは抽象クラスにしてください。
3) クラスは具象クラスから派生すべきではありません。
4) 継承を使用する場合は、リスコフ置換原則に従うようにしてください。
2.4、単一責任の原則
2.4.1. 単一責任原則の定義
単一責任原則 (SRP) は、単一機能原則としても知られ、ロバート C. マーティン氏の著書『アジャイル ソフトウェア開発: 原則、パターン、実践』で提案されました。ここでの責任とは、クラス変更の理由を指します。単一責任原則では、クラスには変更の理由が 1 つだけあるべきであり、そうでない場合はクラスが分割される必要があります (クラスの変更理由が複数あってはならない)。 )。
この原則は、オブジェクトがあまりにも多くの責任を引き受けるべきではないことを示しています。オブジェクトがあまりにも多くの責任を引き受けると、少なくとも 2 つの欠点が生じます。
1) 1 つの責任が変更されると、このクラスが他の責任を実行する能力が弱まるか阻害される可能性があります。
2) クライアントがオブジェクトに対して特定の責任を必要とする場合、他のすべての不必要な責任を含める必要があり、その結果、コードが冗長になったり、コードが無駄になったりします。
2.4.2. 単一責任原則の利点
単一責任の原則の中核は、クラスの粒度を制御し、オブジェクトを分離し、それらの凝集性を向上させることです。単一責任の原則に従うと、次のような利点があります。
1) クラスの複雑さを軽減します。クラスは 1 つの責任のみを担当し、そのロジックは複数の責任を負うロジックよりも明らかに単純です。
2) クラスの可読性を向上させます。複雑さが軽減されると、当然、読みやすさも向上します。
3) システムの保守性を向上します。可読性が向上し、当然メンテナンスも容易になります。
4) 変化によるリスクの軽減。単一責任の原則がしっかりと守られていれば、機能が変更された場合、5) 他の機能への影響を大幅に軽減できます。
2.4.3、単一責任原則の実現方法
単一責任の原則は最も単純ですが、適用するのが最も難しい原則であり、設計者はクラスのさまざまな責任を発見して分離し、それらを異なるクラスまたはモジュールにカプセル化する必要があります。クラスの複数の責任を発見するには、設計者に強力な分析能力と設計能力、および関連するリファクタリングの経験が必要です。
以下では、大学生の勤務管理プログラムを例として、単一責任原則の適用を紹介します。
大学生の業務には主に「学生生活指導」と「学業指導」の2つの側面があり、「生活指導」には主に学級委員会の設置、出席統計、心理カウンセリング、授業料徴収、クラス管理などが含まれ、「学業指導」には主に専門指導、学習指導、科学研究が含まれます。指導、学習のまとめ、その他の作業。これらを教師に任せるのは明らかに無理があり、生活相談はカウンセラーが担当し、学習指導は家庭教師が担当するのが正しい考え方である。
大学生勤務管理プログラムのクラス図は以下のとおりです。
注: 単一責任はメソッドにも適用されます。メソッドは 1 つのことをできるだけうまく実行する必要があります。メソッドが処理する処理が多すぎると、その粒度が非常に粗くなり、再利用に役立ちません。
2.5、インターフェース絶縁原理
2.5.1. インターフェース分離原則の定義
インターフェイス分離原則 (Interface Segregation Principle、ISP) では、プログラマは、顧客が関心のあるメソッドのみがインターフェイスに含まれるように、肥大化して巨大なインターフェイスをより小さく、より具体的なインターフェイスに分割することを試みる必要があります。
2002 年、Robert C. Martin は、クライアントが使用していないメソッドに依存することを強制されるべきではないという「インターフェイス分離原則」を定義しました。この原則には別の定義があります。「あるクラスから別のクラスへの依存関係は、可能な限り最小のインターフェイスに依存する必要があります (あるクラスから別のクラスへの依存関係は、可能な限り最小のインターフェイスに依存する必要があります)。」
上記 2 つの定義の意味は、呼び出しに依存するすべてのクラスに対して非常に大きなインターフェイスを作成しようとするのではなく、各クラスに必要な専用インターフェイスを確立することです。インターフェイスの分離と単一責任の原則は、どちらもクラスの結合を改善し、クラス間の結合を減らすことであり、カプセル化の考えを反映していますが、この 2 つは異なります。
1) 単一責任の原則は責任に焦点を当てますが、インターフェイス分離の原則はインターフェイスの依存関係の分離に焦点を当てます。
2) 単一責任の原則は主にクラスを制約することであり、プログラムの実装と詳細を目的としています; インターフェイス分離の原則は主にインターフェイスを制約し、主に抽象化と全体の構築を目的としていますプログラムの枠組み。
2.5.2. インターフェース分離原理の利点
インターフェイス分離の原理は、インターフェイスを制約し、クラスのインターフェイスへの依存を減らすことであり、インターフェイス分離の原理に従うと、次の 5 つの利点があります。
1) 肥大化し巨大なインターフェースを複数の粒度の細かいインターフェースに分解することで、外部変更の拡散を防ぎ、システムの柔軟性と保守性を向上させることができます。
2) インターフェイスの分離により、システムの凝集性が向上し、外部との相互作用が減少し、システムの結合が減少します。
3) インターフェースの粒度が適切に定義されていればシステムの安定性は確保できますが、定義が小さすぎるとインターフェースが増えすぎて設計が複雑になり、定義が大きすぎると柔軟性が損なわれます。が削減され、カスタマイズされたサービスが提供できなくなり、プロジェクト全体に予期せぬリスクがもたらされます。
4) 一般的なインターフェイスの定義はインターフェイスの継承を通じて実現できるため、複数の特殊なインターフェイスの使用はオブジェクトの階層を反映することもできます。
5) プロジェクトエンジニアリングにおけるコードの冗長性を削減できます。通常、過大なインターフェイスには未使用のメソッドが多数配置されるため、このインターフェイスを実装する場合は冗長なコードを設計する必要があります。
2.5.3. インターフェース分離原理の実装方法
界面絶縁原理を具体的に適用する場合は、次の規則に従って測定する必要があります。
1) インターフェースは可能な限り小さくする必要がありますが、制限されています。インターフェイスは 1 つのサブモジュールまたはビジネス ロジックのみを提供します。
2) インターフェースに依存するクラスのサービスをカスタマイズします。呼び出し元が必要とするメソッドのみを提供し、不要なメソッドはブロックします。
3) 環境を理解し、盲目的に従うことを拒否します。各プロジェクトまたは製品には選択された環境要因があり、環境が異なればインターフェイス分割の基準も異なります。ビジネス ロジックの深い理解も必要です。
4) 結束力を向上させ、外部との相互作用を軽減します。インターフェイスが最小限のメソッドで最大限のことを達成できるようにします。
以下では、学生のパフォーマンス管理プログラムを例として使用して、インターフェイス分離原則の適用を紹介します。
学生の成績管理プログラムには、通常、成績の挿入、成績の削除、成績の変更、合計得点の計算、平均得点の計算、成績情報の印刷、成績情報のクエリなどの機能が含まれています。これらすべての機能を 1 つのインターフェイスにまとめるのは明らかに不合理です。最善の方法は、入力モジュール、統計モジュール、印刷モジュールの 3 つのモジュールにそれぞれ入れることです。
学生パフォーマンス管理プログラムのクラス図は次のとおりです。
関連するコードは次のとおりです。
// 输入模块接口
class IInputModule
{
public:
void insert();
void delete();
void modify();
};
// 统计模块接口
class ICountModule
{
public:
void countTotalScore();
void countAverage();
};
// 打印模块接口
class IPrintModule
{
public:
void printStuInfo();
void queryStuInfo();
};
// 实现类
class StuScoreList :public IInputModule, public ICountModule, public IPrintModule
{
private:
StuScoreList(){};
public:
static IInputModule* getInputModule()
{
return (IInputModule*)new StuScoreList();
}
static CountModule getCountModule()
{
return (ICountModule*)new StuScoreList();
}
static IPrintModule getPrintModule()
{
return (IPrintModule*)new StuScoreList();
}
void insert()
{
std::cout<<"输入模块的insert()方法被调用!"<<endl;
}
void delete()
{
std::cout<<"输入模块的delete()方法被调用!"<<endl;
}
void modify()
{
std::cout<<"输入模块的modify()方法被调用!"<<endl;
}
void countTotalScore()
{
std::cout<<"统计模块的countTotalScore()方法被调用!"<<endl;
}
void countAverage()
{
std::cout<<"统计模块的countAverage()方法被调用!"<<endl;
}
void printStuInfo()
{
std::cout<<"打印模块的printStuInfo()方法被调用!"<<endl;
}
void queryStuInfo()
{
std::cout<<"打印模块的queryStuInfo()方法被调用!"<<endl;
}
};
// 测试代码
IInputModule* pInput =StuScoreList::getInputModule();
ICountModule* pIount =StuScoreList::getCountModule();
IPrintModule* pPrint =StuScoreList::getPrintModule();
pInput->insert();
pCount->countTotalScore();
pPrint->printStuInfo();
プログラムを実行した結果は次のようになります。
入力モジュールの insert() メソッドが呼び出されます。
統計モジュールの countTotalScore() メソッドが呼び出されます。
印刷モジュールの printStuInfo() メソッドが呼び出されます。
2.6. デメテルの法則
2.6.1. デメテルの法則の定義
デメテルの法則 (LoD) は、最小知識原則 (LKP) としても知られ、1987 年に米国のノースイースタン大学で行われたデメテルと呼ばれる研究プロジェクトに由来します。イアン ホランドによって提案され、理論の創設者の 1 人であるブーチによって広められました。 UML は、後に古典的な書籍「The Pragmatic Programmer」で言及されたことで広く知られるようになりました。
デメテルの法則の定義は、「見知らぬ人」ではなく、直接の友人とのみ話しなさい(見知らぬ人ではなく、直接の友人とのみ話してください)です。その意味は、2 つのソフトウェア エンティティが直接通信する必要がない場合、直接の相互通話は存在せず、通話はサード パーティによって転送できるということです。その目的は、クラス間の結合の度合いを減らし、モジュールの相対的な独立性を向上させることです。
ディミットの法則における「フレンド」とは、現在のオブジェクト自体、現在のオブジェクトのメンバー オブジェクト、現在のオブジェクトによって作成されたオブジェクト、現在のオブジェクトのメソッド パラメータなどを指します。これらのオブジェクトは、関連付け、集約、または結合されます。現在のオブジェクトに直接アクセスするためのメソッドにすることができます。
2.6.2. デメテルの法則の利点
ディミットの法則では、ソフトウェア エンティティ間の通信の幅と深さを制限する必要があり、ディミットの法則を正しく使用すると、次の 2 つの利点があります。
1) クラス間の結合度を減らし、モジュールの相対的な独立性を向上させます。
2) アフィニティの低下により、クラスの再利用性とシステムの拡張性が向上します。
ただし、ディミットの法則を過度に使用すると、システムが多数の中間クラスを生成するため、システムが複雑になり、モジュール間の通信効率が低下します。したがって、ディミッターの法則を使用する場合は、明確なシステム構造を確保しながら高い凝集性と低い結合性を確保するために、トレードオフを繰り返す必要があります。
2.6.2. デメテルの法則の実装方法
ディミットの法則の定義と特徴から、次の 2 つの点が強調されます。
1)依存する側から見て、依存すべきものだけを依存する。
2) 依存者の観点から、公開すべきメソッドのみを公開します。
したがって、ディミッターの法則を使用する際には、次の 6 つの点に注意する必要があります。
1) クラス分割に関しては、弱結合クラスを作成する必要があります。クラス間の結合が弱いほど、再利用性という目標の達成に役立ちます。
2) クラスの構造設計では、クラス メンバーのアクセス権を減らすようにしてください。
3) クラス設計では、クラスを不変クラスとして設定することを優先します。
4) 他のクラスの参照では、他のオブジェクトへの参照の数を最小限に抑えます。
5) クラスの属性メンバーを公開する代わりに、対応するアクセサー (set メソッドと get メソッド) を提供する必要があります。
6) Serializable 関数は注意して使用してください。
以下は、このルールを説明するための有名人とエージェントの関係の例です。
スターは芸術に専念するため、ファンとの会合やメディア会社との商談など、日常業務の多くはマネージャーが担当する。ここでのエージェントはスターの友人ですが、ファンやメディア企業は他人なので、ディミッターの法則が適しています。
スターとエージェントのクラス図:
関連するコードは次のとおりです。
// 经纪人
class Agent
{
public:
void setStar(Star* pStar)
{
this.pMyStar=pStar;
}
void setFans(Fans* pFans)
{
this.pMyFans=pFans;
}
void setCompany(Company* pCompany)
{
this.pMyCompany=pCompany;
}
void meeting()
{
std::cout<<myFans.getName()<<"与明星"<<myStar.getName()<<"见面了。"<<endl;
}
void business()
{
std::cout<<myCompany.getName()<<"与明星"<<myStar.getName()<<"洽淡业务。"<<endl;
}
private:
Star* pMyStar;
Fans* pMyFans;
Company* pMyCompany;
};
// 明星
class Star
{
public:
Star(const char* pName)
{
this.name=pName;
}
char* getName()
{
return name.c_str();
}
private:
string name;
};
// 粉丝
class Fans
{
public:
Fans(const char* pName)
{
this.name=pName;
}
char* getName()
{
return name.c_str();
}
private:
string name;
};
// 媒体公司
class Company
{
public:
Company(String name)
{
this.name=name;
}
String getName()
{
return name;
}
private:
String name;
};
// 测试代码
Agent agent;
agent.setStar(new Star("张颂文"));
agent.setFans(new Fans("粉丝小李"));
agent.setCompany(new Company("XXXXX公司"));
agent.meeting();
agent.business();
プログラムを実行した結果は次のようになります。
ファン・シャオ・リーさんはスターのチャン・ソンウェンさんに会いました。
XXXXX社は著名人の張松文氏と商談を行った。
2.7. 合成と再利用の原則
2.7.1. 合成再利用原則の定義
複合再利用原則 (CRP) は、複合再利用原則 (CARP) とも呼ばれます。ソフトウェアを再利用する場合は、可能な限り結合または集約を使用してそれを実現し、次に継承関係を使用してそれを実現することを検討する必要があります。
継承関係を使用する場合は、リスコフ置換原則に厳密に従う必要があります。構成再利用原則とリスコフ置換原則は相互に補完しており、どちらもオープニングとクロージング原則の具体的な実装仕様です。
2.7.2. 合成と再利用の原則の重要性
一般にクラスの再利用には継承の再利用と合成の再利用の2種類があり、継承の再利用にはシンプルで実装が容易というメリットがある一方で、次のようなデメリットもあります。
1) 継承の再利用により、クラスのカプセル化が破壊されます。継承によって親クラスの実装の詳細がサブクラスに公開され、親クラスがサブクラスに対して透過的になるため、この種の再利用は「ホワイト ボックス」再利用とも呼ばれます。
2) サブクラスと親クラスの結合度が高い。親クラスの実装を変更すると、サブクラスの実装も変更されるため、クラスの拡張や保守には役立ちません。
3) 多重化の柔軟性が制限されます。親クラスから継承された実装は静的であり、コンパイル時に定義されるため、実行時に変更することはできません。
組み合わせまたは集約再利用を使用すると、既存のオブジェクトを新しいオブジェクトに組み込んで新しいオブジェクトの一部にすることができ、新しいオブジェクトは既存のオブジェクトの関数を呼び出すことができます。これには次の利点があります。
1) クラスのカプセル化を維持します。構成オブジェクトの内部の詳細は新しいオブジェクトには見えないため、このタイプの再利用は「ブラック ボックス」再利用とも呼ばれます。
2)新旧クラス間の結合度が低い。この再利用に必要な依存関係は少なくなり、新しいオブジェクトが構成オブジェクトにアクセスする唯一の方法は、構成オブジェクトのインターフェイスを経由することになります。
3)多重化の自由度が高い。この再利用は実行時に動的に行うことができ、新しいオブジェクトは構成オブジェクトと同じタイプのオブジェクトを動的に参照できます。
2.7.3、複合再利用原理の実現方法
複合再利用の原理は、既存のオブジェクトを新しいオブジェクトのメンバオブジェクトとして組み込むことで実現され、新しいオブジェクトは既存のオブジェクトの関数を呼び出して再利用することができます。以下では、自動車分類管理プログラムを例として、合成と再利用の原則の適用を紹介します。
車は「動力源」によってガソリン車、電気自動車などに分けられ、「色」によって白い車、黒い車、赤い車などに分けられます。これら 2 つの分類を同時に考慮すると、多くの組み合わせが存在します。
継承関係によって実装される自動車分類のクラス図:
上図から、継承関係を実装することで多くのサブクラスが生成されることが分かりますが、新たに「電源」を追加したり、新たに「色」を追加するにはソースコードの修正が必要となり、開閉の原則に違反します。 、明らかにお勧めできません。
しかし、代わりに組み合わせ関係を使用すると、上記の問題はうまく解決でき、組み合わせ関係によって実現される車の分類のクラス図は次のようになります。
3. 最後に
ソフトウェア設計の上記 7 つの原則を一言で要約すると、次のようになります。
設計原則 | 一文の要約 | 目的 |
開閉原理 | 拡張の場合はオープン、変更の場合はクローズ | メンテナンスによる新たなリスクを軽減 |
依存関係逆転の原則 | 上位層は下位層に依存すべきではありません | コード構造のアップグレードと拡張がより容易になります。 |
単一責任の原則 | クラスは 1 つのことだけを行います | 理解しやすく、コードの可読性が向上します |
インターフェース分離原理 | インターフェースが行うことは 1 つだけです | 機能的デカップリング、高集約、低カップリング |
デメテルの法則 | 知るべきではない、知らない | 友人とのみ通信し、デフォルトの人とは話さないようにし、コードの肥大化を軽減します |
リーの置換ルール | サブクラスのオーバーライドされたメソッドの機能が変更されますが、親クラスのメソッドの意味には影響しません。 | 継承の洪水を防ぐ |
合成再利用の原則 | 継承の代わりに合成を使用してコードを再利用してみてください。 | コードの結合を減らす |