So erzwingen Sie den Aufruf nur der Methoden der Oberklasse, obwohl diese überschrieben wurden (in Ocaml)
-
26-10-2019 - |
Frage
Ehrlich gesagt weiß ich nicht viel über das Objektsystem von OCaml.Der folgende Ausschnitt veranschaulicht mein Problem:
class foo =
object (o)
method foo1 y =
print_endline "foo1";
o#foo2 (y - 1)
method foo2 y =
print_endline "foo2";
if y > 0 then o#foo2 y else print_endline "end"
end
class bar =
object (o : 'self_type)
inherit foo as super
method foo2 y =
print_endline "bar2";
if y > 0 then super#foo1 y else print_endline "endbar"
end
let _ = (new bar)#foo2 3
Bei der Ausführung erzeugt das Snippet die folgende Ausgabe:
bar2
foo1
bar2
foo1
bar2
endbar
, was zeigt, dass die Oberklassenmethode foo1 (aufgerufen über super#foo1) die überschriebene Methode foo2 aus der Unterklasse ausführt.Ich hätte stattdessen erwartet, dass es die Methode foo2 aus der Superklasse aufruft, wie sie über super aufgerufen wird.
Ist es möglich, dieses Verhalten zu erreichen, d.h.Soll eine Superklassenmethode nur andere Superklassenmethoden aufrufen, auch wenn diese in einer Unterklasse überschrieben werden?
Lösung
Ich bin mir nicht 100% sicher, aber ich bin mir ziemlich sicher, dass Sie es nicht können. In OCAML sind Vererbung und Subtyping zwei verschiedene Konzepte. Eine Klasse kann ein Subtyp eines anderen Typs ohne Vererbung sein. Alles, was die Vererbung tut, ist, die Methoden aus der ererbten Klasse zu ziehen.
Der Polymorphismus wird durch strukturelle Typisierung erreicht, sodass Ihr Aufruf dazu foo2
Ruft die Methode in auf bar
Weil bar
hat implementiert foo2
und nicht weil bar
Erben von foo
(Wie angelegt, C ++, wo bar#foo2
wird aufgrund einer virtuellen Funktionstabelle aufgerufen).
Das heißt, OCAML bietet Ihnen eine Möglichkeit, zwischen überschriebenen Methoden und ererbten Methoden zu unterscheiden, die mit dem vererbt wurden inherit...as...
Syntax. Im bar
Aus Ihrem Beispiel, o#foo1
und super#foo1
sind die gleiche Methode (seitdem bar
Implementiert nicht foo1
) wohingegen o#foo2
und super#foo2
sind verschiedene Methoden. Trotzdem glaube ich nicht, dass die ererbte Klasse sowieso wissen, dass sie zwischen den Methoden und den überschriebenen Methoden geerbt und unterscheidet. Dafür mag es eine Syntax geben, aber ich bezweifle sehr, dass Vererbung und Polymorphismus unabhängige Konzepte sind.
Andere Tipps
Nein, ist es nicht. Dies ist "späte Bindung", eine grundlegende Idee von OO, nicht spezifisch für OCAML.
Ich weiß nicht, was Sie versuchen, aber objektorientierte Programmierung ist vielleicht nicht die richtigen Werkzeuge oder zumindest nicht so. Wenn Sie OCAML lernen möchten, sollten Sie sich wahrscheinlich auf die Nicht-OO-Teile konzentrieren, was für den Anfang interessant genug ist.
Wie Gasche betont, ist dies das beabsichtigte und Standardverhalten für OO -Sprachen.
Das super#foo1
Rufen Sie seit bar
Überschreibt nicht foo1
, ist genau gleichwertig zu o#foo1
. Das super
Konstrukt existiert nur, um das aufrufen zu können foo2
Methode von foo
Aus Methoden in bar
(Ansonsten gibt es keine Möglichkeit, diese Methode zu verweisen). Im foo1
wie genannt von bar#foo2
, o
ist eigentlich a bar
, kein foo
, so ruft das an foo2
Methode ruft die auf bar
foo2
Methode.
Ich würde sagen, wenn Sie dieses Verhalten fest codieren möchten, sollten Sie besser keine objektorientierte Programmierung verwenden.Implementieren Sie es einfach als Funktionen, die andere Funktionen aufrufen.
(„dieses Verhalten“, wie ich es verstanden habe:wenn Sie anrufen foo2
aus dem Code heraus, der aufgerufen wurde als super#foo1
, dann genau das foo2
aus der Implementierung der Oberklasse sollte aufgerufen werden, nicht aus den „spezifischeren“ Implementierungen von Unterklassen)
Es ist die einfachste, sauberste und klarste Vorgehensweise:Programmfunktionen, die das tun, was Sie wollen.
Oder Sie sollten es sich und uns erklären:Warum brauchen Sie OOP?Der Grund dafür wird im Fragetext nicht genannt.Warum machen foo1
Und foo2
Methoden statt unabhängiger Funktionen?(Abgesehen von foo1
Und foo2
, Möglicherweise enthält Ihr Programm einige Objekte, Klassen und Methoden (sofern zutreffend).
Ich frage mich, ob diese Frage aus dem Vergleich mit anderen Sprachen resultiert
Wenn Sie eine andere OO-Sprache beherrschen, ist es seltsam, dass Sie „dieses Verhalten“ von OOP erwarten:Es ist nicht das Verhalten, das beispielsweise in Java oder C++ erwartet wird, da sie das Konzept einer Tabelle virtueller Methoden verwenden, die jedem Objekt zur Laufzeit zugeordnet wird. Wenn Sie also eine Methode in Ihrem Programm aufrufen, wird sie zur Laufzeit abgesetzt. Zeit bis zur Implementierung der tatsächlich mit dem Objekt verknüpften Methode.Kurz gesagt: Wann immer Sie in Ihrem Programm einen Methodenaufrufausdruck verwenden, verpflichten Sie sich zu diesem Prinzip, die Implementierung der Methode zu finden („späte Bindung“)., wie Gasche betonte.Obwohl immer noch vgl.die Unterschiede zwischen OCaml und mit einer virtuellen Methodentabelle implementierten Sprachen, auf die Niki Yoshiuchi hingewiesen hat.
Formalisierung der gesamten Diskussion über die verfügbaren und gewünschten Verhaltensweisen
Obwohl das gewünschte Verhalten möglicherweise nicht das erwartete und verfügbare Verhalten in vielen gängigen OO-Sprachen ist, ist es vorstellbar und könnte in einigen spezifischen OOP-Systemen umsetzbar sein, wenn man Zugriff auf die Interna der OOP-Implementierung hat.
Sagen wir, wenn in einer Implementierung, super
ist eine Struktur, die die Methodentabelle der Oberklasse enthält (auf die beim Auflösen eines Methodenaufrufs für das aktuelle Objekt zurückgegriffen werden kann), und die Methoden sind Funktionen, die das Objekt (die Methodentabelle) als erstes Argument empfangen müssen, um dann das auszuführen, was Sie tun wollen, würde man schreiben super.foo1(super, y)
.
(Ich frage mich tatsächlich, ob es OOP-Implementierungen gibt, deren Interna dem Programmierer offengelegt werden und die einen solchen Aufruf ermöglichen.)
Während das übliche OOP-Verhalten in diesem System durch ausgedrückt würde this.foo1(this, y)
(Wo this
ist die Methodentabelle für das aktuelle Objekt.)
Ihr OCaml-Anruf super#foo1 y
oder ein Java super.foo1(y);
übersetzt in dieses „mein“ System als super.foo1(this, y)
.(Obwohl noch vgl.die von Niki Yoshiuchi aufgezeigten Unterschiede zwischen OCaml und Sprachen wie Java, die mit einer virtuellen Methodentabelle implementiert werden.)
Sie sehen die Unterschiede zwischen den 3 Fällen.
Anhang.Auf der Suche nach Sprachen, die auf diese Weise funktionieren würden
Hmm, PHP könnte eine Sprache sein, in der dieses Verhalten möglich wäre, aber:
- nur bei der Programmierung auf „Klassenebene“ (statische Methoden), nicht auf Objektebene;
- Nur wenn Sie etwas Seltsames fest codieren „späte statische Bindung“ bei den Funktionsaufrufen:
#!/usr/bin/php
<?php
class Foo {
public static function foo1($y) {
echo "foo1\n";
static::foo2($y-1);
}
public static function foo2($y) {
echo "foo2\n";
if ($y > 0)
static::foo2($y);
else
echo "end\n";
}
}
class Bar extends Foo {
public static function foo2($y) {
echo "bar2\n";
if ($y > 0)
Foo::foo1($y);
else
echo "endbar\n";
}
}
class Model extends Foo {
public static function foo2($y) {
echo "model2\n";
if ($y > 0)
static::foo1($y);
else
echo "endmodel\n";
}
}
Model::foo2(3);
Bar::foo2(3);
?>
Das Modell verhält sich gewissermaßen wie Standard-OO-Objekte mit virtuellen Methoden und Bar as du wolltest:
$ ./test-force-super-binding.php | head -20 model2 foo1 model2 foo1 model2 foo1 model2 endmodel bar2 foo1 foo2 foo2 foo2 foo2 foo2 foo2 foo2 foo2 foo2 foo2 $
(Übrigens, mit parent::
anstatt Foo::
würde uns nicht zu Ihrem gewünschten Verhalten bringen.)
Ich verstehe den Zweck der verrückten PHP-Bindungsspezifikationen nicht static::
, die nur für statische Methoden (d. h. Programmierung auf Klassenebene) von gewisser Wirkung sind.
Ein analoges C++-Beispiel gibt standardmäßig kein OO-Verhalten auf Objektebene an:
#include<iostream>
class Foo {
protected:
static void foo1(int y) {
std::cout << "foo1" << std::endl;
foo2(y-1);
}
public:
static void foo2(int y) {
std::cout << "foo2" << std::endl;
if (y > 0)
foo2(y);
else
std::cout << "end" << std::endl;
}
};
class Bar: public Foo {
public:
static void foo2(int y) {
std::cout << "bar2" << std::endl;
if (y > 0)
foo1(y);
else
std::cout << "endbar" << std::endl;
}
};
int main() {
Bar::foo2(3);
return 0;
}
-- es gibt Ihr gewünschtes Verhalten:
$ ./a.out | head -10 bar2 foo1 foo2 foo2 foo2 foo2 foo2 foo2 foo2 foo2 $
auch ohne spezielles Qualifikationsmerkmal beim Funktionsaufruf im Code für Bar::foo2()
, also für uns nicht interessant.
Was ist mit den statischen Methoden von Java?(Unterscheiden sie sich von C++ und bieten sie uns standardmäßig eine virtuelle, methodenähnliche Auflösung der Funktionsaufrufe?)