Warum sollten statische Methoden nicht überschreibbar sein können?


13

Bei der Beantwortung dieser Frage herrschte allgemeiner Konsens darüber, dass statische Methoden nicht überschrieben werden sollen (und statische Funktionen in C # daher nicht virtuell oder abstrakt sein können). Dies ist jedoch nicht nur in C # der Fall. Java verbietet dies auch und C ++ scheint es auch nicht zu mögen. Ich kann mir jedoch viele Beispiele für statische Funktionen vorstellen, die ich in einer untergeordneten Klasse überschreiben möchte (z. B. Factory-Methoden). Theoretisch gibt es zwar Möglichkeiten, sie zu umgehen, aber keine davon ist sauber oder einfach.

Warum sollten statische Funktionen nicht überschreibbar sein?


4
Delphi unterstützt Klassenmethoden , die statischen Methoden ziemlich ähnlich sind und überschrieben werden können. Sie erhalten einen selfZeiger, der auf die Klasse und nicht auf eine Instanz der Klasse verweist.
CodesInChaos

Eine Definition von Statik ist: fehlende Veränderung. Ich bin neugierig, warum Sie eine Funktion statisch gemacht haben, die Sie überschreiben möchten.
JeffO

4
@JeffO: Diese englische Definition von "static" hat absolut nichts mit der einzigartigen Semantik statischer Elementfunktionen zu tun.
Leichtigkeit Rennen mit Monica

5
Ihre Frage lautet: "Warum sollten statische Methoden statisch typisierten Versand anstelle von einfachem virtuellem Versand verwenden?" Wenn Sie die Frage so formulieren, denke ich, dass sie sich von selbst beantwortet.
Eric Lippert

1
@EricLippert Die Frage könnte besser formuliert werden als "Warum bietet C # statische Methoden anstelle von Klassenmethoden an?", Aber es ist immer noch eine gültige Frage.
CodesInChaos

Antworten:


13

Bei den statischen Methoden besteht keine Aufgabe darin, den Übersteuerungsmechanismus ordnungsgemäß zu steuern.

Der Mechanismus der normalen virtuellen Klasse / Instanz-Methode ermöglicht die fein abgestimmte Steuerung von Überschreibungen wie folgt: Jedes reale Objekt ist eine Instanz von genau einer Klasse. Diese Klasse bestimmt das Verhalten der Überschreibungen. es bekommt immer den ersten Riss bei virtuellen Methoden. Sie kann dann die übergeordnete Methode zum richtigen Zeitpunkt für die Implementierung aufrufen. Jede übergeordnete Methode ruft dann auch ihre übergeordnete Methode auf. Dies führt zu einer Kaskade von übergeordneten Aufrufen, die einen der Begriffe der Wiederverwendung von Code erfüllt, für den die Objektorientierung bekannt ist. (Hier wird der Code der Basis- / Superklasse in relativ komplexer Weise wiederverwendet. Ein weiterer orthogonaler Begriff der Wiederverwendung von Code in OOP besteht darin, einfach mehrere Objekte derselben Klasse zu haben.)

Basisklassen können von verschiedenen Unterklassen wiederverwendet werden und jede kann problemlos koexistieren. Jede Klasse, die verwendet wird, um Objekte zu instanziieren, die ihr eigenes Verhalten diktieren, friedlich und gleichzeitig mit den anderen koexistieren. Der Client hat die Kontrolle darüber, welches Verhalten er wann haben möchte, indem er wählt, mit welcher Klasse ein Objekt instanziiert und wie gewünscht an andere weitergegeben wird.

(Dies ist kein perfekter Mechanismus, da man natürlich immer Fähigkeiten identifizieren kann, die nicht unterstützt werden, weshalb Muster wie die Factory-Methode und die Abhängigkeitsinjektion überlagert sind.)

Wenn wir also eine Override-Funktion für die Statik erstellen würden, ohne etwas anderes zu ändern, hätten wir Schwierigkeiten, die Overrides zu bestellen. Es ist schwierig, einen begrenzten Kontext für die Anwendbarkeit der Außerkraftsetzung zu definieren, sodass die Außerkraftsetzung eher global als lokal wie bei Objekten erfolgt. Es gibt kein Instanzobjekt, um das Verhalten zu ändern. Wenn also jemand die statische Methode aufgerufen hat, die zufällig von einer anderen Klasse überschrieben wurde, sollte die Überschreibung dann die Kontrolle erhalten oder nicht? Wenn es mehrere solcher Überschreibungen gibt, wer erhält zuerst die Kontrolle? zweite? Bei Überschreibungen von Instanzobjekten haben diese Fragen alle sinnvolle und gut begründete Antworten, bei statischen Fragen jedoch nicht.

Überschreibungen für Statik wären im Wesentlichen chaotisch, und solche Vorgänge wurden bereits durchgeführt.

Beispielsweise verwendeten das Mac OS System 7 und frühere Versionen einen Trap-Patch-Mechanismus, um das System zu erweitern, indem die Kontrolle über von Anwendungen ausgeführte Systemaufrufe vor dem Betriebssystem erlangt wurde. Sie können sich die Systemaufruf-Patch-Tabelle als ein Array von Funktionszeigern vorstellen , ähnlich einer vtable für Instanzobjekte , mit der Ausnahme, dass es sich um eine einzelne globale Tabelle handelt.

Dies verursachte den Programmierern aufgrund der ungeordneten Art des Trap-Patches ungeahnten Kummer. Wer zuletzt die Falle flicken musste, gewann im Grunde genommen, auch wenn er es nicht wollte. Jeder Patcher des Traps erfasste den vorherigen Trap-Wert für eine Art übergeordneter Aufruffunktion, die äußerst zerbrechlich war. Das Entfernen eines Trap-Patches, wenn Sie nicht mehr über einen Systemaufruf Bescheid wissen mussten, wurde als fehlerhaft eingestuft, da Sie nicht über die erforderlichen Informationen zum Entfernen Ihres Patches verfügten Sie).

