Offen / Geschlossen nach dem SOLID-Prinzip

offen geschlossenes Prinzip
Methods

Software-Entitäten (Klassen, Module, Funktionen usw.) sollten zur Erweiterung geöffnet, zur Bearbeitung jedoch geschlossen sein.

Entwerfen der Software: Module, Klassen und Funktionen so, dass wir, wenn neue Funktionen benötigt werden, keinen vorhandenen Code ändern, sondern neuen Code schreiben sollten, der von vorhandenem Code verwendet wird. Dies mag seltsam klingen, insbesondere bei Sprachen wie Java, C, C ++ oder C #, bei denen dies nicht nur für den Quellcode selbst, sondern auch für die Binärdatei gilt. Wir möchten neue Funktionen so erstellen, dass vorhandene Binärdateien, ausführbare Dateien oder DLLs nicht neu verteilt werden müssen.
OCP im SOLID-Kontext

 

SRP und OCP ergänzen sich

Wir haben bereits das SRP-Prinzip der Einzelverantwortung gesehen, das besagt, dass ein Modul nur einen Grund zur Änderung haben sollte. Die OCP- und SRP-Prinzipien ergänzen sich. Der nach dem SRP-Prinzip entworfene Code berücksichtigt auch die OCP-Prinzipien. Wenn wir Code haben, der nur einen Grund zur Änderung hat, wird durch die Einführung einer neuen Funktion ein sekundärer Grund für diese Änderung erstellt. Somit würden sowohl SRP als auch OCP verletzt. Wenn wir Code haben, der sich nur ändern sollte, wenn sich seine Hauptfunktion ändert, und unverändert bleiben sollte, wenn neue Funktionen hinzugefügt werden, und somit die OCP respektieren, wird er auch in erster Linie die SRP respektieren.
Dies bedeutet nicht, dass SRP immer zu OCP führt oder umgekehrt, aber in den meisten Fällen ist es recht einfach, das zweite zu erreichen, wenn einer von ihnen eingehalten wird.

 

Beispiel für einen Verstoß gegen das OCP-Prinzip

Aus rein technischer Sicht ist das Open / Closed-Prinzip sehr einfach. Eine einfache Beziehung zwischen zwei Klassen, wie die folgende, verstößt gegen das OCP-Prinzip.

Benutzerlogik ocp

Die User-Klasse verwendet die Logic-Klasse direkt. Wenn wir eine zweite Logikklasse so implementieren müssen, dass wir sowohl die aktuelle als auch die neue verwenden können, muss die vorhandene Logikklasse geändert werden. Der Benutzer ist direkt an die Implementierung der Logik gebunden. Es gibt keine Möglichkeit für uns, eine neue Logik bereitzustellen, ohne die aktuelle zu beeinflussen. Und wenn wir über statisch typisierte Sprachen sprechen, ist es sehr wahrscheinlich, dass auch die Benutzerklasse Änderungen erfordert. Wenn wir über kompilierte Sprachen sprechen, müssen sicherlich sowohl die ausführbare Datei des Benutzers als auch die ausführbare Datei der Logik oder die dynamische Bibliothek neu kompiliert und bereitgestellt werden.

In Bezug auf das vorherige Schema können wir schließen, dass jede Klasse, die direkt eine andere Klasse verwendet, zu einer Verletzung des Open / Closed-Prinzips führen kann. 
Nehmen wir an, wir möchten eine Klasse schreiben, die den Fortschritt "in Prozent" einer heruntergeladenen Datei über unsere Anwendung bereitstellen kann. Wir werden zwei Hauptklassen haben, eine Fortschritts- und eine Datei, und ich denke, wir möchten sie wie folgt verwenden:

 

Funktion testItCanGetTheProgressOfAFileAsAPercent () {
     $ file = new File ();
     $ file-> length = 200;
     $ file-> sent = 100;
     $ progress = neuer Fortschritt ($ file);
     $ this-> assertEquals (50, $ progress-> getAsPercent ());
}

