Heim  >  Artikel  >  Java  >  Was sind die sechs Prinzipien von Designmustern?

Was sind die sechs Prinzipien von Designmustern?

青灯夜游
青灯夜游Original
2023-01-06 16:25:0619005Durchsuche

Die sechs Prinzipien von Entwurfsmustern: 1. Das Prinzip der Einzelverantwortung, dessen Kern darin besteht, die Granularität von Klassen zu kontrollieren, Objekte zu entkoppeln und ihren Zusammenhalt zu verbessern; 2. Das Prinzip des Öffnens und Schließens, das erreicht werden kann durch „ abstrakte Einschränkungen und Kapselungsänderungen „Zu erreichen; 3. Das Liskov-Substitutionsprinzip erklärt hauptsächlich einige Prinzipien der Vererbung; 4. Das Abhängigkeitsinversionsprinzip reduziert die Kopplung zwischen Clients und Implementierungsmodulen; 5. Das Schnittstellenisolationsprinzip besteht darin, Schnittstellen einzuschränken und Klassen zu reduzieren Paare. Abhängigkeiten von Schnittstellen 6. Das Demeter-Gesetz erfordert eine Begrenzung der Breite und Tiefe der Kommunikation zwischen Software-Entitäten.

Was sind die sechs Prinzipien von Designmustern?

Die Betriebsumgebung dieses Tutorials: Windows7-System, Java8-Version, DELL G3-Computer.

In Bezug auf Designmuster habe ich vor langer Zeit viele Designmusterbücher gelesen und einige davon mehrmals gelesen. Ich habe immer gehofft, dass ich diese Designmuster beim Codieren verwenden kann. Bei der täglichen Codierung wird jedoch am häufigsten der Singleton verwendet, gefolgt von den Beobachter- und Builder-Mustern (Builder), die häufiger verwendet werden, und die anderen werden seltener verwendet.

Der Grund, warum wir sie nicht verwenden können, ist, dass wir die Idee von Designmustern immer noch nicht verstehen und diese Designmuster nicht mit den beim Codieren auftretenden Problemen in Verbindung bringen können, sodass wir Designmuster nicht verwenden können.

Tatsächlich werden Entwurfsmuster vorgeschlagen, um ein häufiges Problem zu lösen. Wenn Sie also darüber nachdenken, welches Designmuster Sie übernehmen möchten, sollten Sie sich zunächst fragen, wo das aktuelle Problem liegt. Wählen Sie je nach Problem das passende Entwurfsmuster aus.

Nachdem Sie sich mit Designmustern vertraut gemacht haben, werden Sie feststellen, dass zwischen einigen Designmustern eine umfassende Beziehung besteht und sie sogar sehr ähnlich sind, aber unterschiedliche Designmuster unterschiedliche Probleme lösen.

Wenn wir ein Modul entwerfen, können wir es aus folgenden Perspektiven betrachten:

  • Welche Beziehung besteht zwischen diesem Modul und anderen Modulen?

  • Welche Teile des Moduls bleiben unverändert, welche Teile ändern sich ständig und wie?

  • Wie ist die Beziehung zwischen den Klassen? Warum müssen wir uns darauf verlassen?

  • Möchten Sie eine Schnittstelle hinzufügen? Welches Problem soll die Schnittstelle lösen?

Natürlich lernen Sie in diesem Artikel nicht, wie Sie Designmuster verwenden. Es erklärt vielmehr die Designprinzipien von Designmustern. Auch Entwurfsmuster folgen bei der Gestaltung einigen Regeln.

Sechs Prinzipien von Entwurfsmustern, insbesondere wie folgt:

  • Einzelverantwortungsprinzip (Klassen und Methoden, Schnittstellen)

  • Öffnungs- und Schließprinzip (offen für Erweiterung, geschlossen für Änderung)

  • Richter-Ersetzungsprinzip (Beziehung zwischen Basisklassen und Unterklassen)

  • Abhängigkeitsinversionsprinzip (verlassen Sie sich auf abstrakte Schnittstellen statt auf konkrete Objekte)

  • Schnittstellenisolationsprinzip (Schnittstellen werden nach Funktionen unterteilt)

  • Demeter-Gesetz (die enge Beziehung zwischen Klassen)

