#このチュートリアルの動作環境: Windows7 システム、Java8 バージョン、DELL G3 コンピューター。 デザインパターンに関しては、昔からデザインパターンの本を何冊も読んでおり、中には何度か読んだこともあり、コーディングの際にこのデザインパターンを使えるようになりたいと常々思っていました。ただし、日常のコーディングではシングルトンが最も多く使用され、次にオブザーバー パターンとビルダー パターン (ビルダー) が頻繁に使用され、その他はほとんど使用されません。 これらが使用されない理由は、デザイン パターンの考え方がまだ理解できず、そのデザイン パターンとコーディングで遭遇する問題を結び付けることができないため、デザイン パターンを使用できないためです。 実際、デザイン パターンは一般的な問題を解決するために提案されています。したがって、どのデザイン パターンを採用するかを考えるときは、まず現在の問題は何なのかを自問する必要があります。問題に基づいて適切な設計パターンを選択します。 デザイン パターンに慣れると、いくつかのデザイン パターン間には包括的な関係があり、それらは非常によく似ていますが、異なるデザイン パターンは異なる問題を解決することがわかります。 モジュールを設計するときは、次の観点から検討できます:デザイン パターンの 6 つの原則: 1. 単一責任の原則、その核となるのは、クラスの粒度を制御し、オブジェクトを分離し、それらの凝集性を向上させることです。2. 開始および終了の原則。 「抽象制約」、「カプセル化の変更」を通じて達成される; 3. リスコフ置換原則は主に継承に関するいくつかの原則を説明する; 4. 依存関係逆転原則はクライアントと実装モジュール間の結合を軽減する; 5. インターフェース分離原則は次のとおりであるインターフェイスを制約する、インターフェイスに対するクラスの依存性を減らす; 6. デメテルの法則では、ソフトウェア エンティティ間の通信の幅と深さを制限する必要があります。
このモジュールと他のモジュールとの関係は何ですか?
モジュールのどの部分が変更されておらず、どの部分が常に変更されており、どのように変更されていますか?
#クラス間の関係は何ですか?なぜクラスに依存する必要があるのですか?どうすればクラスに依存しないことができるのでしょうか?
# インターフェースを追加しますか?インターフェイスはどのような問題を解決するために存在するのでしょうか?
単一責任の原則 (クラス、メソッド、インターフェイス)
開始と終了の原則 (拡張の場合はオープン、変更の場合はクローズ)
##ディミッターの法則(クラス間の近接関係)
#各設計原則の横に括弧があり、適用範囲を説明または説明しています。それぞれの原理については以下で詳しく説明します。
<br>
単一責任原則の中核は、クラスの粒度: オブジェクトを分離し、その結合度を向上させます。単一責任の原則に従うと、次のような利点があります。
クラスの複雑さを軽減します。クラスが 1 つの責任のみを担当する場合、そのロジックは、複数の責任を担当する場合よりも明らかに単純になります。
システムの保守性を向上します。可読性が向上すると、当然保守も容易になります。
#変更によるリスクの軽減。変化は避けられないものであり、単一責任の原則がしっかり守られていれば、1 つの機能が変更された場合でも、他の機能への影響を大幅に軽減できます。
#単一責任の原則を実装する方法
単一責任の原則は最も単純ですが、最も難しいですこの原則を適用するには、設計者がクラスのさまざまな役割を発見して分離し、それらをさまざまなクラスまたはモジュールにカプセル化する必要があります。ディスカバリー クラスには複数の責任があるため、設計者には強力な分析能力と設計能力、および関連するリファクタリングの経験が必要です。Example
public interface UserService { public void login(String username, String password); public void register(String email, String username, String password); public void logError(String msg); public void sendEmail(String email); }
このコードには明らかに大きな問題があります。UserService はユーザー登録とログインの両方を担当します。また、ログの記録と電子メールの送信を担当しており、後者の動作は前者とは明らかに異なります。 <br>
電子メール送信ロジックを変更すると仮定すると、このクラスを変更する必要があります。このとき、QA はログインおよび登録ロジックに戻る必要があります。明らかに不合理です。
UserService: ログインと登録のみを担当します
public interface UserService { public void login(String username, String password); public void register(String email, String username, String password); }
LogService: はログのみを担当します<br>
public interface LogService { public void logError(String msg); }
EmailService: メールの送信のみを担当します
public interface EmailService { public void sendEmail(String email); }
この時点で、上記の利点を確認すると、深く理解できます。
loadImage(String url, ImageView view) { // 下载图片,展示图片 }loadImage メソッドの場合、パラメーターの URL は問題ありません。 , ただし、パラメータImageView しかし、それは無理があります。ここでは、画像のダウンロードと画像の表示という 2 つの操作が行われるためです。全体のロジックが明確になるように、このメソッドは
// 下载图片 loadImage(String url) { } // 显示图片 displayImage(String url, ImageView view) { // 调用 getBitmap (url) 获取图片 // 获取图片后将其设置到 view 中。 } // 根据 url 获取图片, getBitmap(String url) { }のように分割する必要があります。後でダウンロード ロジックを変更する必要がある場合でも、表示ロジックには影響しません。もちろん、実際には別の疑問があります。これら 2 つのメソッドをクラスに配置する必要があるのでしょうか?
<br>
抽象化は優れた柔軟性と幅広い適応性を備えているため、抽象化が合理的である限り、ソフトウェア アーキテクチャの安定性は基本的に維持されます。ソフトウェア内の揮発性の詳細は、抽象化から派生した実装クラスから拡張でき、ソフトウェアを変更する必要がある場合、要件に従って実装クラスを再派生するだけで拡張できます。例
// 矩形 public class Rectangle { public double getWidth() { return width; } public double getHeight() { return height; } }
長方形の面積を計算する必要があります
// 面积计算器 public class AreaCalculator { public double area(Rectangle shape){ return shape.getWidth() * shape.getHeight(); } }このとき、別の円形クラス
// 圆形 public class Circular { public double getRadius(){ return radius; } }
があり、その面積も計算する必要があるとします。これ:
public class AreaCalculator { public double area(Object shape){ if(shape instanceof Rectangle) { Rectangle rectangle = (Rectangle) shape; return rectangle.getWidth() * rectangle.getHeight(); } else if (shape instanceof Circular) { Circular circular = (Circular) shape; return circular.getRadius() * circular.getRadius() * Math.PI; } else { throw new RuntimeException("There is no such type."); } } }この変更は完了しました。まったく問題ありません。ただし、実際の運用環境では状況はさらに複雑で、変更には多くの部分が関係し、全体に影響を及ぼす可能性があります。また、以前に作成およびテストされた一部の関数は再テストする必要があり、一部の関数が使用できなくなる場合もあります。 面積計算の公開ロジックをインターフェースに変える改良版:
public interface Shape { public double getArea(); } public class Rectangle implements Shape{ public double getWidth() { return width; } public double getHeight() { return height; } public double getArea() { return getWidth() * getHeight(); } }
このようにして、要件が変化し、円形面積を計算する必要がある場合に、計算するには、循環クラスを作成し、Shape インターフェイスを実装するだけです:<br>
public class Circular implements Shape { public double getRadius(){ return radius; } public double getArea() { return getRadius() * getRadius() * Math.PI; } }
三角形や四角形の面積を計算する場合…ソースコードを変更せずにShapeインターフェースを実装させるだけで済みます。
リヒター置換原則では、主に継承に関するいくつかの原則、つまり、継承をいつ使用するか、いつ何を使用するかを説明します。継承とその基礎となる原則を使用すべきではありません。 Liskov 置換はもともと継承再利用の基礎であり、基底クラスとサブクラス間の関係を反映し、開始と終了の原則を補足し、抽象化を達成するための特定の手順を規定します。 <br>
リスコフ置換原理の役割
リスコフ置換原理の主な機能は次のとおりです。
リスコフ置換原理は、開閉原理を実現する重要な方法の 1 つです。
#これにより、継承で親クラスをオーバーライドすることによって引き起こされる再利用性の低下という欠点が克服されます。
#アクションの正確性を保証するものです。つまり、クラスの拡張によって既存のシステムに新しいエラーが導入されることはなく、コード エラーの可能性が減ります。
プログラムの堅牢性を強化し、変更時に非常に優れた互換性を実現し、プログラムの保守性と拡張性を向上させ、要件の変更に必要な時間を短縮します。 . リスクが導入されました。
リスコフ置換原則 (継承) の実装方法
わかりやすく言えば、リスコフ置換原則 つまり、サブクラスは親クラスの機能を拡張できますが、親クラスの元の機能を変更することはできません。言い換えると、サブクラスが親クラスを継承する場合、新しい関数を完成させるために新しいメソッドを追加する場合を除き、親クラスのメソッドをオーバーライドしないようにしてください。
上記の理解に基づいて、リスコフ置換原則の定義は次のように要約できます:
サブクラスは親クラスの抽象メソッドを実装できます。クラスの非抽象メソッド
# サブクラスは独自の一意のメソッドを追加できます
サブクラスのメソッドが親クラスのメソッドをオーバーライドする場合、メソッドの前提条件 (メソッドの入力パラメーター) は親クラスのメソッドよりも緩くなります
サブクラスのメソッドが親クラスのメソッドを実装する場合 (上書き/オーバーロード、または抽象メソッドの実装)、メソッドの事後条件 (つまり、出力/戻り値)
<br>
ソフトウェア設計では、詳細は変更可能ですが、抽象化層は比較的安定しているため、抽象化に基づいて構築されたアーキテクチャは、詳細に基づいて構築されたアーキテクチャよりもはるかに安定しているためです。ここでの抽象はインターフェイスまたは抽象クラスを指し、詳細は特定の実装クラスを指します。 インターフェイスまたは抽象クラスを使用する目的は、特定の操作を行わずに仕様と規約を作成し、詳細を示すタスクをその実装クラスに任せることです。依存性と反転原理の機能
依存性反転原理の主な機能は次のとおりです。依存関係逆転の原則により、クラス間の結合を減らすことができます。
#依存関係逆転の原理により、システムの安定性が向上します。
#依存関係逆転の原則により、並列開発によって引き起こされるリスクを軽減できます。
#依存関係逆転の原則により、コードの可読性と保守性が向上します。
依赖倒置原则的实现方法
依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下4点,就能在项目中满足这个规则。
每个类尽量提供接口或抽象类,或者两者都具备。
变量的声明类型尽量是接口或者是抽象类。
任何类都不应该从具体类派生。
使用继承时尽量遵循里氏替换原则。
依赖倒置原则在“顾客购物程序”中的应用。
分析:本程序反映了 “顾客类”与“商店类”的关系。商店类中有 sell() 方法,顾客类通过该方法购物以下代码定义了顾客类通过韶关网店 ShaoguanShop 购物
class Customer { public void shopping(ShaoguanShop shop) { //购物 System.out.println(shop.sell()); } }
但是,这种设计存在缺点,如果该顾客想从另外一家商店(如婺源网店 WuyuanShop)购物,就要将该顾客的代码修改如下:
class Customer { public void shopping(WuyuanShop shop) { //购物 System.out.println(shop.sell()); } }
顾客每更换一家商店,都要修改一次代码,这明显违背了开闭原则。
存在以上缺点的原因是:顾客类设计时同具体的商店类绑定了,这违背了依赖倒置原则。
解决方法是:定义“婺源网店”和“韶关网店”的共同接口 Shop,顾客类面向该接口编程,其代码修改如下:
class Customer { public void shopping(Shop shop) { //购物 System.out.println(shop.sell()); } } class Customer { public void shopping(Shop shop) { //购物 System.out.println(shop.sell()); } }
这样,不管顾客类 Customer 访问什么商店,或者增加新的商店,都不需要修改原有代码了,其类如下图所示:<br><br><br>
package principle; public class DIPtest { public static void main(String[] args) { Customer wang=new Customer(); System.out.println("顾客购买以下商品:"); wang.shopping(new ShaoguanShop()); wang.shopping(new WuyuanShop()); } } //商店 interface Shop { public String sell(); //卖 } //韶关网店 class ShaoguanShop implements Shop { public String sell() { return "韶关土特产:香菇、木耳……"; } } //婺源网店 class WuyuanShop implements Shop { public String sell() { return "婺源土特产:绿茶、酒糟鱼……"; } } //顾客 class Customer { public void shopping(Shop shop) { //购物 System.out.println(shop.sell()); } }
程序的运行结果如下:
顾客购买以下商品: 韶关土特产:香菇、木耳…… 婺源土特产:绿茶、酒糟鱼……
インターフェイス分離原則 (ISP) では、プログラマーは、肥大化したインターフェイスをより小さなインターフェイスに分割する必要があります。 , そのため、インターフェイスには顧客が関心のあるメソッドのみが含まれます。 <br>
2002 年、Robert C. Martin は「インターフェイス分離原則」を次のように定義しました: クライアントは、使用していないメソッドに依存することを強制されるべきではありません。この原則には別の定義があります。あるクラスの別のクラスへの依存関係は、可能な限り最小のインターフェイスに依存する必要があります。
上記の 2 つの定義の意味は、呼び出しに依存するすべてのクラスに対して巨大なインターフェイスを構築しようとするのではなく、各クラスに必要な専用インターフェイスを確立することです。
インターフェイス分離の原則と単一責任は、どちらもクラスの凝集性を向上させ、クラス間の結合を軽減することであり、カプセル化の概念を具体化していますが、この 2 つは異なります:
単一責任原則は責任に焦点を当てますが、インターフェイス分離原則はインターフェイスの依存関係の分離に焦点を当てます。
単一責任原則は主にプログラムの実装と詳細を目的としたクラスを制約し、インターフェイス分離原則は主に抽象化とインターフェイスを制約します。プログラム全体のフレームワークの構築。
#インターフェイス分離原則の利点
インターフェイス分離原則は、インターフェイスを制約し、クラスを削減することです。インターフェイス分離の原則に従ったインターフェイスへの依存関係には、次の 5 つの利点があります。肥大化したインターフェースを複数の粒度の小さなインターフェースに分解すると、外部変更の拡散を防ぎ、システムの柔軟性と保守性を向上させることができます。
インターフェイスの分離により、システムの凝集性が向上し、外部との相互作用が減少し、システムの結合が減少します。
インターフェースの粒度が適切に定義されていれば、システムの安定性は保証できますが、定義が小さすぎると、システムの安定性が保証されなくなります。インターフェイスが多く設計が複雑になり、定義が大きすぎると柔軟性が低下し、カスタマイズされたサービスが提供できなくなり、プロジェクト全体に予期せぬリスクがもたらされます。
インターフェース全体の定義はインターフェースの継承を通じて実現できるため、複数の特殊なインターフェースを使用すると、オブジェクト階層を反映することもできます。
# プロジェクト エンジニアリングにおけるコードの冗長性を削減できます。通常、大きすぎるインターフェイスには未使用のメソッドが多数含まれているため、このインターフェイスを実装する場合は冗長なコードを設計する必要があります。
#インターフェイス分離原則の実装方法インターフェイス分離原則を具体的に適用する場合は、次のとおりです。測定するためのいくつかのルールに従う必要があります。
public interface UserService { public void login(String username, String password); public void register(String email, String username, String password); public void logError(String msg); public void sendEmail(String email); }
这时候,应该就能理解拆分的好处了。
迪米特法则(Law of Demeter,LoD)又叫作最少知识原则(Least Knowledge Principle,LKP),产生于 1987 年美国东北大学(Northeastern University)的一个名为迪米特(Demeter)的研究项目,由伊恩·荷兰(Ian Holland)提出,被 UML 创始者之一的布奇(Booch)普及,后来又因为在经典著作《程序员修炼之道》(The Pragmatic Programmer)提及而广为人知。<br>
迪米特法则的定义是:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。
迪米特法则的优点
迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点。
降低了类之间的耦合度,提高了模块的相对独立性。
由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
迪米特法则的实现方法
从迪米特法则的定义和特点可知,它强调以下两点:
从依赖者的角度来说,只依赖应该依赖的对象。
从被依赖者的角度说,只暴露应该暴露的方法。
所以,在运用迪米特法则时要注意以下 6 点。
在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
在类的结构设计上,尽量降低类成员的访问权限。
在类的设计上,优先考虑将一个类设置成不变类。
在对其他类的引用上,将引用其他对象的次数降到最低。
不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
谨慎使用序列化(Serializable)功能
明星与经纪人的关系实例。
分析:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如下图所示。<br><br><br>
package principle; public class LoDtest { public static void main(String[] args) { Agent agent=new Agent(); agent.setStar(new Star("林心如")); agent.setFans(new Fans("粉丝韩丞")); agent.setCompany(new Company("中国传媒有限公司")); agent.meeting(); agent.business(); } } //经纪人 class Agent { private Star myStar; private Fans myFans; private Company myCompany; public void setStar(Star myStar) { this.myStar=myStar; } public void setFans(Fans myFans) { this.myFans=myFans; } public void setCompany(Company myCompany) { this.myCompany=myCompany; } public void meeting() { System.out.println(myFans.getName()+"与明星"+myStar.getName()+"见面了。"); } public void business() { System.out.println(myCompany.getName()+"与明星"+myStar.getName()+"洽淡业务。"); } } //明星 class Star { private String name; Star(String name) { this.name=name; } public String getName() { return name; } } //粉丝 class Fans { private String name; Fans(String name) { this.name=name; } public String getName() { return name; } } //媒体公司 class Company { private String name; Company(String name) { this.name=name; } public String getName() { return name; } }
程序的运行结果如下:
粉丝韩丞与明星林心如见面了。 中国传媒有限公司与明星林心如洽淡业务。
到此,设计模式的六大原则就讲完了。
更多编程相关知识,请访问:编程教学!!
以上がデザインパターンの6つの原則とは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。