ナビゲーション:
目次
3.2 インターフェイス分離の原則 (Interface Segregation Principle)
3.3 Dependence Inversion Principle (依存性逆転原理)
1. 面接の定番質問で要点をつかむ
試作パターン
- 1) UMLクラス図を使ってプロトタイプモードの核となる役割を描いてください
- 2)プロトタイプ設計モードのディープ コピーとシャロー コピーとは何か、ディープ コピーの 2 つの方法のソース コードを記述します (クローン メソッドを書き換えてディープ コピーを実現し、シリアル化を使用してディープ コピーを実現します)。
- 3) Spring フレームワークでプロトタイプ モードを使用する場所、およびソース コードを分析する
<bean id="id01"class="com.atguigu.spring.bean.Monster" scope="prototype"/>
- 4) Spring でのプロトタイプ Bean の作成は、プロトタイプ モードのアプリケーションです。
- 5) コード解析+ソースコードのデバッグ
デザイン パターンの 7 つの原則
- 1) 単一責任の原則
- 2) インターフェースアイソレーションの原理
- 3) 依存性逆転の原則
- 4) リスコフ置換原理
- 5) オープンクローズド原則 (OCP)
- 6) デメテルの法則
- 7) 合成再利用の原則
必須:
- 1) 設計7原則の核となる考え方
- 2) クラス図で設計原則を説明できる
- 3) プロジェクトの実際の開発において、OCP 原則をどこで使用しましたか?
状態モード
金融融資プラットフォーム プロジェクト: 融資プラットフォームの注文には、レビュー、リリース、注文の取得などの手順があります. 注文のステータスは、さまざまな操作で変化します. プロジェクトでのこのモジュールの実装では、ステータス モデルを使用します。ステータス パターンを使用し、実際のコードを完成させる
問題分析: この種のコードは変更に対応するのが難しい. 状態を追加する場合は手動で if/else を追加する必要がある. 関数を追加する場合はすべての状態を判断する必要がある. そのため、コードはどんどん肥大化し、ある状態が処理されなくなると、非常に深刻なバグが発生し、維持することが困難になります。
通訳者の設計パターン
- 1) インタプリタの設計パターンとは何かを紹介してください。
- 2) インタプリタ設計パターンの UML クラス図を描き、設計パターンにおける役割を分析してください。
- 3) インタープリターの設計パターンが Spring フレームワークのどこで使用されているかを説明し、ソース コード レベルの分析を行ってください。
シングルトンの設計パターン
シングルトン設計パターンにはいくつの実装がありますか? コードを使用して実現し、各実装の利点と本当のポイントを説明してください。
- 1) 2種類のハングリーチャイニーズスタイル
- 2) 怠惰なスタイルの 3 種類
- 3) ダブルチェック
- 4) 静的内部クラス
- 5) 列挙
2. デザインパターンの目的と基本原則
重要性:
- 設計パターンは、ソフトウェア設計における遍在する問題に対する解決策です。
- プロジェクトの開発が完了した後、プロジェクトを維持し(読みやすさ、標準化)、新しい機能を追加するのに便利です。
- 実際のプロジェクトでどのようなデザイン パターンをどのように使用しましたか? それはどのような問題を解決しましたか?
- デザインパターンは、プロジェクトの機能モジュールとフレームワークで使用されます。
デザインパターンの目的:
ソフトウェアを作成する過程で、プログラマーは結合、結束、保守性、スケーラビリティ、再利用性、柔軟性などの課題に直面します。
設計パターンは、プログラム (ソフトウェア) がより良いパフォーマンスを発揮できるように設計されています。
- 1) 再利用性(つまり、同じ機能を持つコードを複数回記述する必要がないこと。コードの再利用性とも呼ばれます)
- 2) 読みやすさ(つまり、プログラミングの規範、他のプログラマーが読みやすく理解しやすい)
- 3)スケーラビリティ(つまり、新しい機能を追加する必要がある場合、非常に便利です。保守性とも呼ばれます)
- 4) 信頼性(つまり、新しい機能を追加しても、元の機能には影響しません)
- 5) プログラムに高凝集性と低結合性の特徴を提示させる
設計原則の核となる考え方
- 1) アプリケーションで変更が必要な可能性があるものを見つけて、それらを分離し、変更する必要のないコードと混ぜないでください。
- 2)実装ではなく、インターフェイスにプログラムする
- 3)インタラクティブなオブジェクト間の疎結合設計に努める
デザインパターンにはオブジェクト指向の本質が込められており、「デザインパターンを理解すれば、オブジェクト指向の分析と設計(OOA/D)の本質が理解できる」
3. デザインパターンの7原則
一般的に使用される 7 つの原則は次のとおりです。
- 1) 単一責任の原則
- 2) インターフェースアイソレーションの原理
- 3) 依存性逆転の原則
- 4) リスコフ置換原理
- 5) 開閉の原理
- 6) デメテルの法則
- 7) 合成再利用の原則
3.1 単一責任の原則
クラスの場合、つまり、クラスは 1 つの責任のみを担当する必要があります。
たとえば、user テーブルは、ユーザー関連の情報のみを格納します。たとえば、クラス A は、責任 1 と責任 2 という 2 つの異なる責任を負います。責任 1 の要件が変更され、A が変更されると、責任 2 の実行エラーが発生する可能性があるため、クラス A の粒度を A1、A2 に分解する必要があります。
注意事項と詳細
- 1)クラスの複雑さを減らします。クラスは1つの責任のみを担当します
- 2) クラスの可読性と保守性を向上させる
- 3) 変更によるリスクを軽減する
- 4) 通常の状況では, 単一責任の原則に従うべき. ロジックが十分に単純な場合にのみ, コードレベルで単一責任の原則に違反することができる. クラス内のメソッドの数が十分に少ない場合にのみ,単一責任の原則をメソッドレベルで維持する
場合:
シナリオ 1 (単一原則の違反)、メソッド内の条件付き判断、シナリオの区別:
public class SingleResponsibility1 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("汽车"); vehicle.run("轮船"); vehicle.run("飞机"); } } /** * 方式1的分析 * 1.在方式1的run方法中,违反了单一职责原则 * 2.解决的方案非常的简单,根据交通工具运行方法不同,分解成不同类即可 */ class Vehicle{ public void run(String type){ if ("汽车".equals(type)) { System.out.println(type + "在公路上运行..."); } else if ("轮船".equals(type)) { System.out.println(type + "在水面上运行..."); } else if ("飞机".equals(type)) { System.out.println(type + "在天空上运行..."); } } }
シナリオ 2 (単一の責任):さまざまなクラス、さまざまなシナリオ:
public class SingleResponsibility2 { public static void main(String[] args) { RoadVehicle roadVehicle = new RoadVehicle(); roadVehicle.run("汽车"); WaterVehicle waterVehicle = new WaterVehicle(); waterVehicle.run("轮船"); AirVehicle airVehicle = new AirVehicle(); airVehicle.run("飞机"); } } /** * 方案2的分析 * 1.遵守单一职责原则 * 2.但是这样做的改动很大,即将类分解,同时修改客户端 * 3.改进:直接修改Vehicle类,改动的代码会比较少=>方案3 */ class RoadVehicle{ public void run(String type){ System.out.println(type + "在公路上运行..."); } } class WaterVehicle{ public void run(String type){ System.out.println(type + "在水面上运行..."); } } class AirVehicle{ public void run(String type){ System.out.println(type + "在天空上运行..."); } }
シナリオ 3 (メソッド レベルの単一責任): さまざまなメソッド、さまざまなシナリオ:
public class SingleResponsibility3 { public static void main(String[] args) { Vehicle2 vehicle = new Vehicle2(); vehicle.run("汽车"); vehicle.runWater("轮船"); vehicle.runAir("飞机"); } } /** * 方式3的分析 * 1.这种修改方法没有对原来的类做大的修改,只是增加方法 * 2.这里虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责 */ class Vehicle2{ public void run(String type){ System.out.println(type + "在公路上运行..."); } public void runWater(String type){ System.out.println(type + "在水面上运行..."); } public void runAir(String type){ System.out.println(type + "在天空上运行..."); } }
3.2 インターフェイス分離の原則 (Interface Segregation Principle)
クライアントは、必要のないインターフェースに依存すべきではありません。つまり、クラスの別のクラスへの依存は、最小のインターフェースで確立する必要があります。
Interface Segregation Principle (Interface Segregation Principle、ISP) は SOLID の設計原則であり、「クライアントは、使用していないメソッドに依存することを強制されるべきではない」、つまり、クラスが依存することを強制されるべきではないと定義されています。必要のないインターフェイス。
インターフェース分離の原則の主な目的は、大きく肥大化したインターフェースをより小さく、より具体的なインターフェースに分割して、クライアントが必要に応じて必要な特定のインターフェースを選択できるようにすることです。これにより、不要なインターフェースへのクライアントの依存が大幅に減少し、システムがより柔軟になり、保守が容易になり、拡張が容易になります。
典型的な例は、インターフェイスを使用する必要がある場合、通常はインターフェイスのすべてのメソッドを実装する必要がありますが、実際には一部のメソッドしか使用できない場合があります。このインターフェイスに多くのメソッドが含まれていると、コードが冗長になり、実装クラスに過度に依存するようになります。
合理的なインターフェイスの分割と組み合わせにより、インターフェイスをより合理化し、コードの再利用性とスケーラビリティを向上させることができます。同時に、コードの保守性を向上させ、コード変更のリスクと保守コストを削減することにも役立ちます。
インターフェースは動作を記述する抽象化であり、分離の目的は、インターフェースの数をより複雑にするのではなく、インターフェースが抽象的な動作をより適切に記述できるようにすることであることに注意してください。したがって、インターフェイスの分離を適度に行い、特定の状況に応じて選択および分割する必要があります。
インターフェース分離の原則に従わないケース:
- クラス A はインターフェイス Interface1 を介してクラス B に依存し、クラス C はインターフェイス Interface1 を介してクラス D に依存します。実装する必要があります)、クラス B とクラス D は不要なメソッドを実装する必要があります。
- 分離の原則に従って、次のように処理する必要があります。インターフェイス Interface1はいくつかの独立したインターフェイスに分割され、クラス A とクラス C はそれぞれ必要なインターフェイスとの依存関係を確立します。つまり、界面分離の原理
違法に分離されたコード:
interface Interface1 { void operation1(); void operation2(); void operation3(); void operation4(); void operation5(); } class B implements Interface1 { @Override public void operation1() { System.out.println("B 实现了 operation1"); } @Override public void operation2() { System.out.println("B 实现了 operation2"); } @Override public void operation3() { System.out.println("B 实现了 operation3"); } @Override public void operation4() { System.out.println("B 实现了 operation4"); } @Override public void operation5() { System.out.println("B 实现了 operation5"); } } class D implements Interface1 { @Override public void operation1() { System.out.println("D 实现了 operation1"); } @Override public void operation2() { System.out.println("D 实现了 operation2"); } @Override public void operation3() { System.out.println("D 实现了 operation3"); } @Override public void operation4() { System.out.println("D 实现了 operation4"); } @Override public void operation5() { System.out.println("D 实现了 operation5"); } } /** * A类通过接口Interface1依赖(使用)B类,但是只会用到1,2,3方法 */ class A { public void depend1(Interface1 i) { i.operation1(); } public void depend2(Interface1 i) { i.operation2(); } public void depend3(Interface1 i) { i.operation3(); } } /** * C类通过接口Interface1依赖(使用)D类,但是只会用到1,4,5方法 */ class C { public void depend1(Interface1 i) { i.operation1(); } public void depend4(Interface1 i) { i.operation4(); } public void depend5(Interface1 i) { i.operation5(); } }
インターフェイスを分割した後のコード:
interface Interface1 { void operation1(); } interface Interface2 { void operation2(); void operation3(); } interface Interface3 { void operation4(); void operation5(); } class B implements Interface1, Interface2 { @Override public void operation1() { System.out.println("B 实现了 operation1"); } @Override public void operation2() { System.out.println("B 实现了 operation2"); } @Override public void operation3() { System.out.println("B 实现了 operation3"); } } class D implements Interface1, Interface3 { @Override public void operation1() { System.out.println("D 实现了 operation1"); } @Override public void operation4() { System.out.println("D 实现了 operation4"); } @Override public void operation5() { System.out.println("D 实现了 operation5"); } } /** * A类通过接口Interface1,Interface2依赖(使用)B类,但是只会用到1,2,3方法 */ class A { public void depend1(Interface1 i) { i.operation1(); } public void depend2(Interface2 i) { i.operation2(); } public void depend3(Interface2 i) { i.operation3(); } } /** * C类通过接口Interface1,Interface3依赖(使用)D类,但是只会用到1,4,5方法 */ class C { public void depend1(Interface1 i) { i.operation1(); } public void depend4(Interface3 i) { i.operation4(); } public void depend5(Interface3 i) { i.operation5(); } }
3.3 Dependence Inversion Principle (依存性逆転原理)
3.3.1 はじめに
- 1) 高レベル モジュールは低レベル モジュールに依存してはならず、両方ともその抽象化(インターフェースまたは抽象クラス)に依存する必要があります。
- 2) 抽象化は詳細に依存すべきではなく、詳細は抽象化に依存すべきである
- 3) 依存関係の逆転 (インバージョン) の中心的な考え方は、インターフェイス指向のプログラミングです
- 4) 依存関係反転の原則は、設計コンセプトに基づいています。詳細の可変性と比較して、抽象的なものははるかに安定しています。抽象化に基づくアーキテクチャは、詳細に基づくアーキテクチャよりもはるかに安定しています。Javaでは、抽象化はインターフェースまたは抽象クラスを指し、詳細は具体的な実装クラスを指します
- 5) インターフェースや抽象クラスを使用する目的は、特定の操作を伴わずに仕様を策定し、詳細を示す作業を実装クラスに任せることです。
- ポリモーフィズムは、依存性逆転の原則を実装する方法の 1 つです。
ケース:ユーザー クラスは、電子メール、WeChat、およびその他の情報を受け取ります。
依存関係の逆転の違反: 抽象の代わりに具体的な参照:
/** * 方式1分析 * 1.简单,比较容易想到 * 2.如果我们获取的对象是微信,短信等等,则新增类,同时 Peron也要增加相应的接收方法 * 3.解决思路: * 引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖 * 因为Email,Weixin等等属于接收的范围,他们各自实现IReceiver接口就ok,这样我们就符号依赖倒转原则 */ class Email { public String getInfo() { return "电子邮件信息:Hello World!"; } } class Person { public void receive(Email email) { System.out.println(email.getInfo()); } }
改善: 抽象化を参照するポリモーフィックな方法:
interface IReceiver { String getInfo(); } class Email implements IReceiver { @Override public String getInfo() { return "电子邮件信息:Hello World!"; } } class Weixin implements IReceiver { @Override public String getInfo() { return "微信消息:Hello World!"; } } class ShortMessage implements IReceiver { @Override public String getInfo() { return "短信信息:Hello World!"; } } class Person { public void receive(IReceiver receiver) { System.out.println(receiver.getInfo()); } }
3.3.2 依存性移転の 3 つの方法
- 1)プログラムの安定性を高めるために、低レベルのモジュールには抽象クラスまたはインターフェース、あるいはその両方が必要です。
- 2) 変数の宣言型は、可能な限り抽象クラスまたはインターフェイスにする必要があります。これにより、変数参照と実際のオブジェクトの間にバッファー層があり、プログラムの拡張と最適化に役立ちます。
- 3) 継承時はリスコフ置換原理に従う
テレビを切り替える場合:
1) インターフェース転送
ITV インターフェイスは、IOpenAndClose インターフェイスの共通メソッド パラメータです。
//方式1:通过接口传递实现依赖 //开关的接口 interface IOpenAndClose { void open(ITV tv);//抽象方法,接收接口 } //ITV接口 interface ITV { void play(); } //实现接口 class OpenAndClose implements IOpenAndClose { public void open(ITV tv){ tv.play(); } }
2) 工法移管
ITV インターフェイスは、OpenAndClose クラスのメンバ変数およびコンストラクタ パラメータです。
//方式2:通过构造函数实现依赖 //开关的接口 interface IOpenAndClose { void open();//抽象方法 } //ITV接口 interface ITV { public void play(); } //实现接口 class OpenAndClose implements IOpenAndClose { private ITV tv; // 成员 public OpenAndClose(ITV tv){ // 构造器 this.tv = tv; } public void open(){ this.tv.play(); } }
3) セッターによる転送
ITV インターフェイスは、OpenAndClose クラスのメンバ変数およびセッター メソッド パラメータです。
//方式3,通过setter方法传递 interface IOpenAndClose{ void open();//抽象方法 void setTv(ITV tv); } //ITV接口 interface ITV{ void play(); } //实现接口 class OpenAndClose implements IOpenAndClose{ private ITV tv; public void setTv(ITV tv){ this.tv=tv; } public void open(){ this.tv.play(); } }
3.4 リスコフ置換原理
Facing Object OO における継承の考え方と説明
- 1) 継承にはそのような層の意味が含まれます: 親クラスで実装されたすべてのメソッドは実際には仕様と契約を設定しています. すべてのサブクラスがこれらの契約に従うことを強制するわけではありませんが, サブクラスがこれらの契約を既に実装している場合.実装されたメソッドが継承システム全体に損害を与える
- 2) 継承はプログラム設計に便利な反面、デメリットももたらします。たとえば、継承の使用はプログラムへの侵入をもたらし、プログラムの移植性を低下させ、オブジェクト間の結合を増加させます. クラスが他のクラスによって継承される場合、このクラスを変更する必要がある場合、すべてのサブクラスを考慮する必要があります. 、および親クラスが変更された後、サブクラスに関連するすべての関数が失敗する可能性があります
- 3) 疑問が生じます: プログラミングで継承を正しく使用するには? =>リスコフ置換原理
基本紹介
- 1) リスコフ置換原理は、1988 年に MIT の李さんによって提案されました。
- 2)親タイプのオブジェクトがサブタイプのオブジェクトに置き換えられた後、関数は変更されません。タイプ T1 のすべてのオブジェクト o1 に対して、タイプ T2 のオブジェクト o2 が存在する場合、T1 によって定義されたすべてのプログラム P は、すべてのオブジェクト o1 o2 になってもプログラム P の動作は変わらず、型 T2 は型 T1 のサブタイプです。つまり、基本クラスを参照するすべての場所で、そのサブクラスのオブジェクトを透過的に使用できる必要があります。
- 3) 継承を使用する場合は、リスコフ置換の原則に従い、親クラスのメソッドをサブクラスでオーバーライドしないようにします
- 4) Liskov 置換原理は、継承が実際に 2 つのクラスの結合を強化し、適切な状況下では、集約、構成、および依存関係によって問題を解決できることを示しています。
場合:
従来の解決策:サブクラスは、親クラスの減算メソッドを加算メソッドとして書き換えます。継承システム全体の再利用性は比較的低くなります。
public void test() { A a = new A(); System.out.println("11-3=" + a.func1(11, 3)); System.out.println("1-8=" + a.func1(1, 8)); System.out.println("---------------------"); B b = new B(); System.out.println("11-3=" + b.func1(11, 3)); System.out.println("1-8=" + b.func1(1, 8)); System.out.println("11+3+9=" + b.func2(11, 3)); } class A { //返回两个数的差 public int func1(int num1, int num2) { return num1 - num2; } } class B extends A { @Override public int func1(int num1, int num2) { return num1 + num2; } //增加了一个新功能:完成两个数相加,然后和9求和 public int func2(int num1, int num2) { return func1(num1, num2) + 9; } }
改善案:元の親クラスとサブクラスの両方がより一般的な基底クラスを継承し、元の継承関係が削除され、代わりに依存関係、集約、結合などの関係が使用されます
//创建一个更加基础的基类 class Base { //将更基础的成员和方法写到Base类中 } class A extends Base { //返回两个数的差 public int func1(int num1, int num2) { return num1 - num2; } } class B extends Base { //如果B需要使用A类的方法,使用组合关系 private A a; public int func1(int num1, int num2) { return num1 + num2; } //增加了一个新功能:完成两个数相加,然后和9求和 public int func2(int num1, int num2) { return func1(num1, num2) + 9; } public int func3(int num1, int num2) { return this.a.func1(num1, num2); } }
3.5 オープンクローズの原則
オン:拡張機能を使用できます。
休館日:変更のため休館。
- 1) 開閉の原則は、プログラミングにおける最も基本的かつ重要な設計原則です。
- 2) クラス、モジュール、関数などのソフトウェア エンティティは、拡張用に(プロバイダーに対して) 開いており、変更に対して(ユーザーに対して) 閉じている必要があります。抽象化でフレームワークを構築し、実装で詳細を拡張します。新しい機能を追加した後、元のコードは変更されていません。
- 3) ソフトウェアを変更する必要がある場合は、既存のコードを変更するのではなく、ソフトウェア エンティティの動作を拡張することによって変更を達成するようにします。
- 4) プログラミングの他の原則に従います。デザイン パターンを使用する目的は、開閉の原則に従うことです。
場合:
オプション 1: 従来のソリューション
グラフィックを描画する機能であるクラス図は、次のように設計されています。
class GraphicEditor { public void drawShape(Shape s) { if (s.m_type == 1) { drawRectangle(s); } else if (s.m_type == 2) { drawCircle(s); } else if (s.m_type == 3) { drawTriangle(s); } } public void drawRectangle(Shape r) { System.out.println("矩形"); } public void drawCircle(Shape r) { System.out.println("圆形"); } public void drawTriangle(Shape r) { System.out.println("三角形"); } } class Shape { public int m_type; } class RectangleShape extends Shape { RectangleShape() { m_type = 1; } } class CircleShape extends Shape { CircleShape() { m_type = 2; } } class TriangleShape extends Shape { TriangleShape() { m_type = 3; } }
方法 1 の長所と短所
- 1) 分かりやすくシンプルで操作しやすいのがメリット
- 2) 欠点は、設計パターンの OCP 原則に違反していること、つまり、拡張に対してオープン(プロバイダー) で、変更に対してクローズ(ユーザー) であることです。つまり、新しい関数をクラスに追加するときは、コードを変更しないようにするか、コードをできるだけ変更しないようにします。
- 3) たとえば、この時点で新しいグラフィック タイプを追加する場合、次の変更を行う必要があり、多くの変更があります 4) コードのデモ
モード1の改善案の分析
作成した Shape クラスを抽象クラスにし、サブクラスが実装するための抽象描画メソッドを提供します
このように、新しいタイプのグラフィックスがある場合、新しいグラフィックス クラスに Shape を継承させてdraw メソッドを実装させるだけで済みます。
ユーザーのコードを変更する必要はなく、開閉の原則は満たされています。
方法 2 オープンとクローズの原則:描画関数は基本クラスの抽象メソッドとして設定され、新しい実装クラスは基本クラスを継承して描画の抽象メソッドを実装するだけで済みます。
class GraphicEditor { public void drawShape(Shape s) { s.draw(); } } abstract class Shape { int m_type; public abstract void draw(); } class RectangleShape extends Shape { RectangleShape() { m_type = 1; } @Override public void draw() { System.out.println("矩形"); } } class CircleShape extends Shape { CircleShape() { m_type = 2; } @Override public void draw() { System.out.println("圆形"); } } class TriangleShape extends Shape { TriangleShape() { m_type = 3; } @Override public void draw() { System.out.println("三角形"); } }
3.6 デメテルの原理
基本紹介
- 1) オブジェクトは、他のオブジェクトに関する最小限の知識を保持する必要があります
- 2) クラスとクラスの関係が近いほど、結合度が高くなる
- 3) ディミットの法則は最小知識の原則とも呼ばれます。つまり、クラスが依存するクラスについて知っていることが少ないほど良いということです。つまり、依存するクラスがどれほど複雑であっても、ロジックをクラス内にカプセル化するようにしてください。提供された公開方法を除き、情報が外部に公開されることはありません
- 4) デメテルの法則のより単純な定義もあります:直接の友人とのみ通信します。
- 5) 直接の友人: 各オブジェクトは他のオブジェクトとカップリング関係にあり、2 つのオブジェクト間にカップリング関係がある限り、2 つのオブジェクトは友人であると言えます。結合には、依存関係、関連付け、構成、集約など、さまざまな方法があります。このうち、メンバー変数、メソッドのパラメータ、メソッドの戻り値に現れるクラスを直接の友と呼びますが、ローカル変数に現れるクラスは直接の友人ではありません。つまり、なじみのないクラスはローカル変数の形でクラス内に現れないのが最善です。
注意事項と詳細
- メイン クラス A では、クラス B がメソッドの直接のフレンドであり、クラス A のすべてのメソッド ローカル変数のクラス B が直接のフレンドです。
- 1)ディミットの法則の核心は、クラス間の結合を減らすことです
- 2) ただし、注意: クラスごとに不要な依存関係が削減されるため、ディミットの法則は、クラス間 (オブジェクト間) の結合関係を削減することのみを要求し、依存関係はまったく必要としません。
ケース
従来のソリューション:
1) その下に様々な大学や本部がある学校があり、現在は学校本部の職員のIDと大学の職員のIDをプリントアウトすることが義務付けられています
//总部员工 class Employee { private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } } //学院员工 class CollegeEmployee { private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } } //学院员工管理 类 class CollegeManager { public List<CollegeEmployee> getAllEmployee() { List<CollegeEmployee> list = new ArrayList<>(); //是直接朋友 CollegeEmployee collegeEmployee; //是直接朋友 for (int i = 0; i < 10; i++) { collegeEmployee = new CollegeEmployee(); collegeEmployee.setId("学院员工id=" + i); list.add(collegeEmployee); } return list; } } //总部员工管理类 class SchoolManager { public List<Employee> getAllEmployee() { //仅出现成员变量,方法参数,方法返回值中的类为直接的朋友 List<Employee> list = new ArrayList<>(); //Employee 是直接朋友,出现在返回值 Employee employee; //是直接朋友 for (int i = 0; i < 5; i++) { employee = new Employee(); employee.setId("总部员工id=" + i); list.add(employee); } return list; } public void printAllEmployee(CollegeManager sub) { System.out.println("--------------学院员工--------------"); List<CollegeEmployee> list1 = sub.getAllEmployee(); //不是直接朋友,出现在局部变量 for (CollegeEmployee collegeEmployee : list1) { System.out.println(collegeEmployee.getId()); } System.out.println("---------------总部员工-------------"); List<Employee> list2 = this.getAllEmployee(); for (Employee employee : list2) { System.out.println(employee.getId()); } } }
応用例の改善
- 1) 以前の設計の問題は、SchoolManager では、CollegeEmployee クラスが SchoolManager クラスの直接の友人ではないことです (分析)
- 2) ディミットの法則によると、このようなクラス内での間接的な友人関係結合は避けるべきです。
- 3) ディミットの法則に従ってコードを改善し、ローカル オブジェクト変数をパラメーターにカプセル化します。
//学院员工管理类 class CollegeManager { public List<CollegeEmployee> getAllEmployee() { List<CollegeEmployee> list = new ArrayList<>(); //是直接朋友 CollegeEmployee collegeEmployee; //是直接朋友 for (int i = 0; i < 10; i++) { collegeEmployee = new CollegeEmployee(); collegeEmployee.setId("学院员工id=" + i); list.add(collegeEmployee); } return list; } //改进,新增方法,输出学院员工信息 public void printEmployee(){ System.out.println("--------------学院员工--------------"); //CollegeEmployee是直接朋友,出现在上面getAllEmployee()方法的返回值 List<CollegeEmployee> list1 = getAllEmployee(); for (CollegeEmployee collegeEmployee : list1) { System.out.println(collegeEmployee.getId()); } } } //总部员工管理类 class SchoolManager { public List<Employee> getAllEmployee() { //仅出现成员变量,方法参数,方法返回值中的类为直接的朋友 List<Employee> list = new ArrayList<>(); //Employee 是直接朋友,出现在返回值 Employee employee; //是直接朋友 for (int i = 0; i < 5; i++) { employee = new Employee(); employee.setId("总部员工id=" + i); list.add(employee); } return list; } public void printAllEmployee(CollegeManager sub) { sub.printEmployee(); //改进,降低耦合,不用非直接朋友。 System.out.println("---------------总部员工-------------"); List<Employee> list2 = this.getAllEmployee(); for (Employee employee : list2) { System.out.println(employee.getId()); } } }
3.7 複合再利用の原則
基本紹介
原則は、継承の代わりに合成/集約を使用しようとすることです
つまり、使用する必要があるクラスは、このクラスのパラメーター、メンバー変数、およびローカル変数として使用されます。
-
依存性:あるオブジェクトが別のオブジェクトを使用する状況を指します。通常、別のオブジェクトがオブジェクトのメソッドでパラメータとして渡されるか、別のオブジェクトのインスタンスがメソッドで作成されます。依存関係は、依存オブジェクトが不要になったときに解放できる「短命」の参照関係です。
-
構成: 2 つ以上のオブジェクト間の包含および包含関係を指します。含まれるオブジェクトは全体であり、含まれるオブジェクトはコンポーネントです. それらのライフサイクルは一貫しており、単独では存在できません. たとえば、車はエンジン、ホイール、シャーシなどで構成されており、これらのコンポーネントは、組み合わされた車全体と同じライフサイクルを持っています。全体が死ぬとき、すべての部分も一緒に死ぬ。
-
Aggregation ( Aggregation ):含まれるオブジェクトと含まれるオブジェクトの関係を指し、含まれるオブジェクトは複数の含まれるオブジェクト間に存在できます。集約関係では、含まれるオブジェクトは含まれるオブジェクトから独立して存在でき、ライフサイクルは必ずしも同じではありません。たとえば、大学は学部、大学、図書館などで構成されています。これらの部分は独立して存在することも、異なる大学に属することもできます。たとえ大学全体が死んでも、大学は存続することができます。
結論として、依存関係、構成、および集約は、オブジェクト指向プログラミングでオブジェクトの関係を記述する上で重要な概念であり、優れたスケーラビリティと保守性を備えたアプリケーションを設計および実装するのに役立ちます。
ケース
クラス B がクラス A のメソッドを使用したい場合、それが直接継承される場合、結合が改善され、クラス A が変更された後にクラス B が変更される必要があります。
解決:
- クラス A をクラス B の通常のメソッドの仮パラメータとして使用します。
- クラス A をクラス B のメンバー変数として使用し、setter メソッドを使用する
- クラス B の通常のメソッドでクラス A のオブジェクトを作成します。
4. UML クラス図:統一モデリング言語
- 1) UML - 統一モデリング言語 UML ( Unified Modeling Language ) は、ソフトウェア システムの分析と設計のための言語ツールであり、ソフトウェア開発者が考え、考えた結果を記録するのに使用されます。
- 2) UML 自体は、数学記号や化学記号と同様に、一連の記号です。これらの記号は、ソフトウェア モデル内のさまざまな要素と、それらの間の関係 (クラス、インターフェイス、実装、一般化、依存関係、構成など) を記述するために使用されます。、集計など
- 3) UML を使用してモデル化します。一般的に使用されるツールは Rational Rose です。一部のプラグインを使用してモデル化することもできます。
UML クラス図
- 1) システム内のクラス (オブジェクト) 自体の構成と、クラス (オブジェクト) 間のさまざまな静的関係を記述するために使用されます。
- 2) クラス間の関係: 依存関係、一般化 (継承)、実装、関連付け、集約、合成
4.1、クラス図 - 依存関係 (依存関係)
クラス内で相互に使用されている限り、それらの間には依存関係があります。相手がいなければ編纂すら通用しない
- 1) 相手が授業で使われている
- 2) クラスのメンバー属性
- 3) メソッドの戻り値の型
- 4) メソッドが受け取ったパラメータの型
- 5) メソッドで使用
A が B を使用すると、A は B に依存します。つまり、A------->B:
4.2、クラス図 - 一般化 (一般化)
汎化関係は実際には継承関係であり、依存関係の特殊なケースです。
A が B から継承し、次に A→B
4.3、クラス図 - 実装 (実装)
実装関係は、実際にはクラス A がクラス B を実装するというものであり、これは依存関係の特殊なケースです。
A が B を実装し、次に A➟ B
4.4、クラス図 - 関連付け (Association)
関連関係は、実際にはクラス間の接続であり、依存関係の特殊なケースです。
- 関連付けはナビゲート可能です: 双方向または単方向
- 関係には複数性があります: "1" (1 つだけ存在することを意味する)、"0..." (0 以上を意味する)、"0,1" (0 または 1 つを意味する)、"n.. .m " (n ~ m を使用できることを意味する)、"m...*" (少なくとも m を意味する)
一方向の 1 対 1 の関係
public class Person {
private IDCard card;
}
public class IDCard {}
UML クラス図
双方向の 1 対 1 の関係
public class Person {
private IDCard card;
}
public class IDCard {
private Person person;
}
UML クラス図
4.5、クラス図 - 集計 (集計)
集約関係は、全体と部分の関係を表し、全体と部分を分離することができます。集約関係は関連関係の特殊なケースであるため、関連付けられたナビゲーションと多重度があります。
B が A のインスタンス化されていないメンバー変数である場合、B——◇A
たとえば、コンピューターはキーボード、モニター、マウスなどで構成されており、コンピューターを構成する各アクセサリはコンピューターから分離することができ、中空の菱形の実線で表されます。
public class Mouse {}
public class Monitor {}
public class Computer {
private Mouse mouse;
private Monitor monitor;
public void setMouse(Mouse mouse) {
this.mouse = mouse;
}
public void setMonitor(Monitor monitor) {
this.monitor = monitor;
}
}
UML クラス図
4.6、クラス図 - コンポジション (コンポジション)
結合関係も全体と部分の関係であるが、全体と部分は切り離せない。
B が A のインスタンス化されたメンバ変数である場合、B<——◇A
マウス、モニター、およびコンピューターが不可分であると考える場合は、複合関係にアップグレードしてください
public class Mouse {}
public class Monitor {}
public class Computer {
private Mouse mouse = new Mouse();
private Monitor monitor = new Monitor();
}
UML クラス図
別のケースを見てみましょう. プログラムではエンティティを定義します: Person, IDCard, and Head. 次に、Head と Person は組み合わせであり、IDCard と Person は集計です。
public class IDCard{}
public class Head{}
public class Person{
private IDCard card;
private Head head = new Head();
}
UML クラス図
ただし、プログラムで Person エンティティが IDCard のカスケード削除を定義している場合、つまり、Person を削除するときに、IDCard と一緒に削除され、IDCard と Person が結合されます。