Was bedeutet es, wenn man sagt: "Verkapseln, was variiert"?

25

Eines der OOP-Prinzipien, auf die ich gestoßen bin, ist: - Fassen Sie die Unterschiede zusammen.

Ich verstehe, was die wörtliche Bedeutung der Phrase ist, dh verstecke, was variiert. Ich weiß jedoch nicht, wie genau dies zu einem besseren Design beitragen würde. Kann es jemand anhand eines guten Beispiels erklären?

Haris Ghauri
quelle
Siehe en.wikipedia.org/wiki/Encapsulation_(computer_programming), was es gut erklärt. Ich denke das "was variiert" ist nicht korrekt da man auch mal Konstanten einkapseln sollte.
qwerty_so
I don't know how exactly would it contribute to a better designBei der Verkapselung von Details handelt es sich um eine lose Kopplung zwischen dem "Modell" und den Implementierungsdetails. Je weniger das "Modell" an die Implementierungsdetails gebunden ist, desto flexibler ist die Lösung. Und es macht es einfacher, es weiterzuentwickeln. "Entziehen Sie sich aus den Details".
Laiv
@Laiv "Variiert" bezieht sich also darauf, was sich im Laufe Ihres Software-Lebenszyklus entwickelt oder was sich während der Ausführung Ihres Programms ändert oder beides?
Haris Ghauri
2
@HarisGhauri beide. Fasse zusammen, was zusammen variiert. Isolieren Sie, was unabhängig voneinander variiert. Seien Sie misstrauisch gegenüber dem, was sich Ihrer Meinung nach niemals ändern wird.
candied_orange
1
@ Laiv Denken "abstrakt" ist ein guter Punkt. Es kann überwältigend sein, das zu tun. In einem Objekt sollst du eine Verantwortung haben. Das Schöne daran ist, dass Sie hier nur genau über das eine nachdenken müssen. Wenn die Details des Restes des Problems jemand anderes sind, macht es die Dinge einfacher.
candied_orange

Antworten:

30

Sie können Code schreiben, der so aussieht:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

Oder Sie können Code schreiben, der so aussieht:

pet.speak();

Wenn das, was variiert, gekapselt ist, müssen Sie sich darüber keine Sorgen machen. Sie müssen sich nur Gedanken darüber machen, was Sie brauchen und was Sie verwenden.

Fassen Sie die Unterschiede zusammen, und Sie müssen keinen Code verteilen, der sich um die Unterschiede kümmert. Sie stellen das Haustier einfach auf einen bestimmten Typ ein, der zu sprechen weiß, und danach können Sie den Typ vergessen und es einfach wie ein Haustier behandeln. Sie müssen nicht nach dem Typ fragen.

Sie könnten denken, dass der Typ gekapselt ist, weil ein Getter für den Zugriff erforderlich ist. Ich nicht. Getter kapseln nicht wirklich. Sie klappern nur, wenn jemand Ihre Einkapselung bricht. Sie sind ein netter Dekorateur wie Aspekt-orientierter Haken, der am häufigsten als Debugging-Code verwendet wird. Egal wie Sie es schneiden, Sie belichten immer noch Typ.

Sie könnten sich dieses Beispiel ansehen und denken, ich verschmelze Polymorphismus und Verkapselung. Ich bin nicht. Ich verschmelze "was variiert" und "Details".

Die Tatsache, dass Ihr Haustier ein Hund ist, ist ein Detail. Eine, die für Sie variieren kann. Eines, das vielleicht nicht. Aber sicherlich eine, die von Person zu Person unterschiedlich sein kann. Sofern wir nicht glauben, dass diese Software nur von Hundeliebhabern verwendet wird, ist es klug, Hund als Detail zu behandeln und es zu verkapseln. Auf diese Weise sind sich einige Teile des Systems des Hundes völlig unbewusst und werden nicht beeinträchtigt, wenn wir uns mit "Papageien sind wir" verbinden.

Entkoppeln, trennen und verbergen Sie Details vom Rest des Codes. Lassen Sie nicht zu, dass sich das Wissen über Details in Ihrem System ausbreitet, und Sie werden "Kapseln, was variiert" genau verfolgen.