Neben jedem Gestaltungsprinzip befindet sich eine Klammer, die zur Erläuterung bzw. Beschreibung des Anwendungsbereichs dient. Jedes Prinzip wird im Folgenden ausführlich beschrieben.

Definition des Single-Responsibility-Prinzips (Klasse, Methode, Schnittstelle)


Das Single-Responsibility-Prinzip (SRP) wird auch als Single-Function-Prinzip bezeichnet. Die Verantwortung bezieht sich hier auf den Grund für die Änderung der Klasse. Das Prinzip der Einzelverantwortung legt fest, dass eine Klasse nur einen Grund für ihre Änderung haben sollte, andernfalls sollte die Klasse aufgeteilt werden (Es sollte nie mehr als einen Grund für eine Klasse geben). ändern).

Dieses Prinzip besagt, dass Objekte nicht zu viele Verantwortlichkeiten übernehmen sollten. Wenn ein Objekt zu viele Verantwortlichkeiten übernimmt, gibt es mindestens die folgenden zwei Nachteile:

  • Änderungen in einer Verantwortung können diese Klasse schwächen oder hemmen Fähigkeit zur Implementierung anderer Verantwortlichkeiten;

  • Vorteile des Prinzips der Einzelverantwortung

Der Kern des Prinzips der Einzelverantwortung besteht darin, die Granularität von Klassen zu steuern, Objekte zu entkoppeln und ihren Zusammenhalt zu verbessern. Wenn Sie dem Prinzip der Einzelverantwortung folgen, haben Sie folgende Vorteile.

Reduzieren Sie die Komplexität des Kurses. Wenn eine Klasse nur für eine Verantwortlichkeit verantwortlich ist, ist ihre Logik definitiv viel einfacher, als wenn sie für mehrere Verantwortlichkeiten verantwortlich ist.

  • Verbessern Sie die Lesbarkeit von Klassen. Die Komplexität wird reduziert und die Lesbarkeit natürlich verbessert.

  • Verbessern Sie die Wartbarkeit des Systems. Eine verbesserte Lesbarkeit erleichtert natürlich die Wartung.

  • Risikominderung durch Veränderungen. Änderungen sind unvermeidlich, und wenn das Prinzip der Einzelverantwortung gut befolgt wird, können bei der Änderung einer Funktion die Auswirkungen auf andere Funktionen erheblich reduziert werden.

So implementieren Sie das Prinzip der Einzelverantwortung

Das Prinzip der Einzelverantwortung ist das einfachste, aber am schwierigsten anzuwendende Prinzip. Es erfordert, dass Designer die verschiedenen Verantwortlichkeiten einer Klasse entdecken, sie trennen und dann zusammenfassen sie in verschiedene Klassen oder Module einteilen. Die vielfältigen Verantwortlichkeiten von Discovery-Klassen erfordern von Designern ausgeprägte Analyse- und Designfähigkeiten sowie einschlägige Refactoring-Erfahrung.

Beispiel

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);
    
}

Dieser Code ist offensichtlich nicht nur für die Benutzerregistrierung und -anmeldung verantwortlich, sondern auch für die Protokollaufzeichnung und den E-Mail-Versand Das Verhalten der letzteren unterscheidet sich erheblich von dem der ersteren. <br>这段代码很显然存在很大的问题,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);

}

这时候,咱们再去回顾前面提到的优点,就能深深体会了。

这里只是讲了接口,其实对类也一样,甚至方法也是一样的。

对于类来说,根据类名,确保里面提供的方法都是属于这个类的。

对于方法,不要把不相关的对象实例作为参数传进来。如果你发现某个方法依赖某个不相关的对象,那么这个方法的实现可能就存在问题。

比如 android 中图片下载后显示到 imageView 中,我提供如下的方法:

