Ich bin etwas verwirrt darüber, wie das Open-Closed-Prinzip im wirklichen Leben angewendet werden kann. Die Anforderungen an geschäftliche Änderungen ändern sich im Laufe der Zeit. Nach dem Open-Closed-Prinzip sollten Sie die Klasse erweitern, anstatt die vorhandene Klasse zu ändern. Jedes Mal, wenn ich eine Klasse erweitere, scheint es mir nicht praktisch zu sein, die Anforderung zu erfüllen. Lassen Sie mich ein Beispiel mit dem Zugbuchungssystem geben.
Im Zugbuchungssystem gibt es ein Ticketobjekt. Es kann verschiedene Arten von Tickets geben. Regular Ticket, Concession Ticket usw. Ticket ist eine abstrakte Klasse und RegularTicket und ConcessionTickets sind konkrete Klassen. Da alle Tickets über die übliche PrintTicket-Methode verfügen, wird sie in Ticket geschrieben, einer abstrakten Basisklasse. Nehmen wir an, das hat ein paar Monate lang gut funktioniert. Jetzt kommt eine neue Anforderung hinzu, die besagt, dass das Format des Tickets geändert werden muss. Möglicherweise werden dem gedruckten Ticket nur noch wenige Felder hinzugefügt oder das Format wird geändert. Um diese Anforderung zu erfüllen, habe ich folgende Möglichkeiten
- Ändern Sie die PrintTicket () -Methode in der abstrakten Ticketklasse. Dies verstößt jedoch gegen das Open-Closed-Prinzip.
- Überschreiben Sie die PrintTicket () -Methode in untergeordneten Klassen. Dadurch wird jedoch die Drucklogik dupliziert, die gegen das DRY-Prinzip (Wiederholen Sie sich nicht) verstößt.
Fragen sind also
- Wie kann ich die oben genannten Geschäftsanforderungen erfüllen, ohne das Open / Closed-Prinzip zu verletzen?
- Wann soll die Klasse wegen Änderung geschlossen werden? Nach welchen Kriterien ist die Klasse wegen Änderung geschlossen? Ist es nach der ersten Implementierung der Klasse oder kann nach der ersten Bereitstellung in der Produktion sein oder kann etwas anderes sein.
quelle
Antworten:
Dies hängt von der Art und Weise ab, wie das
PrintTicket
implementiert wird. Wenn die Methode Informationen aus Unterklassen berücksichtigen muss, muss sie eine Möglichkeit bieten, zusätzliche Informationen bereitzustellen.Darüber hinaus können Sie überschreiben, ohne Ihren Code zu wiederholen. Wenn Sie beispielsweise Ihre Basisklassenmethode aus der Implementierung aufrufen, vermeiden Sie Wiederholungen:
Das Muster der Vorlagenmethode bietet eine dritte Option: Implementieren Sie es
PrintTicket
in der Basisklasse und verlassen Sie sich auf abgeleitete Klassen, um bei Bedarf zusätzliche Details bereitzustellen.Hier ist ein Beispiel mit Ihrer Klassenhierarchie:
Es ist nicht die Klasse, die für Änderungen geschlossen werden sollte, sondern die Schnittstelle dieser Klasse (ich meine "Schnittstelle" im weiteren Sinne, dh eine Sammlung von Methoden und Eigenschaften und deren Verhalten, nicht das Sprachkonstrukt). Die Klasse verbirgt ihre Implementierung, sodass die Eigentümer der Klasse sie jederzeit ändern können, solange ihr extern sichtbares Verhalten unverändert bleibt.
Die Schnittstelle der Klasse muss nach dem ersten Veröffentlichen für die externe Verwendung geschlossen bleiben. Interne Verwendungsklassen bleiben für immer für Refactoring offen, da Sie alle Verwendungen finden und korrigieren können.
Abgesehen von den einfachsten Fällen ist es nicht praktikabel zu erwarten, dass Ihre Klassenhierarchie nach einer bestimmten Anzahl von Refactoring-Iterationen alle möglichen Verwendungsszenarien abdeckt. Zusätzliche Anforderungen, die völlig neue Methoden in der Basisklasse erfordern, werden regelmäßig gestellt, sodass Ihre Klasse für immer für Änderungen durch Sie offen bleibt.
quelle
TicketPrinter
Klasse erstellen, die an den Konstruktor übergeben wird.Beginnen wir mit einer einfachen Linie des Open-Closed-Prinzips
"Open for extension Closed for modification"
. Betrachten Sie Ihr Problem. Ich würde die Klassen so gestalten, dass das Basisticket für das Drucken gemeinsamer Ticketinformationen verantwortlich ist und andere Ticketarten für das Drucken ihres eigenen Formats über dem Basisdruck verantwortlich sind.Auf diese Weise erzwingen Sie jede neue Art von Ticket, die in das System eingeführt wird, und implementieren zusätzlich zu den grundlegenden Informationen zum Drucken von Tickets eine eigene Druckfunktion. Das Basisticket ist jetzt zur Änderung geschlossen, kann jedoch erweitert werden, indem zusätzliche Methoden für die abgeleiteten Typen bereitgestellt werden.
quelle
Das Problem ist nicht, dass Sie gegen das Open / Closed-Prinzip verstoßen, sondern dass Sie gegen das Prinzip der Einzelverantwortung verstoßen.
Dies ist buchstäblich ein Schulbuchbeispiel für ein SRP-Problem oder wie in Wikipedia angegeben :
Die verschiedenen Ticketklassen können sich ändern, da Sie ihnen neue Informationen hinzufügen. Das Drucken des Tickets kann sich ändern, da Sie ein neues Layout benötigen. Daher sollten Sie über eine TicketPrinter- Klasse verfügen , die Tickets als Eingabe akzeptiert.
Ihre Ticketklassen können dann verschiedene Schnittstellen implementieren, um verschiedene Datentypen oder eine Art Datenvorlage bereitzustellen.
quelle
Dies verstößt nicht gegen das Open-Closed-Prinzip. Der Code ändert sich ständig. Deshalb ist SOLID wichtig, damit der Code wartbar, flexibel und änderbar bleibt. Es ist nichts Falsches daran, Code zu ändern.
Bei OCP geht es mehr darum, dass externe Klassen die beabsichtigte Funktionalität einer Klasse nicht manipulieren können.
Zum Beispiel habe ich eine Klasse
A
, die einen String im Konstruktor akzeptiert und überprüft, ob dieser String ein bestimmtes Format hat, indem eine Methode zum Überprüfen des Strings aufgerufen wird. Diese Überprüfungsmethode muss auch von Clients verwendet werden könnenA
, damit sie in der naiven Implementierung ausgeführt wirdpublic
.Jetzt kann ich diese Klasse "brechen", indem ich von ihr erbe und die Überprüfungsmethode überschreibe, um einfach immer zurückzukehren
true
. Aufgrund des Polymorphismus kann ich meine Unterklasse immer noch überall dort verwendenA
, wo ich sie verwenden kann , wodurch möglicherweise unerwünschtes Verhalten in meinem Programm entsteht.Dies scheint eine böswillige Sache zu sein, aber in einer komplexeren Codebasis könnte dies als "ehrlicher Fehler" geschehen. Um der OCP zu entsprechen,
A
sollte die Klasse so gestaltet sein, dass dieser Fehler nicht möglich ist.Ich könnte dies tun, indem ich zum Beispiel die Überprüfungsmethode mache
final
.quelle
Vererbung ist nicht der einzige Mechanismus, der verwendet werden kann, um die Anforderungen von OCP zu erfüllen, und in den meisten Fällen würde ich argumentieren, dass es selten der beste ist - es gibt normalerweise bessere Möglichkeiten, solange Sie ein wenig im Voraus planen, dies zu berücksichtigen Art von Änderungen, die Sie wahrscheinlich benötigen.
In diesem Fall würde ich argumentieren, dass die Verwendung eines Template-Systems sehr sinnvoll wäre, wenn Sie häufige Formatänderungen an Ihrem Ticketdrucksystem erwarten (und das scheint mir eine ziemlich sichere Sache zu sein). Jetzt müssen wir den Code überhaupt nicht mehr berühren: Alles, was wir tun müssen, um diese Anforderung zu erfüllen, ist eine Vorlage zu ändern (solange Ihr Vorlagensystem sowieso auf alle erforderlichen Daten zugreifen kann).
In der Realität kann ein System niemals vollständig gegen Änderungen geschlossen werden, und selbst das Schließen würde eine enorme Verschwendung erfordern. Sie müssen also beurteilen, welche Art von Änderungen wahrscheinlich sind, und Ihren Code entsprechend strukturieren.
quelle