kandierte_orange
quelle
3
Das ist wirklich seltsam. "Encapsulate what vary" bedeutet für mich, Zustandsänderungen zu verbergen, zB niemals globale Variablen zu haben. Aber Ihre Antwort macht auch Sinn, auch wenn es sich eher um eine Antwort auf Polymorphismus als um Verkapselung handelt :)
David Arno
2
@DavidArno Polymorphismus ist eine Möglichkeit, diese Arbeit zu machen. Ich hätte die if-Struktur einfach in pet verwandeln können, und dank der Einkapselung von pet würden die Dinge hier draußen gut aussehen. Aber das würde nur die Unordnung in Bewegung setzen, anstatt sie aufzuräumen.
candied_orange
1
"Encapsulate what vary" bedeutet für mich, Zustandsänderungen zu verbergen . Nein, nein. Ich mag den Kommentar von CO. Derick Elkins Antwort geht tiefer, lies sie mehr als einmal. Wie @JacquesB sagte "Dieses Prinzip ist eigentlich ziemlich tief"
Radarbob
16

"Variiert" bedeutet hier "kann sich im Laufe der Zeit aufgrund sich ändernder Anforderungen ändern". Dies ist ein zentrales Konstruktionsprinzip: Trennung und Isolation von Codeteilen oder Daten, die in Zukunft möglicherweise separat geändert werden müssen. Wenn sich eine einzelne Anforderung ändert, sollte es im Idealfall nur erforderlich sein, den zugehörigen Code an einer einzigen Stelle zu ändern. Wenn die Codebasis jedoch schlecht konzipiert ist, dh stark miteinander verknüpft und die Logik für die Anforderung an vielen Stellen verteilt ist, ist die Änderung schwierig und birgt ein hohes Risiko, unerwartete Auswirkungen zu verursachen.

Angenommen, Sie haben eine Anwendung, in der an vielen Stellen die Umsatzsteuer berechnet wird. Wenn sich der Umsatzsteuersatz ändert, was würden Sie bevorzugen:

  • Der Umsatzsteuersatz ist überall in der Anwendung, wo die Umsatzsteuer berechnet wird, ein hartcodiertes Wort.

  • Der Umsatzsteuersatz ist eine globale Konstante, die überall dort verwendet wird, wo die Umsatzsteuer berechnet wird.

  • Es gibt eine einzige Methode, die aufgerufen wird. calculateSalesTax(product)Dies ist der einzige Ort, an dem der Umsatzsteuersatz verwendet wird.

  • Der Umsatzsteuersatz wird in einer Konfigurationsdatei oder einem Datenbankfeld angegeben.

Da sich der Umsatzsteuersatz aufgrund einer politischen Entscheidung unabhängig von anderen Anforderungen ändern kann, ziehen wir es vor, ihn in einer Konfiguration zu isolieren, damit er ohne Auswirkung auf einen Code geändert werden kann. Es ist aber auch denkbar, dass sich die Logik zur Berechnung der Umsatzsteuer ändert, z. B. unterschiedliche Sätze für unterschiedliche Produkte. Deshalb möchten wir auch die Berechnungslogik gekapselt haben. Die globale Konstante scheint eine gute Idee zu sein, ist aber tatsächlich schlecht, da sie möglicherweise dazu anregt, die Umsatzsteuer an verschiedenen Stellen im Programm und nicht an einer einzigen Stelle zu verwenden.

Betrachten Sie nun eine andere Konstante, Pi, die auch an vielen Stellen im Code verwendet wird. Gilt das gleiche Konstruktionsprinzip? Nein, denn Pi wird sich nicht ändern. Das Extrahieren in eine Konfigurationsdatei oder ein Datenbankfeld führt nur zu unnötiger Komplexität (und wenn alles andere gleich ist, bevorzugen wir den einfachsten Code). Es ist sinnvoll, es zu einer globalen Konstante zu machen, anstatt es an mehreren Stellen fest zu codieren, um Inkonsistenzen zu vermeiden und die Lesbarkeit zu verbessern.

Der Punkt ist, wenn wir uns nur ansehen, wie das Programm jetzt funktioniert , Umsatzsteuersatz und Pi sind gleichwertig, beides sind Konstanten. Nur wenn wir uns überlegen, was in Zukunft anders sein kann , müssen wir sie im Design anders behandeln.

Dieses Prinzip ist eigentlich ziemlich tiefgreifend, da man über das hinausschauen muss, was die Codebasis heutzutage tun soll , und auch die externen Kräfte berücksichtigen muss, die dazu führen können, dass sie sich ändert, und sogar die verschiedenen Stakeholder verstehen muss, die hinter den Anforderungen stehen.