loadImage(String url, ImageView view) {
// 下载图片,展示图片
}

对于 loadImage 这个方法,参数 url 是ok 的,但是参数 ImageView 却是不合理的。因为这里做了两个操作,下载图片,展示图片。应该将这个方法在进行拆分:

// 下载图片 
loadImage(String url) {

}
// 显示图片
displayImage(String url, ImageView view) {

// 调用 getBitmap (url)  获取图片
// 获取图片后将其设置到 view 中。

}

// 根据 url 获取图片, 
getBitmap(String url) {

}

这样整个逻辑就很清晰。后续需要修改下载逻辑,也不会影响到展示逻辑。当然其实还有个问题是,这两个方法要不要放在一个类里面?

开闭原则


开闭原则的实现方法:可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。

因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。

示例

// 矩形
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>

Angenommen, ich möchte die Logik des E-Mail-Versands ändern, muss ich diese Klasse zu diesem Zeitpunkt ändern Anmelde- und Registrierungslogik, die offensichtlich unvernünftig ist.
Wir müssen es also entsprechend den spezifischen Funktionen aufteilen, es kann wie folgt aufgeteilt werden: 🎜🎜🎜UserService🎜: nur für Anmeldung und Registrierung verantwortlich🎜🎜
public class Circular implements Shape {

    public double getRadius(){
        return radius;
    }

    public double getArea() {
        return getRadius() * getRadius() * Math.PI;
    }
}
🎜🎜🎜LogService: 🎜Nur verantwortlich für die Protokollierung<br>🎜🎜<pre class="brush:js;toolbar:false;">class Customer { public void shopping(ShaoguanShop shop) { //购物 System.out.println(shop.sell()); } }</pre>🎜🎜🎜EmailService🎜: Nur verantwortlich für das Versenden von E-Mails🎜🎜<pre class="brush:js;toolbar:false;">class Customer { public void shopping(WuyuanShop shop) { //购物 System.out.println(shop.sell()); } }</pre>🎜🎜<code class="java language-java"> Lassen Sie uns zu diesem Zeitpunkt die oben genannten Vorteile noch einmal durchgehen und Sie werden es tiefgreifend verstehen. 🎜🎜Ich habe hier nur über Schnittstellen gesprochen. Tatsächlich ist es dasselbe für Klassen, und sogar die Methoden sind gleich. 🎜🎜Stellen Sie bei Klassen basierend auf dem Klassennamen sicher, dass alle darin bereitgestellten Methoden zu dieser Klasse gehören. 🎜🎜Übergeben Sie bei Methoden keine irrelevanten Objektinstanzen als Parameter. Wenn Sie feststellen, dass eine Methode von einem nicht verwandten Objekt abhängt, liegt möglicherweise ein Problem mit der Implementierung der Methode vor. 🎜🎜Nachdem ich beispielsweise Bilder in Android heruntergeladen und in imageView angezeigt habe, stelle ich die folgende Methode bereit: 🎜🎜
class Customer {
    public void shopping(Shop shop) {
        //购物
        System.out.println(shop.sell());
    }
}

class Customer {
    public void shopping(Shop shop) {
        //购物
        System.out.println(shop.sell());
    }
}
🎜🎜Für die Methode „loadImage“ ist die Parameter-URL in Ordnung, aber der Parameter „ImageView“ ist unangemessen. Denn hier werden zwei Vorgänge ausgeführt: das Herunterladen von Bildern und das Anzeigen von Bildern. Diese Methode sollte aufgeteilt werden: 🎜🎜
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()); 
    }
}
🎜🎜 damit die gesamte Logik klar ist. Wenn Sie die Download-Logik später ändern müssen, hat dies keine Auswirkungen auf die Anzeigelogik. Natürlich gibt es tatsächlich noch eine andere Frage: Sollten diese beiden Methoden in eine Klasse eingeordnet werden? 🎜

🎜Öffnungs- und Schließprinzip🎜


