Wann sollte Vererbung verwendet werden, wann nur ein boolesches Feld?

18

In unserer Rails-Anwendung fügen wir Benachrichtigungen hinzu. Einige davon sind blocking: Sie stoppen den Fortschritt der hinzugefügten Ressource, da einige Informationen zu dieser Ressource fehlen.

Andere Benachrichtigungen sind einfache Benachrichtigungen, die nur Informationen enthalten.

Heute hatte ich eine Diskussion mit einem anderen Programmierer in unserem Team. Ich habe die Vererbungsstruktur wie folgt erstellt:

Bildbeschreibung hier eingeben

Er möchte jedoch lieber, dass ich nur blockingeine boolesche Rückgabemethode für jede Benachrichtigung hinzufüge und eine Liste von Unterklassen spezifiziere, die innerhalb der übergeordneten Klasse "Benachrichtigung" blockiert werden.

Der Unterschied zwischen diesen Ansätzen ist nicht sehr groß; In meinem Ansatz muss man diese Liste nicht spezifizieren, um die Stammklasse sauberer zu halten. Auf der anderen Seite ist die spezielle Logik, die gerade auftritt, auch Notification::Blockingnicht sehr groß.

Welche Art von Abstraktion ist für dieses Problem besser geeignet?

Qqwy
quelle
11
Eine Elternklasse sollte niemals etwas über ihre Kinder erfahren. Warum müssen Sie eine Liste von Unterklassen führen?
Coteyr
Wie werden sie einer Ressource hinzugefügt und wie stoppen sie ihren Fortschritt?
null
3
Warum brauchen Sie so viele Benachrichtigungsklassen? Klingt für mich so, als könnten Sie eine Benachrichtigungsklasse erstellen und dann die Aktionen anstelle der Datentypen von den Daten steuern lassen.
Trisped
1
@Trisped: Richtig, wenn Sie feststellen, dass Sie einen Aspekt des Verhaltens mit einer vollständigen Liste von Fällen in die Basisklasse verschieben, dann haben Sie den Mut zu Ihrer Überzeugung und geben zu, dass Sie nicht wirklich eine verwendbare Basisklasse für die Anpassung durch Erweiterung entwerfen , und verschieben Sie das gesamte Verhalten in die Basisklasse!
Steve Jessop

Antworten:

35

Sie möchten vermeiden, dass Basisklassen über abgeleitete Klassen Bescheid wissen. Es führt zu einer engen Kopplung und bereitet Wartungsprobleme, da Sie jedes Mal, wenn Sie eine neue abgeleitete Klasse erstellen, daran denken müssen, die Liste zu erweitern.

Außerdem wird verhindert, dass Sie die Notification-Klasse in ein wiederverwendbares Paket / eine wiederverwendbare Assembly einfügen können, wenn Sie diese Klasse in mehreren Projekten verwenden möchten.

Wenn Sie wirklich eine einzelne Basisklasse verwenden möchten, können Sie dieses Problem auch lösen, indem Sie der Basisklasse Notification eine virtuelle Eigenschaft oder Methode IsBlocking hinzufügen. Abgeleitete Klassen können dies dann überschreiben, um true oder false zurückzugeben. Sie hätten eine Einzelklassenlösung, ohne dass die Basisklasse über abgeleitete Klassen Bescheid weiß.

17 von 26
quelle
3
Dies. Treffen Sie Entscheidungen an einem Ort. Verteile nicht das Wissen darüber, welche Klassen zwischen der Klasse und der Liste blockieren.
candied_orange
Dies ist die, die ich mache, funktioniert sehr gut und ist sehr wiederverwendbar.
Coteyr
13

und geben Sie eine Liste der Unterklassen an, die in der übergeordneten Klasse Notification blockiert werden.

Das sieht sehr eigenartig aus und ist ein besonderer Codegeruch.

Ich würde Unterklassen bereitstellen, wenn Sie unterschiedliche Verhaltensweisen zwischen den Klassen aufweisen und alle diese Benachrichtigungen auf dieselbe Weise behandeln möchten (dh mit Polymorphismus ).

Brian Agnew
quelle
1
Ich denke, "Verhalten" ist hier der Schlüssel: Wenn es sich nur um Daten handelt, sollte das Feld ein ausreichendes Unterscheidungsmerkmal sein. Verhalten ist der bessere Grund für die Verwendung von Polymorphismus. Bei der Erstellung von Vererbungshierarchien sollte jedoch immer die Komplexität der Wartung berücksichtigt werden. Lesen Sie en.wikipedia.org/wiki/Composition_over_inheritance
cottsak
7

Im Gegensatz zu den vorhandenen Antworten würde ich vorschlagen, dass die boolesche Eigenschaft die beste Option ist, wenn der zu verwendende Modus dynamisch geändert werden muss (z. B. über eine Konfigurationsdatei, die eine Liste der zu blockierenden Typen enthält und welche nicht).

Das heißt, ein besseres Design selbst in dieser Situation könnte darin bestehen, ein Decorator-Objekt zu verwenden.

Jules
quelle
1

Ich würde sagen, es hängt davon ab, wie viel das Besondere an einer Sperrbenachrichtigung ist. Mein erster Gedanke ist jedoch, "beides" zu wählen:

class Notification
 virtual Boolean Blocking{get return false;}

class BlockingNotification inherits Notification
 virtual overrides Boolean Blocking{get return true;}

Auf diese Weise können Sie verwenden , n.Blockingoder n is BlockingNotification(alle in Pseudo-Code), obwohl, wenn Sie einer Klasse zu ermöglichen , gehen zu einem kontextsensitiven umzusetzen BlockingWert, da Sie jedes Mal, wenn Wert überprüfen müßten, das BlockingNotificationwird Klasse weniger nützlich.

In jedem Fall stimme ich den anderen Antworten zu, bei denen Sie nicht möchten, dass die Basisklassenimplementierung etwas Blockingüber die abgeleiteten Klassen weiß.

Mark Hurd
quelle
0

Anstatt zwei Basisklassen und jeweils mehrere Instanzen zu erstellen, erstellen Sie eine Benachrichtigungsklasse mit einem Bool, um anzugeben, ob die Benachrichtigung blockiert ist, und um weitere Informationen zu erhalten, die zur Übermittlung der Benachrichtigung an den Benutzer erforderlich sind.

Auf diese Weise können Sie eine Codemenge zum Verarbeiten und Präsentieren von Benachrichtigungen verwenden und die Komplexität Ihres Codes verringern.

Trisped
quelle