Dies soll nicht heißen, dass es unmöglich wäre, einen Mechanismus zum Überschreiben von Statik zu erstellen, aber ich würde es wahrscheinlich vorziehen, stattdessen die statischen Felder und statischen Methoden in Instanzfelder und Instanzmethoden von Metaklassen umzuwandeln, so dass das normale Objekt Dann würden Orientierungstechniken angewendet. Beachten Sie, dass es auch Systeme gibt, die dies tun: CSE 341: Smalltalk-Klassen und Metaklassen ; Siehe auch: Was ist das Smalltalk-Äquivalent der Java-Statik?


Ich versuche zu sagen, dass Sie einige ernsthafte Sprachfeatures entwerfen müssten, damit es auch einigermaßen gut funktioniert. Zum Beispiel wurde eine naive Herangehensweise unternommen, die hinkte, aber sehr problematisch war und möglicherweise (dh ich würde argumentieren) architektonisch fehlerhaft war, indem eine unvollständige und schwer zu verwendende Abstraktion bereitgestellt wurde.

Wenn Sie mit dem Entwerfen der Funktion für statische Überschreibungen fertig sind, haben Sie möglicherweise gerade eine Form von Metaklassen erfunden, die eine natürliche Erweiterung von OOP in / für klassenbasierte Methoden darstellt. Es gibt also keinen Grund, dies nicht zu tun - und einige Sprachen tun dies tatsächlich. Vielleicht ist es nur eine Randbedingung, dass eine Reihe von Sprachen dies nicht tun.


Wenn ich das richtig verstehe: Es geht nicht darum, ob Sie theoretisch so etwas tun würden oder sollten, sondern eher programmgesteuert, ob die Implementierung schwierig und nicht unbedingt nützlich wäre.
PixelArtDragon

@ Garan, siehe mein Nachtrag.
Erik Eidt

1
Ich kaufe das Bestellargument nicht; Sie erhalten die Methode, die von der Klasse bereitgestellt wird, für die Sie die Methode aufgerufen haben (oder die erste Oberklasse, die eine Methode gemäß der von Ihrer Sprache gewählten Linearisierung bereitstellt).
Hobbs

1
@PierreArlaud: Hat aber this.instanceMethod()eine dynamische Auflösung. Wenn self.staticMethod()also die gleiche Auflösung vorliegt , nämlich dynamisch, wäre dies keine statische Methode mehr.
Jörg W Mittag