🎜So implementieren Sie das Öffnungs- und Schließprinzip: Das Öffnungs- und Schließprinzip kann durch „abstrakte Einschränkungen und Kapselungsänderungen“ realisiert werden, also durch Definieren von Software Entitäten über Schnittstellen oder abstrakte Klassen Eine relativ stabile Abstraktionsschicht, die dieselben variablen Faktoren in derselben konkreten Implementierungsklasse kapselt.
🎜🎜Da die Abstraktion eine gute Flexibilität und eine breite Anpassungsfähigkeit aufweist, kann die Stabilität der Softwarearchitektur grundsätzlich aufrechterhalten werden, solange die Abstraktion angemessen ist. Die flüchtigen Details in der Software können aus der aus der Abstraktion abgeleiteten Implementierungsklasse erweitert werden. Wenn die Software geändert werden muss, müssen Sie lediglich eine Implementierungsklasse entsprechend den Anforderungen zur Erweiterung neu ableiten. 🎜🎜🎜Beispiel🎜🎜🎜
顾客购买以下商品:
韶关土特产:香菇、木耳……
婺源土特产:绿茶、酒糟鱼……
🎜🎜Die Fläche eines Rechtecks ​​muss berechnet werden🎜🎜
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);
    
}
🎜🎜Angenommen, zu diesem Zeitpunkt gibt es eine andere Kreisklasse 🎜🎜
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;
    }
}
🎜 🎜Es ist auch notwendig, die Fläche zu berechnen. Zu diesem Zeitpunkt sieht es so aus:🎜🎜
粉丝韩丞与明星林心如见面了。
中国传媒有限公司与明星林心如洽淡业务。
🎜🎜Nachdem diese Änderung abgeschlossen ist ist überhaupt kein Problem. In einer realen Produktionsumgebung ist die Situation jedoch komplizierter und Änderungen betreffen viele Teile, die sich auf den gesamten Körper auswirken können. Außerdem müssen einige zuvor geschriebene und getestete Funktionen erneut getestet werden, was sogar dazu führt, dass einige Funktionen nicht mehr verfügbar sind. 🎜🎜Verbesserte Version, die die öffentliche Logik zur Flächenberechnung in eine Schnittstelle umwandelt: 🎜🎜rrreee🎜🎜Wenn sich die Anforderungen ändern und wir die kreisförmige Fläche berechnen müssen, können wir auf diese Weise Sie müssen lediglich eine zirkuläre Klasse erstellen und die Shape-Schnittstelle implementieren:<br>🎜🎜rrreee🎜

计算三角形面积、四边形面积... 的时候,我们只需让它们去实现 Shape 接口即可,无需修改源代码。

Das Richter-Substitutionsprinzip


Das Richter-Substitutionsprinzip erklärt hauptsächlich einige Prinzipien zur Vererbung, das heißt, wann Vererbung genutzt werden sollte, wann Vererbung nicht genutzt werden sollte und welche Prinzipien ihr zugrunde liegen. Die Liskov-Substitution ist ursprünglich die Grundlage für die Wiederverwendung der Vererbung. Sie spiegelt die Beziehung zwischen Basisklassen und Unterklassen wider, ergänzt das Öffnungs- und Schließprinzip und regelt die spezifischen Schritte zur Erzielung einer Abstraktion.

Die Rolle des Richter-Substitutionsprinzips

Die Hauptfunktionen des Richter-Substitutionsprinzips sind wie folgt.

  • Das Richter-Substitutionsprinzip ist eine der wichtigen Möglichkeiten, das Eröffnungs- und Schlussprinzip zu verwirklichen.

  • Es überwindet die Mängel der schlechten Wiederverwendbarkeit, die durch das Überschreiben übergeordneter Klassen bei der Vererbung verursacht werden.

  • Es ist die Garantie für die Richtigkeit der Aktion. Das heißt, durch die Erweiterung der Klasse werden keine neuen Fehler in das bestehende System eingeführt, wodurch die Möglichkeit von Codefehlern verringert wird.

  • Stärken Sie die Robustheit des Programms und erreichen Sie gleichzeitig eine sehr gute Kompatibilität bei Änderungen, verbessern Sie die Wartbarkeit und Skalierbarkeit des Programms und reduzieren Sie die Risiken, die bei sich ändernden Anforderungen entstehen.