In diesem Code sind wir Progress-Benutzer. Wir möchten einen Wert in Prozent erhalten, unabhängig von der tatsächlichen Dateigröße. Wir verwenden Datei als Informationsquelle. Eine Datei hat eine Länge in Bytes und ein Feld namens sent, das die an den Downloader gesendete Datenmenge darstellt. Es ist uns egal, wie diese Werte in der Anwendung aktualisiert werden. Wir können davon ausgehen, dass es eine magische Logik gibt, die dies für uns tut, sodass wir sie in einem Test explizit festlegen können.

 

Klasse Datei {
     öffentliche $ Länge;
     public $ sent;
}

 

Die File-Klasse ist nur ein einfaches Datenobjekt, das die beiden Felder enthält. Natürlich sollte es auch andere Informationen und Verhaltensweisen enthalten, wie z. B. Dateiname, Pfad, relativer Pfad, aktuelles Verzeichnis, Typ, Berechtigungen usw.

 

Klasse Fortschritt {

     private $ file;

     Funktion __construct (Datei $ Datei) {
          $ this-> file = $ file;
     }

     Funktion getAsPercent () {
          return $ this-> file-> sent * 100 / $ this-> file-> length;
     }

}

Fortschritt ist einfach eine Klasse, die eine Datei in ihrem Konstruktor akzeptiert. Aus Gründen der Übersichtlichkeit haben wir den Variablentyp in den Konstruktorparametern angegeben. Es gibt eine einzige nützliche Methode für Progress, getAsPercent (), mit der die gesendeten Werte und die Länge aus der Datei in einen Prozentsatz umgewandelt werden. Einfach und es funktioniert.

Dieser Code scheint korrekt zu sein, verstößt jedoch gegen das Open / Closed-Prinzip.

Aber warum

Und wie?

 

Versuchen wir, die Anforderungen zu ändern

Jede Anwendung benötigt neue Funktionen, um sich im Laufe der Zeit weiterzuentwickeln. Eine neue Funktion für unsere Anwendung könnte darin bestehen, Musik-Streaming zuzulassen, anstatt nur Dateien herunterzuladen. Die Länge der Datei wird in Bytes dargestellt, die Dauer der Musik in Sekunden. Wir möchten unseren Zuhörern einen Fortschrittsbalken anbieten, aber können wir die oben beschriebene Klasse wiederverwenden?

Nein Wir können nicht. Unser Fortschritt ist an File gebunden. Es kann nur Dateiinformationen verwalten, obwohl es auch auf Musikinhalte angewendet werden kann. Aber um das zu tun, müssen wir es modifizieren, wir müssen Progress die Musik und die Dateien bekannt machen. Wenn unser Design OCP entspricht, müssen wir weder Datei noch Fortschritt berühren. Wir könnten den vorhandenen Fortschritt einfach wiederverwenden und auf Musik anwenden.

 

Mögliche Lösung

Dynamisch typisierte Sprachen haben den Vorteil, dass Objekttypen zur Laufzeit verwaltet werden. Auf diese Weise können wir den Typehint aus dem Progress-Konstruktor entfernen und der Code funktioniert weiterhin.

Klasse Fortschritt {

     private $ file;

     Funktion __construct ($ file) {
         $ this-> file = $ file;
     }

    Funktion getAsPercent () {
         return $ this-> file-> sent * 100 / $ this-> file-> length;
     }

}

Wir können jetzt alles bei Progress starten. Und mit irgendetwas meine ich wörtlich alles:

Klasse Musik {

öffentliche $ Länge;
public $ sent;

öffentlicher $ Künstler;
öffentliches $ Album;
public $ releaseDate;

Funktion getAlbumCoverFile () {
Geben Sie 'Images / Covers /' zurück. $ this-> artist. '/'. $ this-> album. '.png';
}
}

Und der Musikunterricht wie oben wird perfekt funktionieren. Wir können es leicht mit einem Test testen, der File sehr ähnlich ist.
Funktion testItCanGetTheProgressOfAMusicStreamAsAPercent () {
$ music = neue Musik ();
$ music-> length = 200;
$ music-> sent = 100;

$ progress = neuer Fortschritt ($ music);

$ this-> assertEquals (50, $ progress-> getAsPercent ());
}

