Sollen alle öffentlichen Methoden in einer abstrakten Klasse als virtuell markiert werden?

12

Ich musste kürzlich eine abstrakte Basisklasse auf einem von mir verwendeten OSS aktualisieren, damit es durch virtuelle Erstellung besser getestet werden kann (ich konnte kein Interface verwenden, da es zwei kombiniert). Dies brachte mich zu der Überlegung, ob ich alle Methoden, die ich brauchte, als virtuell markieren sollte, oder ob ich jede öffentliche Methode / Eigenschaft als virtuell markieren sollte. Ich stimme im Allgemeinen Roy Osherove zu, dass jede Methode virtuell gemacht werden sollte, aber ich bin auf diesen Artikel gestoßen, der mich dazu brachte, darüber nachzudenken, ob dies notwendig war oder nicht . Ich werde dies der Einfachheit halber auf abstrakte Klassen beschränken (ob alle konkreten öffentlichen Methoden virtuell sein sollten, ist allerdings besonders fraglich, da ich mir sicher bin).

Ich könnte sehen, wo Sie einer Unterklasse erlauben möchten, eine Methode zu verwenden, aber nicht, dass sie die Implementierung überschreibt. Wenn Sie jedoch darauf vertrauen, dass das Substitutionsprinzip von Liskov eingehalten wird, warum sollten Sie dann nicht zulassen, dass es außer Kraft gesetzt wird? Wenn Sie es als abstrakt kennzeichnen, erzwingen Sie trotzdem eine bestimmte Überschreibung. Daher sollten meines Erachtens alle öffentlichen Methoden innerhalb einer abstrakten Klasse tatsächlich als virtuell gekennzeichnet werden.

Ich wollte jedoch fragen, ob es etwas gibt, an das ich nicht denken könnte. Sollen alle öffentlichen Methoden innerhalb einer abstrakten Klasse virtuell gemacht werden?

Justin Pihony
quelle
Dies kann helfen: stackoverflow.com/questions/391483/…
NoChance
1
Vielleicht sollten Sie untersuchen, warum in C # die Standardeinstellung nicht virtuell ist, in Java jedoch. Der Grund, warum Sie wahrscheinlich einen Einblick in die Antwort geben würden.
Daniel Little

Antworten:

9

Wenn Sie jedoch darauf vertrauen, dass das Substitutionsprinzip von Liskov eingehalten wird, warum sollten Sie dann nicht zulassen, dass es außer Kraft gesetzt wird?

Zum Beispiel, weil ich möchte, dass die Skelettimplementierung eines Algorithmus behoben wird und nur bestimmte Teile durch Unterklassen (neu) definiert werden. Dies ist allgemein als Template-Methode bekannt (Hervorhebung von mir unten):

Die Template-Methode verwaltet somit das Gesamtbild der Aufgabensemantik und verfeinert die Implementierungsdetails der Auswahl und Reihenfolge der Methoden. Dieses größere Bild nennt abstrakte und nicht abstrakte Methoden für die jeweilige Aufgabe. Die nicht abstrakten Methoden werden vollständig von der Template-Methode gesteuert, aber die in Unterklassen implementierten abstrakten Methoden liefern die Ausdruckskraft und den Freiheitsgrad des Musters. Einige oder alle der abstrakten Methoden können auf eine Unterklasse spezialisiert sein, sodass der Schreiber der Unterklasse ein bestimmtes Verhalten mit minimalen Änderungen an der größeren Semantik bereitstellen kann. Die Template-Methode (die nicht abstrakt ist) bleibt in diesem Muster unverändert und stellt sicher, dass die untergeordneten nicht abstrakten Methoden und abstrakten Methoden in der ursprünglich beabsichtigten Reihenfolge aufgerufen werden.

Aktualisieren