So implementieren Sie das Richter-Substitutionsprinzip (Vererbung)

Das Richter-Substitutionsprinzip lautet im Allgemeinen: Unterklassen können die Funktionen der übergeordneten Klasse erweitern, jedoch nicht die ursprünglichen Funktionen der übergeordneten Klasse ändern. Mit anderen Worten: Wenn eine Unterklasse eine übergeordnete Klasse erbt, versuchen Sie, die Methoden der übergeordneten Klasse nicht zu überschreiben, außer durch das Hinzufügen neuer Methoden zur Vervollständigung neuer Funktionen.

Basierend auf dem obigen Verständnis kann die Definition des Liskov-Substitutionsprinzips wie folgt zusammengefasst werden:

  • Eine Unterklasse kann die abstrakte Methode der übergeordneten Klasse implementieren, aber die nicht abstrakte Methode der übergeordneten Klasse nicht überschreiben Klasse

  • In der Unterklasse können Sie Ihre eigenen eindeutigen Methoden hinzufügen

  • Wenn eine Methode einer Unterklasse eine Methode einer übergeordneten Klasse überschreibt, werden die Vorbedingungen der Methode (d. h. die Eingabeparameter der Methode) überschrieben ) sind lockerer als die Methode der übergeordneten Klasse

  • Wenn eine Methode einer Unterklasse eine Methode einer übergeordneten Klasse implementiert (eine abstrakte Methode überschreibt/überlädt oder implementiert), sind die Nachbedingungen der Methode (d. h. die Ausgabe/ Rückgabewert der Methode) sind strenger oder gleich denen der übergeordneten Klasse

Obwohl es einfach ist, neue Funktionen durch Umschreiben der übergeordneten Klassenmethode zu schreiben, ist die Wiederverwendbarkeit des gesamten Vererbungssystems relativ schlecht Da Polymorphismus häufig verwendet wird, ist die Wahrscheinlichkeit von Fehlern bei der Programmausführung sehr hoch.

Wenn das Programm gegen das Liskov-Substitutionsprinzip verstößt, weist das Objekt der geerbten Klasse dort, wo die Basisklasse erscheint, einen Laufzeitfehler auf.

Die Korrekturmethode zu diesem Zeitpunkt besteht darin, die ursprüngliche Vererbungsbeziehung aufzuheben und die Beziehung zwischen ihnen neu zu gestalten.

Was das Liskov-Substitutionsprinzip betrifft, so ist das bekannteste Beispiel „Ein Quadrat ist kein Rechteck“. Natürlich gibt es viele ähnliche Beispiele im Leben, zum Beispiel werden Pinguine, Strauße und Kiwis aus biologischer Sicht als Vögel klassifiziert, aber aus einer Klassenvererbungsbeziehung, weil sie die „Vogel“-Fähigkeit, zu fliegen, nicht erben können können nicht als Unterklassen von „bird“ definiert werden. Da „Ballonfische“ nicht schwimmen können, können sie auch nicht als Unterkategorie von „Fischen“ definiert werden; „Spielzeugkanonen“ können keine Feinde in die Luft jagen, also können sie nicht als Unterkategorie von „Kanonen“ usw. definiert werden.

Der beste Weg für Quadrate und Rechtecke besteht darin, eine weitere übergeordnete Klasse hinzuzufügen und sie gleichzeitig von dieser übergeordneten Klasse zu erben.

Abhängigkeitsumkehr (Drahtzeichnungsdetails)


Das Prinzip der Abhängigkeitsumkehr ist eine der wichtigen Möglichkeiten zur Implementierung des Open-Close-Prinzips, das die Kopplung zwischen Clients und Implementierungsmodulen reduziert.

