Das Open-Closed-Prinzip (OCP) besagt, dass ein Objekt zur Erweiterung geöffnet, zur Änderung jedoch geschlossen sein sollte. Ich glaube, ich verstehe es und verwende es in Verbindung mit SRP, um Klassen zu erstellen, die nur eines tun. Und ich versuche, viele kleine Methoden zu erstellen, die es ermöglichen, alle Verhaltenssteuerelemente in Methoden zu extrahieren, die in einigen Unterklassen erweitert oder überschrieben werden können. So erhalte ich Klassen mit vielen Erweiterungspunkten, sei es durch: Abhängigkeitsinjektion und -zusammensetzung, Ereignisse, Delegierung usw.
Betrachten Sie Folgendes als einfache, erweiterbare Klasse:
class PaycheckCalculator {
// ...
protected decimal GetOvertimeFactor() { return 2.0M; }
}
Sagen wir nun zum Beispiel, dass sich die OvertimeFactor
Änderungen auf 1.5 ändern. Da die obige Klasse erweitert werden sollte, kann ich leicht eine andere Unterklasse erstellen und zurückgeben OvertimeFactor
.
Aber ... obwohl die Klasse für die Erweiterung und Einhaltung von OCP ausgelegt ist, werde ich die betreffende einzelne Methode ändern, anstatt die betreffende Methode zu unterklassifizieren und zu überschreiben und dann meine Objekte in meinem IoC-Container neu zu verkabeln.
Infolgedessen habe ich einen Teil dessen verletzt, was OCP zu erreichen versucht. Es fühlt sich an, als wäre ich nur faul, weil das oben genannte etwas einfacher ist. Verstehe ich OCP falsch? Sollte ich wirklich etwas anderes machen? Nutzen Sie die Vorteile von OCP anders?
Update : Basierend auf den Antworten sieht es so aus, als ob dieses erfundene Beispiel aus verschiedenen Gründen schlecht ist. Die Hauptabsicht des Beispiels bestand darin, zu demonstrieren, dass die Klasse so erweitert werden sollte, dass Methoden bereitgestellt werden, die beim Überschreiben das Verhalten öffentlicher Methoden ändern , ohne dass interner oder privater Code geändert werden muss. Trotzdem habe ich OCP definitiv falsch verstanden.
quelle
During the 1990s, the open/closed principle became popularly redefined to refer to the use of abstracted interfaces, where the implementations can be changed and multiple implementations could be created and polymorphically substituted for each other.
en.wikipedia.org/wiki/Open/closed_principleDas Open Closed-Prinzip ist also ein Problem ... besonders wenn Sie versuchen, es gleichzeitig mit YAGNI anzuwenden . Wie halte ich mich gleichzeitig an beide? Wenden Sie die Dreierregel an . Wenn Sie zum ersten Mal eine Änderung vornehmen, nehmen Sie diese direkt vor. Und das zweite Mal auch. Das dritte Mal ist es Zeit, diese Veränderung zu abstrahieren.
Ein anderer Ansatz ist "täusche mich einmal ...". Wenn Sie eine Änderung vornehmen müssen, wenden Sie OCP an, um sich in Zukunft vor dieser Änderung zu schützen . Ich würde fast so weit gehen, vorzuschlagen, dass die Änderung der Überstundenquote eine neue Geschichte ist. "Als Lohnbuchhalter möchte ich die Überstundenquote ändern, damit ich die geltenden Arbeitsgesetze einhalten kann." Jetzt haben Sie eine neue Benutzeroberfläche, mit der Sie die Überstundenrate ändern und speichern können. GetOvertimeFactor () fragt nur das Repository nach der Überstundenrate.
quelle
In dem von Ihnen veröffentlichten Beispiel sollte der Überstundenfaktor eine Variable oder eine Konstante sein. * (Java-Beispiel)
ODER
Wenn Sie dann die Klasse erweitern, setzen oder überschreiben Sie den Faktor. "Magische Zahlen" sollten nur einmal erscheinen. Dies ist viel mehr im Stil von OCP und DRY (Don't Repeat Yourself), da es nicht erforderlich ist, eine ganz neue Klasse für einen anderen Faktor zu erstellen, wenn die erste Methode verwendet wird und nur die Konstante in einer Redewendung geändert werden muss Platz in der zweiten.
Ich würde den ersten in Fällen verwenden, in denen es mehrere Arten von Taschenrechnern gibt, die jeweils unterschiedliche konstante Werte benötigen. Ein Beispiel wäre das Chain of Responsibility-Muster, das normalerweise mit geerbten Typen implementiert wird. Ein Objekt, das nur die Schnittstelle sehen kann (dh
getOvertimeFactor()
), verwendet sie, um alle benötigten Informationen abzurufen, während sich die Untertypen um die tatsächlich bereitgestellten Informationen kümmern.Die zweite ist nützlich in Fällen, in denen die Konstante wahrscheinlich nicht geändert wird, aber an mehreren Stellen verwendet wird. Es ist viel einfacher, eine Konstante zu ändern (in dem unwahrscheinlichen Fall, dass dies der Fall ist), als sie überall festzulegen oder aus einer Eigenschaftendatei abzurufen.
Das Open-Closed-Prinzip ist weniger ein Aufruf, ein vorhandenes Objekt nicht zu ändern, als eine Warnung, die Schnittstelle für sie unverändert zu lassen. Wenn Sie ein etwas anderes Verhalten als eine Klasse oder zusätzliche Funktionen für einen bestimmten Fall benötigen, erweitern und überschreiben Sie diese. Wenn sich jedoch die Anforderungen für die Klasse selbst ändern (z. B. das Ändern des Faktors), müssen Sie die Klasse ändern. Es macht keinen Sinn in einer riesigen Klassenhierarchie, von der die meisten nie verwendet werden.
quelle
Ich sehe Ihr Beispiel nicht wirklich als eine großartige Darstellung von OCP. Ich denke, was die Regel wirklich bedeutet, ist Folgendes:
Eine schlechte Implementierung unten. Jedes Mal, wenn Sie ein Spiel hinzufügen, müssen Sie die GamePlayer-Klasse ändern.
Die GamePlayer-Klasse sollte niemals geändert werden müssen
Angenommen, meine GameFactory hält sich auch an OCP, wenn ich ein weiteres Spiel hinzufügen möchte, müsste ich nur eine neue Klasse erstellen, die von der
Game
Klasse erbt, und alles sollte einfach funktionieren.Allzu oft werden Klassen wie die erste nach Jahren der "Erweiterungen" aufgebaut und von der Originalversion nie richtig überarbeitet (oder schlimmer noch, was mehrere Klassen sein sollten, bleibt eine große Klasse).
Das von Ihnen angegebene Beispiel ist OCP-ish. Meiner Meinung nach wäre der richtige Weg, um Änderungen der Überstundenrate zu verarbeiten, eine Datenbank mit historischen Raten, damit die Daten erneut verarbeitet werden können. Der Code sollte weiterhin zur Änderung geschlossen werden, da immer der entsprechende Wert aus der Suche geladen wird.
Als Beispiel aus der Praxis habe ich eine Variante meines Beispiels verwendet, und das Open-Closed-Prinzip strahlt wirklich. Funktionalität ist sehr einfach hinzuzufügen, da ich nur von einer abstrakten Basisklasse ableiten muss und meine "Fabrik" sie automatisch aufnimmt und es dem "Spieler" egal ist, welche konkrete Implementierung die Fabrik zurückgibt.
quelle
In diesem Beispiel haben Sie einen sogenannten "magischen Wert". Im Wesentlichen ein fest codierter Wert, der sich im Laufe der Zeit ändern kann oder nicht. Ich werde versuchen, das Rätsel zu lösen, das Sie generisch ausdrücken, aber dies ist ein Beispiel für die Art von Dingen, bei denen das Erstellen einer Unterklasse mehr Arbeit bedeutet als das Ändern eines Werts in einer Klasse.
Nehmen wir an, wir haben die
PaycheckCalculator
. DasOvertimeFactor
würde höchstwahrscheinlich von Informationen über den Mitarbeiter abgeschlüsselt werden. Ein stündlicher Mitarbeiter kann einen Überstundenbonus erhalten, während ein Angestellter nichts bezahlt bekommt. Trotzdem werden einige Angestellte aufgrund des Vertrags, an dem sie gearbeitet haben, gerade Zeit bekommen. Sie können entscheiden, dass es bestimmte bekannte Klassen von Vergütungsszenarien gibt, und so würden Sie Ihre Logik aufbauen.In der Basisklasse
PaycheckCalculator
machen Sie es abstrakt und geben die erwarteten Methoden an. Die Kernberechnungen sind die gleichen, nur dass bestimmte Faktoren unterschiedlich berechnet werden. SieHourlyPaycheckCalculator
würden dann diegetOvertimeFactor
Methode implementieren und je nach Fall 1,5 oder 2,0 zurückgeben. SieStraightTimePaycheckCalculator
würden das implementierengetOvertimeFactor
, um 1.0 zurückzugeben. Schließlich wäre eine dritte Implementierung eineNoOvertimePaycheckCalculator
, die diegetOvertimeFactor
Rückgabe von 0 implementiert .Der Schlüssel besteht darin, nur das Verhalten in der Basisklasse zu beschreiben, das erweitert werden soll. Die Details von Teilen des Gesamtalgorithmus oder bestimmten Werten würden durch Unterklassen ausgefüllt. Die Tatsache, dass Sie einen Standardwert für die
getOvertimeFactor
Zeilen angegeben haben, führt zu einer schnellen und einfachen "Korrektur" der einen Zeile, anstatt die Klasse wie beabsichtigt zu erweitern. Es wird auch die Tatsache hervorgehoben, dass mit der Erweiterung des Unterrichts Anstrengungen verbunden sind. Es sind auch Anstrengungen erforderlich, um die Hierarchie der Klassen in Ihrer Anwendung zu verstehen. Sie möchten Ihre Klassen so gestalten, dass die Notwendigkeit, Unterklassen zu erstellen, minimiert wird und gleichzeitig die erforderliche Flexibilität geboten wird.Denkanstöße: Wenn unsere Klassen bestimmte Datenfaktoren wie
OvertimeFactor
in Ihrem Beispiel enthalten, benötigen Sie möglicherweise eine Möglichkeit, diese Informationen aus einer anderen Quelle abzurufen. Beispielsweise würde eine Eigenschaftendatei (da dies wie Java aussieht) oder eine Datenbank den Wert enthalten, und SiePaycheckCalculator
würden ein Datenzugriffsobjekt verwenden, um Ihre Werte abzurufen. Auf diese Weise können die richtigen Personen das Verhalten des Systems ändern, ohne dass ein Code neu geschrieben werden muss.quelle