Einige konkrete Beispiele für Projekte, an denen ich gearbeitet habe:

  1. Kommunikation mit einem alten Mainframe-System über verschiedene "Bildschirme". Jeder Bildschirm enthält eine Reihe von Feldern mit festem Namen, fester Position und fester Länge, die bestimmte Datenbits enthalten. Eine Anfrage füllt bestimmte Felder mit bestimmten Daten. Eine Antwort gibt Daten in einem oder mehreren anderen Feldern zurück. Jede Transaktion folgt der gleichen Grundlogik, aber die Details sind auf jedem Bildschirm unterschiedlich. Wir haben die Template-Methode in verschiedenen Projekten verwendet, um das feste Grundgerüst der Logik für die Bildschirmverarbeitung zu implementieren, und Unterklassen die Möglichkeit gegeben, bildschirmspezifische Details zu definieren.
  2. Exportieren / Importieren von Konfigurationsdaten in DB-Tabellen in / aus Excel-Dateien. Das grundlegende Schema zum Verarbeiten einer Excel-Datei und zum Einfügen / Aktualisieren von DB-Datensätzen oder zum Speichern der Datensätze in Excel ist für jede Tabelle identisch, die Details der einzelnen Tabellen sind jedoch unterschiedlich. Die Template-Methode ist daher eine naheliegende Wahl, um Code-Duplikationen zu vermeiden und das Verständnis und die Pflege des Codes zu vereinfachen.
  3. PDF-Dokumente generieren. Jedes Dokument hat die gleiche Struktur, aber sein Inhalt hängt von vielen Faktoren ab. Auch hier macht es die Template-Methode einfach, das feste Gerüst des Generierungsalgorithmus von den fallspezifischen, veränderbaren Details zu trennen. Eigentlich. Es gilt hier sogar auf mehreren Ebenen, da das Dokument aus mehreren Abschnitten besteht , von denen jeder aus null oder mehr Feldern besteht . Die Vorlagenmethode wird hier auf 3 verschiedenen Ebenen angewendet.

In den ersten beiden Fällen verwendete die ursprüngliche Legacy-Implementierung Strategy , was zu vielen duplizierten Codes führte, die natürlich über die Jahre hin und wieder geringfügige Unterschiede aufwiesen, viele duplizierte oder leicht unterschiedliche Fehler enthielten und sehr schwer zu warten waren. Durch das Refactoring auf die Vorlagenmethode (und einige andere Verbesserungen, z. B. die Verwendung von Java-Anmerkungen) wurde die Codegröße um etwa 40-70% reduziert.

Dies sind nur die jüngsten Beispiele, die mir in den Sinn kommen. Ich könnte aus fast jedem Projekt, an dem ich bisher gearbeitet habe, weitere Fälle anführen.

Péter Török
quelle
Einfach die GoF zu zitieren ist keine Antwort. Sie müssten einen tatsächlichen Grund angeben, um so etwas zu tun.
DeadMG
@DeadMG, ich habe während meiner Karriere regelmäßig die Template-Methode verwendet, daher dachte ich, es sei offensichtlich, dass es ein sehr praktisches und nützliches Muster ist (da die meisten GoF-Muster ... keine theoretischen akademischen Übungen sind, sondern gesammelt aus der realen Welt Erfahrung). Aber anscheinend sind nicht alle damit einverstanden. Deshalb füge ich meiner Antwort ein paar konkrete Beispiele hinzu.
Péter Török
2

Es ist durchaus sinnvoll und manchmal wünschenswert, nicht-virtuelle Methoden in einer abstrakten Basisklasse zu haben. Nur weil es sich um eine abstrakte Klasse handelt, muss nicht jeder Teil polymorph verwendbar sein.

Sie können beispielsweise die Redewendung "Nicht-virtueller Polymorphismus" verwenden, bei der eine Funktion von einer nicht-virtuellen Elementfunktion polymorph aufgerufen wird, um sicherzustellen, dass bestimmte Vor- oder Nachbedingungen erfüllt sind, bevor die virtuelle Funktion aufgerufen wird

class MyAbstractBaseClass
{
protected:
    virtual void OverrideMe() = 0;
public:
    void CallMeFirst();

    void CallMe()
    {
        CallMeFirst();
        OverrideMe();
    }
};
Ben Cottrell
quelle
Ich würde argumentieren, dass dies, solange das Gesamtverhalten gleich bleibt, virtuell gemacht werden könnte. Sie können die Vor- / Nachbedingungen mit einer anderen Implementierung (Datenbank vs. In-Memory) füllen. Ansonsten sollte es eine private Funktion sein?
Justin Pihony
2