1
Die überschreibende Semantik für statische Methoden muss hinzugefügt werden. Eine hypothetische Java + -Metaklasse muss nichts hinzufügen! Sie erstellen einfach Klassenobjekte, und statische Methoden werden dann zu regulären Instanzmethoden. Sie müssen der Sprache nichts hinzufügen, da Sie nur Konzepte verwenden, die bereits vorhanden sind: Objekte, Klassen und Instanzmethoden. Als Bonus können Sie statische Methoden aus der Sprache entfernen ! Sie können ein Feature hinzufügen, indem Sie ein Konzept und damit die Komplexität aus der Sprache entfernen !
Jörg W Mittag

9

Das Überschreiben hängt vom virtuellen Versand ab: Sie verwenden den Laufzeittyp des thisParameters, um zu entscheiden, welche Methode aufgerufen werden soll. Eine statische Methode hat keinen thisParameter, daher gibt es nichts zu versenden.

Einige Sprachen, insbesondere Delphi und Python, haben einen Zwischenbereich, der dies ermöglicht: Klassenmethoden. Eine Klassenmethode ist keine gewöhnliche Instanzmethode, aber sie ist auch nicht statisch. Es erhält einen selfParameter (amüsanterweise bezeichnen beide Sprachen den thisParameter self), der auf den Objekttyp selbst und nicht auf eine Instanz dieses Typs verweist. Mit diesem Wert steht Ihnen jetzt ein Typ zur Verfügung, auf dem Sie den virtuellen Versand durchführen können.

Leider hat weder die JVM noch die CLR etwas Vergleichbares.


selfHaben die verwendeten Sprachen Klassen als tatsächliche, instanziierte Objekte mit überschreibbaren Methoden - Smalltalk natürlich, Ruby, Dart, Objective C, Self, Python? Im Vergleich zu den Sprachen nach C ++, in denen Klassen keine erstklassigen Objekte sind, auch wenn es einen Reflektionszugriff gibt?
Jerry101

Wollen Sie ganz klar sagen, dass Sie in Python oder Delphi Klassenmethoden überschreiben können?
Erik Eidt

@ ErikEidt In Python ist so ziemlich alles überschreibbar. In Delphi kann eine Klassenmethode virtualgenau wie Instanzmethoden explizit deklariert und dann in einer abgeleiteten Klasse überschrieben werden.
Mason Wheeler

Diese Sprachen bieten also eine Art Vorstellung von Metaklassen, nicht wahr?
Erik Eidt

1
@ ErikEidt Python hat "wahre" Metaklassen. In Delphi ist eine Klassenreferenz ein spezieller Datentyp und keine Klasse für sich.
Mason Wheeler

3

Du fragst

Warum sollten statische Funktionen nicht überschreibbar sein?

Ich frage

Warum sollten Funktionen, die Sie überschreiben möchten, statisch sein?

Bestimmte Sprachen zwingen Sie, die Show in einer statischen Methode zu starten. Aber danach kann man wirklich sehr viele Probleme lösen, ohne statischere Methoden.

Manche Menschen wenden statische Methoden immer dann an, wenn in einem Objekt keine Abhängigkeit vom Zustand besteht. Einige Leute benutzen gerne statische Methoden für die Konstruktion anderer Objekte. Manche Leute meiden statische Methoden so oft wie möglich.

Keiner dieser Leute ist falsch.

Wenn es überschreibbar sein soll, beenden Sie einfach die statische Kennzeichnung. Nichts wird kaputt gehen, weil ein staatenloses Objekt herumfliegt.


Wenn die Sprache Strukturen unterstützt, kann ein Einheitentyp eine Struktur mit der Größe Null sein, die an die logische Eingabemethode übergeben wird. Die Erstellung dieses Objekts ist ein Implementierungsdetail. Und wenn wir anfangen, Implementierungsdetails als die eigentliche Wahrheit zu betrachten, müssen Sie meiner Meinung nach auch berücksichtigen, dass in einer realen Implementierung ein Typ mit der Größe Null nicht tatsächlich instanziiert werden muss (da zum Laden keine Anweisungen erforderlich sind) es oder lagere es).
Theodoros Chatzigiannakis

Und wenn Sie noch mehr "hinter den Kulissen" sprechen (z. B. auf ausführbarer Ebene), können die Umgebungsargumente als Typ in der Sprache definiert werden, und der "echte" Einstiegspunkt des Programms kann eine Instanzmethode von sein Dieser Typ wirkt auf jede Instanz, die vom Betriebssystemlader an das Programm übergeben wurde.
Theodoros Chatzigiannakis