JacquesB
quelle
2
Die Steuern sind ein gutes Beispiel. Gesetze und Steuern können sich von Tag zu Tag ändern. Wenn Sie ein Steuererklärungssystem implementieren, sind Sie stark an diese Art von Änderungen gebunden.
Ändert sich
"Pi wird sich nicht ändern" brachte mich zum Lachen. Es ist wahr, Pi wird sich wahrscheinlich nicht ändern, aber nehmen wir an, Sie durften es nicht mehr benutzen? Wenn einige Leute ihren Willen haben, wird Pi veraltet sein. Angenommen, das wird eine Anforderung? Ich hoffe, Sie haben einen schönen Tau-Tag . Nette Antwort übrigens. Tief in der Tat.
candied_orange
14

Beide aktuellen Antworten scheinen nur teilweise ins Schwarze getroffen zu haben und konzentrieren sich auf Beispiele, die die Kernidee trüben. Dies ist auch nicht (ausschließlich) ein OOP-Prinzip, sondern ein Software-Design-Prinzip im Allgemeinen.

Das, was in diesem Satz "variiert", ist der Code. Christophe ist der Meinung, dass es sich in der Regel um etwas handelt, das variieren kann , und dass Sie dies oft vorwegnehmen . Ziel ist es, sich vor zukünftigen Änderungen im Code zu schützen. Dies hängt eng mit der Programmierung an einer Schnittstelle zusammen . Christophe ist jedoch falsch, dies auf "Implementierungsdetails" zu beschränken. Tatsächlich beruht der Wert dieser Beratung häufig auf Änderungen der Anforderungen .

Dies hängt nur indirekt mit der Einkapselung des Staates zusammen, woran David Arno meines Erachtens denkt. Dieser Rat schlägt nicht immer (aber oft) vor, einen Zustand einzukapseln, und dieser Rat gilt auch für unveränderliche Objekte. Tatsächlich ist das bloße Benennen von Konstanten eine (sehr grundlegende) Form der Verkapselung von Unterschieden.

CandiedOrange kombiniert explizit "was variiert" mit "Details". Dies ist nur teilweise richtig. Ich bin damit einverstanden, dass jeder Code, der sich ändert, in gewissem Sinne "Details" ist, aber ein "Detail" darf nicht variieren (es sei denn, Sie definieren "Details", um dies tautologisch zu machen). Es mag Gründe geben, nicht variierende Details zu kapseln, aber dieses Diktum ist keines. Grob gesagt, wenn Sie sehr zuversichtlich wären, dass "Hund", "Katze" und "Ente" die einzigen Typen sind, mit denen Sie sich jemals auseinandersetzen müssten, dann schlägt dieses Sprichwort nicht vor, dass das Refactoring von CandiedOrange durchgeführt wird.

Wenn Sie das Beispiel von CandiedOrange in einem anderen Kontext betrachten, nehmen wir an, dass wir eine prozedurale Sprache wie C haben. Wenn ich Code habe, der Folgendes enthält:

if (pet.type() == dog) {
  pet.bark();
} else if (pet.type() == cat) {
  pet.meow();
} else if (pet.type() == duck) {
  pet.quack()
}

Ich kann davon ausgehen, dass sich dieser Code in Zukunft ändern wird. Ich kann es einfach "einkapseln", indem ich eine neue Prozedur definiere:

void speak(pet) {
  if (pet.type() == dog) {
    pet.bark();
  } else if (pet.type() == cat) {
    pet.meow();
  } else if (pet.type() == duck) {
    pet.quack()
  }
}

und Verwenden dieser neuen Prozedur anstelle des Codeblocks (dh eines Refactorings mit "Extraktionsmethode"). Zu diesem Zeitpunkt muss nur die speakProzedur aktualisiert werden, um einen "Kuh" -Typ oder etwas anderes hinzuzufügen . Natürlich können Sie in einer OO-Sprache stattdessen den dynamischen Versand nutzen, auf den die Antwort von CandiedOrange anspielt. Dies geschieht natürlich, wenn Sie petüber eine Schnittstelle zugreifen . Die Beseitigung der bedingten Logik durch dynamisches Versenden ist eine orthogonale Angelegenheit, die ein Teil des Grundes war, warum ich diese prozedurale Wiedergabe gemacht habe. Ich möchte auch betonen, dass dies keine OOP-spezifischen Funktionen erfordert. Selbst in einer OO-Sprache muss nicht unbedingt eine neue Klasse oder Schnittstelle erstellt werden, um die Variationen zu kapseln.