Es reicht aus, wenn eine Klasse EINE virtuelle Methode enthält, damit die Klasse abstrakt wird. Möglicherweise möchten Sie darauf achten, welche Methoden Sie virtuell verwenden möchten und welche nicht, je nachdem, welche Art von Polymorphismus Sie verwenden möchten.

appoll
quelle
1

Fragen Sie sich, welche Verwendung eine nicht virtuelle Methode in einer abstrakten Klasse hat. Eine solche Methode müsste implementiert werden, damit sie nützlich ist. Aber wenn die Klasse eine Implementierung hat, kann sie trotzdem als abstrakte Klasse bezeichnet werden? Auch wenn die Sprache / der Compiler dies zulässt, macht es Sinn? Persönlich glaube ich nicht. Sie hätten eine normale Klasse mit abstrakten Methoden, die von Nachkommen implementiert werden sollen.

Meine Hauptsprache ist Delphi, nicht C #. Wenn Sie in Delphi eine Methodenzusammenfassung markieren, müssen Sie diese auch als virtuell markieren, oder der Compiler beschwert sich. Ich habe die letzten Sprachänderungen nicht zu genau verfolgt, aber wenn abstrakte Klassen in Delphi sind oder zu Delphi kommen würden, würde ich erwarten, dass sich der Compiler über nicht virtuelle Methoden, private Methoden und Methodenimplementierungen für eine als abstrakt gekennzeichnete Klasse beschwert auf Klassenebene.

Marjan Venema
quelle
2
Ich denke, es ist vollkommen sinnvoll, eine abstrakte Klasse zu haben, die einige abstrakte Methoden, einige virtuelle Methoden und einige nicht virtuelle Methoden enthält. Ich denke, es kommt immer darauf an, was genau du machen willst.
Svick
3
Zuerst werde ich Ihre C # qs durchgehen. Eine abstrakte Methode in C # ist implizit virtuell und kann nicht implementiert werden. Was Ihren ersten Punkt betrifft, so kann und sollte eine abstrakte Klasse in C # eine Art Implementierung haben (andernfalls sollten Sie nur eine Schnittstelle verwenden). Der Sinn einer abstrakten Klasse ist, dass sie in Unterklassen unterteilt werden muss, sie enthält jedoch Logik, die (theoretisch) alle Unterklassen verwenden. Dies reduziert die Codeduplizierung. Was ich frage, ist, ob eine dieser Implementierungen nicht überschrieben werden darf (im Grunde genommen ist der Basisweg der einzige Weg).
Justin Pihony
@ JustinPihony: danke. Wenn Sie in Delphi eine Methodenzusammenfassung markieren, beschwert sich der Compiler, wenn Sie eine Implementierung dafür bereitstellen. Interessant, wie unterschiedliche Sprachen Konzepte unterschiedlich umsetzen und so unterschiedliche Erwartungen bei ihren Nutzern wecken.
Marjan Venema
@svick: Ja, das macht durchaus Sinn. Ich würde es nicht als abstrakte Klasse bezeichnen, sondern als Klasse mit abstrakten Methoden. Aber ich denke, das ist nur meine Interpretation.
Marjan Venema
@ MarjanVenema, aber das ist keine in C # verwendete Terminologie. Wenn Sie Methoden in einer Klasse abstractmarkieren, müssen Sie auch die gesamte Klasse markieren abstract.
Svick
0

Solange Sie jedoch darauf vertrauen, dass das Substitutionsprinzip von Liskov eingehalten wird, warum sollten Sie dann nicht zulassen, dass es außer Kraft gesetzt wird?

Sie machen bestimmte Methoden nicht virtuell, weil Sie nicht glauben, dass dies der Fall ist. Indem Sie bestimmte Methoden nicht virtuell machen, signalisieren Sie den Erben, welche Methode (n) implementiert werden sollen.

Persönlich mache ich häufig Methodenüberladungen, die der Einfachheit halber nicht virtuell sind, sodass Benutzer der Klasse konsistente Standardeinstellungen haben können und Implementierer nicht einmal in der Lage sind, den Fehler zu machen, dieses implizierte Verhalten zu brechen.

Telastyn
quelle