Sollte eine Klasse etwas über ihre Unterklassen wissen?


24

Sollte eine Klasse etwas über ihre Unterklassen wissen? Sollte eine Klasse beispielsweise etwas tun, das für eine bestimmte Unterklasse spezifisch ist?

Mein Instinkt sagt mir, dass dies ein schlechtes Design ist, es scheint eine Art Anti-Muster zu sein.


6
Normalerweise nein, aber in besonderen Fällen ist es nützlich.
CodesInChaos

Es ist ein Anti-Muster, ein gut bekanntes genannt: „Liskov Substitutionsprinzip“ oder manchmal auch nur LSP, lesen Sie es auf en.wikipedia.org/wiki/Liskov_substitution_principle
Jimmy Hoffa

5
@JimmyHoffa - "Liskov Substitution Principle" ist nicht der Name eines Anti-Patterns. Ein Anti-Pattern kann gegen LSP verstoßen, aber es ist nicht einmal sicher, ob dies hier zutrifft. Es ist möglich, dass, selbst wenn ein Typ seine Subtypen kennt, die Subtypen immer noch durch Kontravarianz und Kovarianz für seine Eltern ersetzt werden können.
Matthew Flynn

4
@MatthewFlynn Vielleicht verwende ich LSP ein wenig locker, aber in meiner Definition ist es nicht die Tatsache, dass sie alle die Anforderungen des jeweils anderen erfüllen, die mit LSP übereinstimmen. Es ist eine Anforderung, dass sie eine Entkopplung haben, so dass sie nicht nur miteinander übereinstimmen, sondern auch Sie könnten eine andere Unterklasse implementieren, die nicht gegen LSP verstößt. Wenn die Hierarchie sich selbst bewusst ist, kann eine externe Implementierung LSP möglicherweise nicht erfüllen, ohne die Basisklasse zu ändern. Vielleicht ist das nicht streng LSP, aber es ist mächtig nah. und ja, ich hätte sagen sollen, LSP- Verletzung ist das Anti-Pattern
Jimmy Hoffa

2
In den wenigen Fällen, in denen ich darauf gestoßen bin, habe ich dieses Wissen zu einer Methode umgestaltet, die in einer Unterklasse überschrieben werden kann (entweder indem sie abstrakt oder rein virtuell gemacht wird oder indem eine sinnvolle Standardimplementierung bereitgestellt wird, die überschrieben werden kann).

Antworten:


32

Die Antwort des Klassenbegriffs lautet "nein".

Unabhängig davon, welche Aktion, Daten oder Relationen Sie verarbeiten, sind sie Teil aller Unterklassen. Sie sollten sie in der Oberklasse verarbeiten, ohne den tatsächlichen Typ zu überprüfen. Oder es gilt nur für einige Unterklassen - dann müssten Sie Laufzeit-Typprüfungen durchführen, um das Richtige zu tun, die Superklasse müsste geändert werden, wenn jemand anderes davon erbt (oder es könnte stillschweigend das Falsche tun). Änderungen in den abgeleiteten Klassen können die unveränderte Oberklasse auflösen usw.

Kurz gesagt, es gibt eine Reihe von schlimmen Konsequenzen, die normalerweise so schlimm sind, dass solche Lösungen sofort abgelehnt werden. Wenn mehrere Ihrer Unterklassen dasselbe tun und Sie eine Codeduplizierung vermeiden möchten (praktisch immer eine gute Sache), ist es eine bessere Lösung, eine Mittelklasse einzuführen, von der alle diese Unterklassen den Code erben können.


4
Eine Ausnahme von dieser Regel: In der Dokumentation der Klasse sollten die Unterklassen lokal für ihre eigene Bibliothek aufgelistet sein.
Kieveli

3
@ Kieveli ... solange diese Dokumentation auf dem neuesten Stand ist.
Mouviciel

14
Jedes verwendbare Dokumentationswerkzeug sollte in der Lage sein, Vererbungsbäume zu zeichnen ...
Johannes

13
Ich denke nicht, dass es eine Ausnahme ist. Die Klasse weiß immer noch nichts. Die Dokumentation weiß.
Honza Brabec

4
@Kieveli uhh ich völlig anderer Meinung.
Jimmy Hoffa

16

Nicht nur sollte es nicht wissen, es kann einfach nicht ! Normalerweise kann eine Klasse jederzeit und überall erweitert werden. Es kann um Klassen erweitert werden, die zum Zeitpunkt der Erstellung noch nicht vorhanden waren.