Ich denke, es hängt davon ab, wie Sie es sehen. Wenn beispielsweise ein Programm gestartet wird, lädt das Betriebssystem es in den Speicher und wechselt dann zu der Adresse, an der sich die individuelle Implementierung des Binäreinstiegspunkts durch das Programm befindet (z. B. das _start()Symbol unter Linux). In diesem Beispiel ist die ausführbare Datei das Objekt (sie hat eine eigene "Dispatch-Tabelle" und alles) und der Einstiegspunkt ist die dynamisch versendete Operation. Daher ist der Einstiegspunkt eines Programms von außen betrachtet von Natur aus virtuell und kann auch von innen betrachtet auf einfache Weise virtuell aussehen.
Theodoros Chatzigiannakis

1
"Nichts wird kaputt gehen, weil ein staatenloses Objekt herumfliegt." ++++++
RubberDuck

@TheodorosChatzigiannakis Sie machen ein starkes Argument. Wenn Sie mich sonst nicht überzeugt haben, inspiriert die Linie nicht die Gedankenrichtung, die ich beabsichtigt hatte. Ich habe wirklich versucht, den Leuten die Erlaubnis zu geben, einige der gewohnheitsmäßigeren Praktiken, die sie blindlings mit statischen Methoden in Verbindung bringen, abzuschütteln. Also habe ich aktualisiert. Gedanken?
candied_orange

2

Warum sollten statische Methoden nicht überschreibbar sein können?

Es geht nicht um "sollte".

"Overriding" bedeutet " dynamisch versenden ". "Statische Methode" bedeutet "statisch versenden". Wenn etwas statisch ist, kann es nicht überschrieben werden. Wenn etwas überschrieben werden kann, ist es nicht statisch.

Ihre Frage ist ziemlich ähnlich zu fragen: "Warum sollten Dreiräder nicht vier Räder haben können?" Die Definition von "Dreirad" ist, dass drei Räder hat. Wenn es ein Dreirad ist, kann es keine vier Räder haben. Wenn es vier Räder hat, kann es kein Dreirad sein. Ebenso ist die Definition der "statischen Methode", dass sie statisch versendet wird. Wenn es sich um eine statische Methode handelt, kann sie nicht dynamisch verteilt werden. Wenn sie dynamisch verteilt werden kann, kann sie keine statische Methode sein.

Es ist natürlich durchaus möglich, Klassenmethoden zu haben , die überschrieben werden können. Sie können auch eine Sprache wie Ruby verwenden, in der Klassen wie jedes andere Objekt Objekte sind und daher über Instanzmethoden verfügen, sodass Klassenmethoden nicht mehr erforderlich sind. (Ruby hat nur eine Art von Methoden: Instanzmethoden. Es gibt keine Klassenmethoden, statischen Methoden, Konstruktoren, Funktionen oder Prozeduren.)


1
"Per Definition" Gut ausgedrückt.
candied_orange

1

Daher können statische Funktionen in C # nicht virtuell oder abstrakt sein

In C # rufen Sie statische Member immer über die Klasse auf, z . B. BaseClass.StaticMethod()nicht baseObject.StaticMethod(). Wenn Sie also ChildClassvon BaseClassund childObjecteine Instanz von geerbt haben ChildClass, können Sie Ihre statische Methode nicht von aufrufen childObject. Sie müssen immer explizit die reale Klasse verwenden, damit eine static virtualnur keinen Sinn ergibt.

Sie können dieselbe staticMethode in Ihrer untergeordneten Klasse neu definieren und das newSchlüsselwort verwenden.

class BaseClass {
    public static int StaticMethod() { return 1; }
}

class ChildClass {
    public static new int StaticMethod() { return BaseClass.StaticMethod() + 2; }
}

int result;    
var baseObj = new BaseClass();
var childObj = new ChildClass();

result = BaseClass.StaticMethod();
result = baseObj.StaticMethod(); // DOES NOT COMPILE

result = ChildClass.StaticMethod();
result = childObj.StaticMethod(); // DOES NOT COMPILE

Wenn es möglich wäre anzurufen baseObject.StaticMethod(), wäre Ihre Frage sinnvoll.

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.