Als eher archetypisches Beispiel (das näher an, aber nicht ganz OO ist) möchten wir die Duplikate aus einer Liste entfernen. Nehmen wir an, wir implementieren es, indem wir die Liste nachverfolgen, die wir bisher in einer anderen Liste gesehen haben, und alle Elemente entfernen, die wir gesehen haben. Es ist vernünftig anzunehmen, dass wir möglicherweise aus Gründen der Leistung ändern möchten, wie wir den Überblick über die angezeigten Elemente behalten. Das Sprichwort, was variiert, legt nahe, dass wir einen abstrakten Datentyp erstellen sollten, um die Menge der gesehenen Elemente darzustellen. Unser Algorithmus ist jetzt für diesen abstrakten Datentyp "Set" definiert. Wenn wir zu einem binären Suchbaum wechseln, muss sich unser Algorithmus nicht ändern oder kümmern. In einer OO-Sprache verwenden wir möglicherweise eine Klasse oder Schnittstelle, um diesen abstrakten Datentyp zu erfassen. In einer Sprache wie SML / O '

Angenommen, Sie müssen für ein anforderungsorientiertes Beispiel ein Feld in Bezug auf eine Geschäftslogik validieren. Möglicherweise haben Sie jetzt bestimmte Anforderungen, aber Sie vermuten stark, dass sich diese weiterentwickeln werden. Sie können die aktuelle Logik in einer eigenen Prozedur / Funktion / Regel / Klasse einkapseln.

Obwohl dies ein orthogonales Anliegen ist, das nicht Teil von "verkapseln, was variiert" ist, ist es oft natürlich, das zu abstrahieren, was durch die jetzt verkapselte Logik parametrisiert wird. Dies führt normalerweise zu flexiblerem Code und ermöglicht das Ändern der Logik durch Ersetzen in einer alternativen Implementierung, anstatt die gekapselte Logik zu modifizieren.

Derek Elkins
quelle
Oh bittersüße Ironie. Ja, dies ist nicht nur ein OOP-Problem. Sie haben mich dabei erwischt, wie ein Sprachparadigma meine Antwort verschmutzen ließ und mich zu Recht dafür bestraft, dass ich das Paradigma "veränderte".
candied_orange
„Auch in einer OO - Sprache, Einkapselung , was variiert , bedeutet nicht unbedingt eine neue Klasse oder Schnittstelle Bedürfnisse zu erstellenden“ - es ist schwer , sich eine Situation vorzustellen , wo nicht eine neue Klasse oder Schnittstelle zu schaffen würde SRP nicht verletzen
taurelas
11

"Verkapseln, was variiert" bezieht sich auf das Verbergen von Implementierungsdetails, die sich ändern und weiterentwickeln können.

Beispiel:

Angenommen, die Klasse Courseverfolgt, Studentsdass register () registriert werden kann. Sie können es mit a implementieren LinkedListund den Container verfügbar machen, um eine Iteration zu ermöglichen:

class Course { 
    public LinkedList<Student> Atendees; 
    public bool register (Student s);  
    ...
}

Das ist aber keine gute Idee:

  • Erstens könnten die Leute an gutem Benehmen mangeln und es als Selbstbedienung verwenden, indem sie die Schüler direkt zur Liste hinzufügen, ohne die register () -Methode durchzugehen.
  • Noch ärgerlicher: Dadurch entsteht eine Abhängigkeit des "using-Codes" von den inneren Implementierungsdetails der verwendeten Klasse. Dies kann zukünftige Entwicklungen der Klasse verhindern, wenn Sie beispielsweise ein Array, einen Vektor, eine Karte mit der Sitznummer oder Ihre eigene beständige Datenstruktur bevorzugen.

Wenn Sie verkapseln, was variiert (oder vielmehr gesagt wird, was variieren könnte), behalten Sie die Freiheit, dass sowohl der verwendende Code als auch die gekapselte Klasse sich gegenseitig entwickeln können. Deshalb ist es ein wichtiges Prinzip in OOP.

Zusätzliche Lektüre:

Christophe
quelle