Da beim Softwaredesign Details veränderbar sind, während die Abstraktionsschicht relativ stabil ist, ist eine auf Abstraktion basierende Architektur viel stabiler als eine auf Details basierende Architektur. Die Zusammenfassung bezieht sich hier auf die Schnittstelle oder abstrakte Klasse, und die Details beziehen sich auf die spezifische Implementierungsklasse.

Der Zweck der Verwendung von Schnittstellen oder abstrakten Klassen besteht darin, Spezifikationen und Verträge zu formulieren, ohne dass bestimmte Vorgänge erforderlich sind, und die Aufgabe, die Details anzuzeigen, ihren Implementierungsklassen zu überlassen.

Die Funktionen des Abhängigkeits- und Umkehrprinzips

Die Hauptfunktionen des Abhängigkeitsinversionsprinzips sind wie folgt.

  • Das Prinzip der Abhängigkeitsumkehr kann die Kopplung zwischen Klassen verringern.

  • Das Abhängigkeitsinversionsprinzip kann die Stabilität des Systems verbessern.

  • Das Abhängigkeitsinversionsprinzip kann die durch parallele Entwicklung verursachten Risiken verringern.

  • Das Abhängigkeitsinversionsprinzip kann die Lesbarkeit und Wartbarkeit von Code verbessern.

依赖倒置原则的实现方法

依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,所以我们在实际编程中只要遵循以下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 访问什么商店,或者增加新的商店,都不需要修改原有代码了,其类如下图所示:


Was sind die sechs Prinzipien von Designmustern?
程序代码如下:

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()); 
    }
}

程序的运行结果如下:

顾客购买以下商品:
韶关土特产:香菇、木耳……
婺源土特产:绿茶、酒糟鱼……

Interface Segregation Principle (Interface)


Interface Segregation Principle (ISP) erfordert, dass Programmierer ihr Bestes geben, um aufgeblähte Schnittstellen in kleinere und spezifischere Schnittstellen aufzuteilen, sodass die Schnittstelle nur die für den Kunden interessante Methode enthält.

Im Jahr 2002 definierte Robert C. Martin das „Interface Isolation Prinzip“ wie folgt: Clients sollten nicht gezwungen werden, sich auf Methoden zu verlassen, die sie nicht verwenden (Clients sollten nicht gezwungen werden, sich auf Methoden zu verlassen, die sie nicht verwenden). Es gibt eine andere Definition dieses Prinzips: Die Abhängigkeit einer Klasse zu einer anderen sollte von der kleinstmöglichen Schnittstelle abhängen.

Die Bedeutung der beiden oben genannten Definitionen besteht darin, die dedizierten Schnittstellen einzurichten, die sie für jede Klasse benötigen, anstatt zu versuchen, eine riesige Schnittstelle für alle Klassen zu erstellen, deren Aufruf davon abhängt.

Das Prinzip der Schnittstellenisolation und das Prinzip der Einzelverantwortung dienen beide dazu, den Zusammenhalt der Klassen zu verbessern und die Kopplung zwischen ihnen zu verringern, was die Idee der Kapselung verkörpert, aber die beiden sind unterschiedlich:

  • Der Schwerpunkt liegt auf dem Prinzip der Einzelverantwortung auf Verantwortlichkeiten, während das Schnittstellenisolationsprinzip auf die Isolierung von Schnittstellenabhängigkeiten abzielt.

  • Das Prinzip der Einzelverantwortung schränkt hauptsächlich Klassen ein, die auf die Implementierung und Details des Programms abzielen. Das Prinzip der Schnittstellenisolation schränkt hauptsächlich Schnittstellen ein, hauptsächlich für die Konstruktion der Abstraktion und des Gesamtrahmens des Programms.

Vorteile des Schnittstellenisolationsprinzips