In einigen Sprachen können erweiterte Klassen von der Oberklasse gesteuert werden. In Scala kann eine Klasse als markiert werden sealed, was bedeutet, dass sie nur durch andere Klassen innerhalb derselben Kompilierungseinheit (Quelldatei) erweitert werden kann. Sofern diese Unterklassen nicht auch sealed oder sind final, können die Unterklassen dann durch andere Klassen erweitert werden.

In Scala wird dies verwendet, um geschlossene algebraische Datentypen zu modellieren, also den kanonischen Haskell- ListTyp:

data List a = Nil | Cons a (List a)

kann in Scala folgendermaßen modelliert werden:

sealed trait List[+A]

case object Nil extends List[Nothing]

final case class Cons[+A] extends List[A]

Und Sie können garantieren , dass nur diejenigen zwei Unter „Klassen“ existieren , weil Listist sealedund somit nicht längere außerhalb der Datei sein kann, Consist finalund somit überhaupt nicht erweitert werden und Nilist eine , objectdie nicht ohnehin verlängert werden kann.

Dies ist jedoch ein spezieller Anwendungsfall (Modellierung algebraischer Datentypen über Vererbung), und selbst in diesem Fall weiß die Oberklasse nichts über ihre Unterklassen. Es ist mehr eine Garantie für den Benutzer der ListArt , dass , wenn er einen Fall von Diskriminierung der tut Nilund Conses wird nicht sein , andere Alternative hinter seinem Rücken auftauchen.


Was in vielen Sprachen funktioniert, ist das Hinzufügen einer Art abstract internalMitglied.
CodesInChaos

4

Die einfache Antwort lautet Nein.

Es macht Code spröde und sperrt zwei Grundprinzipien der objektorientierten Programmierung.

  • Liskov Substitutionsprinzip
  • Open Close Prinzip

1

Ja manchmal. Zum Beispiel, wenn es eine begrenzte Anzahl von Unterklassen gibt. Ein Besuchermuster ist ein Beispiel für die Nützlichkeit dieses Ansatzes.

Beispiel: Ein AST-Knoten (Abstract Syntax Tree) mit einer genau definierten Grammatik kann von einer einzelnen NodeKlasse geerbt werden, die ein Besuchermuster implementiert, um alle Knotentypen zu verarbeiten.


9
Nein, nein und nein. Dies ist nicht sinnvoll und niemals eine gute Lösung.
Sulthan

@ Sulthan Ich habe mit ASTs außerhalb des Klassenzimmers gearbeitet und ich glaube, Lorus versteht die Frage nicht wirklich. Ein Besucher ist kein Knotentyp auf einem AST, sondern ein separates Objekt. Es kann und sollte über die Grammatikobjekte Bescheid wissen. Ein AST-Knoten auf hoher Ebene sollte dies jedoch nicht tun.

1
@ JohnGaughan Der Begriff "weiß" verwirrt mich. Die Basisklasse enthält Nodenatürlich keine direkten Verweise auf ihre Unterklassen. Es enthält jedoch eine acceptMethode mit einem VisitorParameter. Und Visitorenthält eine visitMethode pro Unterklasse. Obwohl Nodees keine direkten Verweise auf seine Unterklassen gibt, "weiß" es über die VisitorSchnittstelle indirekt über sie Bescheid . Sie alle verbanden sich dadurch.
Lorus

1
@ Sulthan Sag niemals nie. Der Optionstyp ist ein gültiges Beispiel für den Fall, dass die Oberklasse den untergeordneten Typ kennt. Aber wie Jörg sagte, würde es als versiegelt markiert werden.
Pavel Voronin

Dann sind Sie sich einig: Ein Besucher muss wissen, was er besucht.

1

Wenn ich eine Komponente für eine Firma schreibe und später, nachdem ich gegangen bin, jemand sie für eigene Zwecke erweitert, sollte ich darüber informiert werden?

Nein!

Gleiches gilt für den Unterricht. Vertraue deinen Instinkten.


Berufssicherheit???
Sixtyfootersdude

Du bist dreißig Zentimeter hoch, also werde ich nicht mit dir streiten :-) Ich hoffe jedoch, meine Bedeutung war, nachdem ich gegangen bin und einen anderen Job bekommen habe.
Daniel Hollinrake
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.