Comment forcer l'appel uniquement aux méthodes de la superclasse malgré le fait qu'elles aient été remplacées (en Ocaml)
-
26-10-2019 - |
Question
Pour être honnête, je ne connais pas grand-chose au système d'objets d'OCaml.L'extrait suivant illustre mon problème :
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
Une fois exécuté, l'extrait produit le résultat suivant :
bar2
foo1
bar2
foo1
bar2
endbar
, montrant que la méthode de superclasse foo1 (appelée via super#foo1) exécute la méthode remplacée foo2 de la sous-classe.Je m'attendais plutôt à ce qu'il appelle la méthode foo2 depuis la superclasse, comme elle est appelée via super.
Est-il possible d'obtenir ce comportement, c'est-à-direUne méthode de superclasse appelle-t-elle uniquement d'autres méthodes de superclasse même si elles sont remplacées dans une sous-classe ?
La solution
Je ne suis pas sûr à 100%, mais je suis assez sûr que vous ne pouvez pas. Dans l'héritage et le sous-typage OCaml sont deux concepts différents. Une classe peut être un sous-type d'un autre type sans héritage. Tout l'héritage est fait tirer dans les méthodes de la classe héritée.
Polymorphisme est réalisée par la saisie de structure, de sorte que votre appel à foo2
appelle la méthode en bar
parce que bar
a mis en place foo2
et non pas parce inherits de bar
de foo
(par opposition à dire, C ++, où bar#foo2
est appelé en raison d'une table de fonction virtuelle rechercher).
Cela dit, OCaml ne vous fournit un moyen de faire la distinction entre les méthodes et les méthodes héritées redéfinis en utilisant la syntaxe de inherit...as...
. Dans bar
de votre exemple, o#foo1
et super#foo1
sont la même méthode (car bar
ne met pas en oeuvre foo1
) alors que différentes méthodes sont o#foo2
et super#foo2
. Malgré cela, je ne pense pas qu'il y ait de toute façon pour la classe héritée de savoir qu'il a été hérité et la distinction entre les méthodes et les méthodes surchargées est tout. Il pourrait y avoir une syntaxe pour cela, mais je doute fortement en raison du fait que l'héritage et le polymorphisme sont des concepts indépendants.
Autres conseils
Non, ce n'est pas. Ceci est « obligatoire en retard », une idée de base de OO, pas spécifique à OCaml.
Je ne sais pas ce que vous essayez de faire, mais la programmation orientée objet est peut-être pas les bons outils, ou du moins pas utilisé de cette façon. Si vous voulez apprendre OCaml, vous devriez probablement se concentrer sur les parties non-OO, ce qui est assez intéressant pour commencer.
Comme points de GASCHE sur, c'est le comportement prévu et standard pour les langages OO.
L'appel super#foo1
, puisque bar
ne remplace pas foo1
, est exactement équivalente à o#foo1
. La construction super
existe seulement pour être en mesure d'appeler la méthode foo2
de foo
de méthodes bar
(sinon il n'y a aucun moyen de cette méthode de référence). Dans foo1
comme appelé à partir bar#foo2
, o
est en fait un bar
, pas foo
, donc appeler la méthode foo2
appelle la méthode bar
de foo2
.
Je dirais que si vous souhaitez coder en dur ce comportement, vous feriez mieux de ne pas utiliser de programmation orientée objet.Implémentez-le simplement en tant que fonctions appelant d’autres fonctions.
("ce comportement" tel que je l'ai compris :si j'appelle foo2
de l'intérieur du code qui a été appelé comme super#foo1
, alors exactement le foo2
de l'implémentation de la superclasse doit être appelée, et non des implémentations plus "spécifiques" des sous-classes)
C’est la manière la plus simple, la plus propre et la plus claire de procéder :des fonctions de programme qui font ce que vous voulez.
Ou vous devriez expliquer à vous-même et à nous :Pourquoi avez-vous besoin de POO ?La raison n’en est pas indiquée dans le texte de la question.Pourquoi faire foo1
et foo2
des méthodes plutôt que des fonctions indépendantes ?(À part foo1
et foo2
, vous pouvez avoir des objets, des classes et des méthodes dans votre programme, le cas échéant).
Vous vous demandez si cette question vient d'une comparaison avec d'autres langues
Si vous connaissez un autre langage OO, c'est étrange que vous souhaitiez "ce comportement" de la POO :ce n'est pas le comportement attendu, par exemple, en Java ou en C++, car ils utilisent le concept d'une table de méthodes virtuelles associée à chaque objet au moment de l'exécution. Ainsi, lorsque vous appelez une méthode dans votre programme, elle est distribuée à l'exécution. temps jusqu'à la mise en œuvre de cette méthode réellement associée à l'objet.Donc en bref : chaque fois que vous utilisez une expression d'appel de méthode dans votre programme, vous vous engagez à respecter ce principe de recherche de l'implémentation de la méthode ("liaison tardive"), comme l'a souligné Gasche.Bien que cf.les différences entre OCaml et les langages implémentés avec une table de méthodes virtuelles soulignées par Niki Yoshiuchi.
Formaliser toute la discussion sur les comportements disponibles et recherchés
Bien que ce que vous souhaitez ne soit peut-être pas le comportement attendu et disponible dans de nombreux langages OO populaires, il est imaginable et pourrait être implémenté dans certains systèmes POO spécifiques, si l'on a accès aux composants internes de l'implémentation POO.
Disons, si dans une implémentation, super
est une structure contenant la table des méthodes de la superclasse (à laquelle recourir lors de la résolution d'un appel de méthode pour l'objet actuel), et les méthodes sont des fonctions qui doivent recevoir l'objet (la table des méthodes) comme premier argument, puis pour effectuer ce que vous veux, on écrirait super.foo1(super, y)
.
(Je me demande en fait s'il existe des implémentations de POO dont les composants internes sont exposés au programmeur et qui permettent d'effectuer un tel appel.)
Alors que le comportement habituel de la POO serait exprimé dans ce système par this.foo1(this, y)
(où this
est la table des méthodes pour l'objet actuel.)
Votre appel OCaml super#foo1 y
ou un Java super.foo1(y);
se traduit par ce "mon" système par super.foo1(this, y)
.(Même si toujours cf.les différences soulignées par Niki Yoshiuchi entre OCaml et des langages comme Java implémentés avec une table de méthodes virtuelles.)
Vous voyez les différences entre les 3 cas.
Annexe.À la recherche de langues qui fonctionneraient de cette façon
Hmm, PHP pourrait être un langage dans lequel ce comportement serait possible, mais :
- uniquement sur la programmation "au niveau de la classe" (méthodes statiques), pas au niveau de l'objet ;
- seulement quand vous codez en dur des choses étranges "reliure statique tardive" aux appels de fonction :
#!/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);
?>
Le modèle se comporte dans un sens comme des objets OO standard avec des méthodes virtuelles, et Bar comme tu voulais:
$ ./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 $
(BTW, en utilisant parent::
au lieu de Foo::
ne nous amènerait pas à votre comportement recherché.)
Je ne comprends pas le but des spécifications de liaison insensées de PHP comme static::
, qui n'ont un certain effet que pour les méthodes statiques (c'est-à-dire la programmation au niveau de la classe).
Un exemple C++ analogue ne donne pas de comportement au niveau de l'objet OO par défaut :
#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;
}
-- cela donne votre comportement recherché :
$ ./a.out | head -10 bar2 foo1 foo2 foo2 foo2 foo2 foo2 foo2 foo2 foo2 $
même sans qualificatif spécial lors de l'appel de fonction dans le code de Bar::foo2()
, donc pas intéressant pour nous.
Qu'en est-il des méthodes statiques de Java ?(Est-ce qu'ils diffèrent du C++ et nous donnent par défaut une résolution de type méthode virtuelle des appels de fonction ?)