Grundsätzlich kann jeder messbare Inhalt mit der Progress-Klasse verwendet werden. Vielleicht sollten wir es im Code ausdrücken, indem wir auch den Variablennamen ändern:

Klasse Fortschritt {

private $ messbare Inhalte;

Funktion __construct ($ messbarer Inhalt) {
$ this-> messableContent = $ messableContent;
}

Funktion getAsPercent () {
return $ this-> messableContent-> sent * 100 / $ this-> messableContent-> length;
}

}

Als wir File als Typintipp angegeben haben, waren wir optimistisch, was unsere Klasse verarbeiten kann. Es war explizit und wenn etwas anderes kam, ein großer Fehler, den er uns erzählte.

Una Klasse, die eine Methode einer Basisklasse so überschreibt, dass der Basisklassenvertrag von der abgeleiteten Klasse nicht berücksichtigt wird. 

Wir möchten nicht versuchen, Methoden aufzurufen oder auf Felder für Objekte zuzugreifen, die nicht unserem Vertrag entsprechen. Wenn wir einen Tipp hatten, wurde der Vertrag von ihm spezifiziert. Die Felder und Methoden der File-Klasse. Jetzt, wo wir nichts haben, können wir alles senden, sogar eine Zeichenfolge, und dies würde zu einem schlechten Fehler führen.

Während das Endergebnis in beiden Fällen dasselbe ist, was bedeutet, dass der Code unterbrochen wird, hat das erstere eine nette Nachricht erzeugt. Dies ist jedoch sehr dunkel. Es gibt keine Möglichkeit zu wissen, was die Variable ist - in unserem Fall eine Zeichenfolge - und welche Eigenschaften gesucht und nicht gefunden wurden. Es ist schwierig, das Problem zu debuggen und zu beheben. Ein Programmierer muss die Progress-Klasse öffnen, lesen und verstehen. Der Vertrag wird in diesem Fall, wenn der Typintipp nicht explizit angegeben ist, durch das Verhalten von Progress definiert. Es ist ein impliziter Vertrag, der nur Progress bekannt ist. In unserem Beispiel wird dies durch Zugriff auf die beiden Felder gesendet und Länge in der Methode getAsPercent () definiert. Im wirklichen Leben kann der implizite Vertrag sehr komplex und schwer zu entdecken sein, wenn Sie nur ein paar Sekunden im Klassenzimmer suchen.

Diese Lösung wird nur empfohlen, wenn keiner der folgenden Tipps einfach implementiert werden kann oder wenn sie schwerwiegende architektonische Änderungen verursachen würden, die den Aufwand nicht rechtfertigen.

Ercole Palmeri

Lebenslauf Ercole Palmeri
Keine Kommentare

Hinterlassen Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

Business-Intelligence-Strategie
Methods
Strategien für erfolgreiche Business Intelligence

Der Aufbau einer erfolgreichen Strategie für Ihre Business Intelligence beginnt mit einer korrekten Vision der Ziele. Wir sehen unten einige grundlegende Punkte. Einschätzung der aktuellen Situation Es wäre ein schwerwiegender Fehler, diesen Aspekt zu unterschätzen. Um die aktuelle Situation zu bewerten, müssen Prozesse, Strukturen ...

Feste feste geometrische Figuren
Ausbildung
1
FEST, was sind die 5 Prinzipien der objektorientierten Programmierung

SOLID ist ein Akronym, das sich auf die fünf Prinzipien des objektorientierten Designs (OOD oder OOP) bezieht. Mit diesen Richtlinien können Entwickler Software erstellen, die einfach zu verwalten, zu warten und zu erweitern ist. Wenn Sie diese Konzepte verstehen, werden Sie zu einem besseren Entwickler und können ...

innovation bedeutung
Methods
4 (praktische) Schritte zur Schaffung einer Innovation

Es ist kein Geheimnis: Die Coronavirus-Pandemie hat das Leben so vieler Menschen verändert. Von einem Tag auf den anderen waren wir ohne Arbeit oder einfach mit der Notwendigkeit, etwas Neues zu tun, um einer Innovation Leben einzuhauchen. In diesem Artikel möchten wir Ihnen helfen, indem wir 4 einfache Schritte empfehlen, um ...