Ich verstehe die Absicht des Open-Closed-Prinzips. Es soll das Risiko verringern, dass etwas beschädigt wird, das bereits funktioniert, während es geändert wird, indem Sie versuchen, es ohne Änderung zu erweitern.
Ich hatte jedoch einige Probleme zu verstehen, wie dieses Prinzip in der Praxis angewendet wird. Meines Erachtens gibt es zwei Möglichkeiten, dies anzuwenden. Vor und nach einer möglichen Änderung:
Vorher: Programmiere auf Abstraktionen und sage die Zukunft voraus, so oft du kannst. Beispielsweise muss eine Methode
drive(Car car)
geändert werden, wennMotorcycle
dem System in Zukunft s hinzugefügt werden, sodass dies wahrscheinlich gegen OCP verstößt. Esdrive(MotorVehicle vehicle)
ist jedoch weniger wahrscheinlich, dass sich die Methode in Zukunft ändern muss, sodass sie an OCP festhält.Es ist jedoch ziemlich schwierig, die Zukunft vorherzusagen und im Voraus zu wissen, welche Änderungen am System vorgenommen werden.
After: Wenn eine Änderung erforderlich ist, erweitern Sie eine Klasse, anstatt den aktuellen Code zu ändern.
Übung 1 ist nicht schwer zu verstehen. In der zweiten Übung habe ich jedoch Probleme, die Bewerbung zu verstehen.
Zum Beispiel (ich nahm es von einem Video auf YouTube): Lassen Sie uns sagen , dass wir eine Methode in einer Klasse, die akzeptiert CreditCard
Objekte: makePayment(CraditCard card)
. Ein Tag Voucher
wird dem System hinzugefügt. Diese Methode unterstützt sie nicht und muss daher geändert werden.
Bei der ersten Implementierung der Methode ist es uns nicht gelungen, die Zukunft vorherzusagen und abstrakter zu programmieren (z. B. makePayment(Payment pay)
müssen wir jetzt den vorhandenen Code ändern.
In Übung 2 heißt es, wir sollten die Funktionalität erweitern, anstatt sie zu ändern. Was bedeutet das? Soll ich die vorhandene Klasse in Unterklassen unterteilen, anstatt nur den vorhandenen Code zu ändern? Sollte ich eine Art Wrapper erstellen, um zu vermeiden, dass Code neu geschrieben wird?
Oder bezieht sich das Prinzip nicht einmal auf das richtige Ändern / Hinzufügen von Funktionen, sondern vielmehr auf das Vermeiden von Änderungen an erster Stelle (dh Programmieren auf Abstraktionen)?
quelle
Antworten:
Gestaltungsprinzipien müssen immer gegeneinander abgewogen werden. Sie können die Zukunft nicht vorhersagen, und die meisten Programmierer tun es schrecklich, wenn sie es versuchen. Aus diesem Grund haben wir die Dreierregel , bei der es in erster Linie um die Vervielfältigung geht, die jedoch auch für die Umgestaltung anderer Designprinzipien gilt.
Wenn Sie nur eine Implementierung einer Schnittstelle haben, müssen Sie sich nicht viel um OCP kümmern, es sei denn, es ist glasklar, wo Erweiterungen stattfinden würden. Tatsächlich verlieren Sie oft an Klarheit, wenn Sie versuchen, in dieser Situation zu viel zu entwerfen. Wenn Sie es einmal erweitern, überarbeiten Sie es, um es OCP-freundlich zu machen, wenn dies der einfachste und klarste Weg ist, dies zu tun. Wenn Sie es auf eine dritte Implementierung erweitern, stellen Sie sicher, dass Sie es unter Berücksichtigung von OCP umgestalten, auch wenn es etwas mehr Aufwand erfordert.
In der Praxis ist das Refactoring beim Hinzufügen einer dritten Implementierung in der Regel nicht allzu schwierig, wenn nur zwei Implementierungen vorhanden sind. Wenn Sie es über diesen Punkt hinaus wachsen lassen, wird es schwierig, es aufrechtzuerhalten.
quelle
Ich denke, Sie schauen zu weit in die Zukunft. Lösen Sie das aktuelle Problem auf flexible Weise, die offen / geschlossen hält.
Angenommen, Sie müssen eine
drive(Car car)
Methode implementieren . Abhängig von Ihrer Sprache haben Sie mehrere Möglichkeiten.Für Sprachen, die das Überladen unterstützen (C ++), verwenden Sie einfach
drive(const Car& car)
Irgendwann später brauchen Sie vielleicht
drive(const Motorcycle& motorcycle)
, aber es wird nicht störendrive(const Car& car)
. Kein Problem!Fügen Sie für Sprachen, die keine Überladung unterstützen (Ziel C), den Typnamen in die Methode ein
-driveCar:(Car *)car
.Irgendwann später müssen Sie möglicherweise
-driveMotorcycle:(Motorcycle *)motorcycle
, aber auch hier wird es nicht stören.Dies kann
drive(Car car)
nicht geändert werden, kann jedoch auf andere Fahrzeugtypen ausgeweitet werden. Diese minimalistische Zukunftsplanung ermöglicht es Ihnen, Ihre Arbeit heute zu erledigen, verhindert jedoch, dass Sie sich in Zukunft blockieren.Der Versuch, sich die grundlegendsten Typen vorzustellen, die Sie benötigen, kann zu einer unendlichen Regression führen. Was passiert, wenn Sie einen Segue, ein Fahrrad oder einen Jumbo-Jet fahren möchten? Wie konstruieren Sie einen einzelnen generischen abstrakten Typ, der alle Geräte berücksichtigt, die Menschen für die Mobilität einsetzen und verwenden können?
quelle
Es geht auch darum, nicht alle Objekte zu brechen, die auf dieser Methode basieren, indem das Verhalten bereits vorhandener Objekte nicht geändert wird. Sobald ein Objekt eine Verhaltensänderung angekündigt hat, ist dies riskant, da Sie das bekannte und erwartete Verhalten des Objekts ändern, ohne genau zu wissen, welche anderen Objekte dieses Verhalten erwarten.
Jep.
"Akzeptiert nur Kreditkarten" wird als Teil des Verhaltens dieser Klasse über ihre öffentliche Schnittstelle definiert. Der Programmierer hat der Welt erklärt, dass die Methode dieses Objekts nur Kreditkarten akzeptiert. Sie hat es mit einem nicht besonders eindeutigen Methodennamen gemacht, aber es ist geschafft. Der Rest des Systems verlässt sich darauf.
Das mag damals Sinn gemacht haben, aber wenn es sich jetzt ändern muss, sollten Sie eine neue Klasse erstellen, die andere Dinge als Kreditkarten akzeptiert.
Neues Verhalten = neue Klasse
Nebenbei - Eine gute Möglichkeit, die Zukunft vorherzusagen, besteht darin, über den Namen nachzudenken, den Sie einer Methode gegeben haben. Haben Sie einer Methode einen wirklich allgemein klingenden Methodennamen wie makePayment mit bestimmten Regeln in der Methode gegeben, um genau zu bestimmen, welche Zahlung sie leisten kann? Das ist ein Codegeruch. Wenn Sie spezifische Regeln haben, sollten diese anhand des Methodennamens verdeutlicht werden - makePayment sollte makeCreditCardPayment sein. Tun Sie dies, wenn Sie das Objekt zum ersten Mal schreiben, und andere Programmierer werden es Ihnen danken.
quelle