Das Schnittstellenisolationsprinzip besteht darin, Schnittstellen einzuschränken und die Abhängigkeit von Klassen von Schnittstellen zu verringern, was die folgenden 5 Vorteile bietet.

  • Zerlegen Sie die aufgeblähte Schnittstelle in mehrere Schnittstellen mit geringer Granularität auf, um die Ausbreitung externer Änderungen zu verhindern und die Flexibilität und Wartbarkeit des Systems zu verbessern.

  • Die Schnittstellenisolation verbessert den Zusammenhalt des Systems, reduziert externe Interaktionen und verringert die Kopplung des Systems.

  • Wenn die Granularität der Schnittstelle angemessen definiert ist, kann die Stabilität des Systems gewährleistet werden. Wenn die Definition jedoch zu klein ist, führt dies zu zu vielen Schnittstellen und verkompliziert das Design Im Großen und Ganzen wird die Flexibilität eingeschränkt. Die Unfähigkeit, maßgeschneiderte Dienstleistungen bereitzustellen, birgt unvorhergesehene Risiken für das Gesamtprojekt.

  • Die Verwendung mehrerer spezialisierter Schnittstellen kann auch die Hierarchie von Objekten widerspiegeln, da die Definition der Gesamtschnittstelle durch Schnittstellenvererbung erreicht werden kann.

  • kann Code-Redundanz in der Projektentwicklung reduzieren. Eine zu große Schnittstelle enthält normalerweise viele ungenutzte Methoden. Bei der Implementierung dieser Schnittstelle sind Sie gezwungen, redundanten Code zu entwerfen.

So implementieren Sie das Schnittstellenisolationsprinzip

Bei der konkreten Anwendung des Schnittstellenisolationsprinzips sollte es gemäß den folgenden Regeln gemessen werden.

  • Die Schnittstelle sollte möglichst klein sein, aber in Grenzen. Eine Schnittstelle bedient nur ein Untermodul oder eine Geschäftslogik.

  • Passen Sie Dienste für Klassen an, die auf Schnittstellen basieren. Stellen Sie nur die Methoden bereit, die der Aufrufer benötigt, und blockieren Sie die Methoden, die nicht benötigt werden.

  • Verstehen Sie die Umgebung und weigern Sie sich, blind zu folgen. Jedes Projekt oder Produkt verfügt über ausgewählte Umgebungsfaktoren. Verschiedene Umgebungen haben unterschiedliche Standards für die Schnittstellenaufteilung. Verstehen Sie die Geschäftslogik im Detail.

  • Verbessern Sie den Zusammenhalt und reduzieren Sie externe Interaktionen. Stellen Sie sicher, dass die Schnittstelle die wenigsten Methoden verwendet, um die meisten Aufgaben zu erledigen.

Für die Schnittstellenisolation können Sie sich weiterhin auf das in der Einzelverantwortung genannte Beispiel beziehen:

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)提及而广为人知。

迪米特法则的定义是:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

迪米特法则中的“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

迪米特法则的优点

迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点。

  • 降低了类之间的耦合度,提高了模块的相对独立性。

  • 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。

但是,过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。

迪米特法则的实现方法

从迪米特法则的定义和特点可知,它强调以下两点:

  • 从依赖者的角度来说,只依赖应该依赖的对象。

  • 从被依赖者的角度说,只暴露应该暴露的方法。

所以,在运用迪米特法则时要注意以下 6 点。

  • 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。

  • 在类的结构设计上,尽量降低类成员的访问权限。

  • 在类的设计上,优先考虑将一个类设置成不变类。

  • 在对其他类的引用上,将引用其他对象的次数降到最低。

  • 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。

  • 谨慎使用序列化(Serializable)功能

明星与经纪人的关系实例。

分析:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则,其类图如下图所示。


Was sind die sechs Prinzipien von Designmustern?
代码如下: 

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;
    }
}

程序的运行结果如下:

粉丝韩丞与明星林心如见面了。
中国传媒有限公司与明星林心如洽淡业务。

 到此,设计模式的六大原则就讲完了。

更多编程相关知识,请访问:编程教学!!

Das obige ist der detaillierte Inhalt vonWas sind die sechs Prinzipien von Designmustern?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Vorheriger Artikel:Was ist SDK?Nächster Artikel:Was ist SDK?