Pourquoi les méthodes statiques ne devraient-elles pas pouvoir être remplacées?

13

En réponse à cette question, le consensus général était que les méthodes statiques ne sont pas censées être remplacées (et donc les fonctions statiques en C # ne peuvent pas être virtuelles ou abstraites). Ce n'est pas seulement le cas en C #; Java l'interdit également et C ++ ne semble pas non plus l'aimer. Cependant, je peux penser à de nombreux exemples de fonctions statiques que je voudrais remplacer dans une classe enfant (par exemple, les méthodes d'usine). Bien qu'en théorie, il existe des moyens de les contourner, aucun d'entre eux n'est propre ou simple.

Pourquoi les fonctions statiques ne devraient-elles pas être remplaçables?

PixelArtDragon
la source
4
Delphi prend en charge les méthodes de classe , qui sont assez similaires aux méthodes statiques et peuvent être remplacées. Ils reçoivent un selfpointeur qui pointe vers la classe et non vers une instance de la classe.
CodesInChaos
Une définition de l'électricité statique est: manque de changement. Je suis curieux de savoir pourquoi vous avez rendu une fonction statique que vous souhaitez remplacer.
JeffO
4
@JeffO: Cette définition anglaise de "statique" n'a absolument rien à voir avec la sémantique unique des fonctions membres statiques.
Courses de légèreté avec Monica
5
Votre question est "pourquoi les méthodes statiques devraient-elles utiliser la répartition de type statique au lieu de la répartition unique virtuelle ?" Lorsque vous formulez la question de cette façon, je pense qu'elle se répond d'elle-même.
Eric Lippert
1
@EricLippert La question pourrait être mieux formulée comme "Pourquoi C # propose-t-il des méthodes statiques au lieu des méthodes de classe?", Mais c'est toujours une question valide.
CodesInChaos

Réponses:

13

Avec les méthodes statiques, aucun objet ne permet de contrôler correctement le mécanisme de priorité.

Le mécanisme de méthode virtuelle de classe / instance normale permet un contrôle finement réglé des remplacements comme suit: chaque objet réel est une instance d'exactement une classe. Cette classe détermine le comportement des remplacements; il obtient toujours la première fissure aux méthodes virtuelles. Il peut alors choisir d'appeler la méthode parent au bon moment pour son implémentation. Chaque méthode parent obtient alors également son tour pour invoquer sa méthode parent. Il en résulte une belle cascade d'invocations parentales, qui accomplit l'une des notions de réutilisation de code pour lesquelles l'orientation d'objet est connue. (Ici, le code des classes de base / super est réutilisé d'une manière relativement complexe; une autre notion orthogonale de réutilisation de code dans OOP est simplement d'avoir plusieurs objets de la même classe.)

Les classes de base peuvent être réutilisées par diverses sous-classes, et chacune peut coexister confortablement. Chaque classe qui est utilisée pour instancier des objets dictant son propre comportement, coexistant pacifiquement et simultanément avec les autres. Le client a le contrôle sur les comportements qu'il souhaite et quand il choisit la classe à utiliser pour instancier un objet et le transmettre aux autres comme il le souhaite.

(Ce n'est pas un mécanisme parfait, car on peut toujours identifier les capacités qui ne sont pas prises en charge, bien sûr, c'est pourquoi des modèles comme la méthode d'usine et l'injection de dépendance sont superposés.)

Donc, si nous devions créer une capacité de substitution pour la statique sans rien changer d'autre, nous aurions du mal à ordonner les substitutions. Il serait difficile de définir un contexte limité pour l'applicabilité du remplacement, vous obtiendrez donc le remplacement globalement plutôt que localement comme avec les objets. Il n'y a aucun objet d'instance pour changer le comportement. Donc, si quelqu'un a invoqué la méthode statique qui a été remplacée par une autre classe, le remplacement doit-il prendre le contrôle ou non? S'il existe plusieurs remplacements de ce type, qui obtient le contrôle en premier? seconde? Avec les remplacements d'objet d'instance, ces questions ont toutes des réponses significatives et bien motivées, mais pas avec la statique.

Les remplacements pour la statique seraient considérablement chaotiques, et des choses comme cela ont déjà été faites.

Par exemple, Mac OS System 7 et versions antérieures utilisaient un mécanisme de correction de déroutement pour étendre le système en obtenant le contrôle des appels système effectués par l'application avant le système d'exploitation. Vous pouvez penser à la table de correctifs des appels système comme un tableau de pointeurs de fonction, un peu comme une table d'objets par exemple, sauf qu'il s'agissait d'une seule table globale.

Cela a causé un chagrin indescriptible aux programmeurs en raison de la nature non ordonnée de la correction des pièges. Quiconque a réussi à réparer le dernier piège a essentiellement gagné, même s'il ne le voulait pas. Chaque patcher du piège capturait la valeur de piège précédente pour une sorte de capacité d'appel parent, qui était extrêmement fragile. Suppression d'un correctif de déroutement, par exemple, lorsque vous n'avez plus besoin de connaître un appel système a été considéré comme une mauvaise forme car vous ne disposiez pas vraiment des informations nécessaires pour supprimer votre correctif (si vous le faisiez, vous décompacteriez également tout autre correctif qui aurait suivi. vous).

Cela ne veut pas dire qu'il serait impossible de créer un mécanisme pour les remplacements de statique, mais ce que je préférerais probablement faire à la place est de transformer les champs statiques et les méthodes statiques en champs d'instances et en méthodes d'instance de métaclasses, de sorte que l'objet normal des techniques d'orientation s'appliqueraient alors. Notez qu'il existe des systèmes qui le font également: CSE 341: classes et métaclasses Smalltalk ; Voir aussi: Quel est l'équivalent Smalltalk de la statique de Java?


J'essaie de dire que vous devrez faire une conception sérieuse des fonctionnalités du langage pour que cela fonctionne même raisonnablement bien. Par exemple, une approche naïve a été adoptée, a fait long feu, mais était très problématique, et sans doute (c'est-à-dire, je dirais) défectueuse sur le plan architectural en fournissant une abstraction incomplète et difficile à utiliser.

Au moment où vous avez terminé de concevoir la fonctionnalité de remplacement statique pour fonctionner correctement, vous venez peut-être d'inventer une forme de métaclasses, qui est une extension naturelle de la POO dans / pour les méthodes basées sur les classes. Il n'y a donc aucune raison de ne pas le faire - et certaines langues le font en fait. C'est peut-être un peu plus une exigence marginale qu'un certain nombre de langues choisissent de ne pas faire.

Erik Eidt
la source
Si je comprends bien: il ne s'agit pas de savoir si théoriquement vous voudriez ou devriez faire quelque chose comme ça, mais plus que par programme, sa mise en œuvre serait difficile et pas nécessairement utile?
PixelArtDragon
@Garan, Voir mon addendum.
Erik Eidt
1
Je n'achète pas l'argument de commande; vous obtenez la méthode fournie par la classe sur laquelle vous avez appelé la méthode (ou la première superclasse qui fournit une méthode en fonction de la linéarisation choisie par votre langue).
Hobbs
1
@PierreArlaud: Mais this.instanceMethod()a une résolution dynamique, donc si self.staticMethod()a la même résolution, à savoir dynamique, ce ne serait plus une méthode statique.
Jörg W Mittag
1
la sémantique prioritaire pour les méthodes statiques doit être ajoutée. Une métaclasse Java + hypothétique n'a pas besoin d'ajouter quoi que ce soit! Vous créez simplement des objets classes, et les méthodes statiques deviennent alors des méthodes d'instance normales. Vous n'avez pas besoin d'ajouter quelque chose au langage, car vous n'utilisez que des concepts qui sont déjà là: objets, classes et méthodes d'instance. En bonus, vous pouvez réellement supprimer les méthodes statiques du langage! Vous pouvez ajouter une fonctionnalité en supprimant un concept et donc la complexité du langage!
Jörg W Mittag
9

Le remplacement dépend de la répartition virtuelle: vous utilisez le type d'exécution du thisparamètre pour décider de la méthode à appeler. Une méthode statique n'a pas de thisparamètre, il n'y a donc rien à distribuer.

Certains langages, notamment Delphi et Python, ont une portée "intermédiaire" qui permet cela: les méthodes de classe. Une méthode de classe n'est pas une méthode d'instance ordinaire, mais elle n'est pas statique non plus; il reçoit un selfparamètre (de manière amusante, les deux langues appellent le thisparamètre self) qui est une référence au type d'objet lui-même, plutôt qu'à une instance de ce type. Avec cette valeur, vous disposez désormais d'un type pour effectuer une répartition virtuelle.

Malheureusement, ni la JVM ni le CLR n'ont rien de comparable.

Mason Wheeler
la source
Est-ce que selfce sont les langages qui utilisent qui ont des classes comme objets réels et instanciés avec des méthodes redéfinissables - Smalltalk bien sûr, Ruby, Dart, Objective C, Self, Python? Par rapport aux langages qui prennent après C ++, où les classes ne sont pas des objets de première classe, même s'il y a un accès à la réflexion?
Jerry101
Juste pour être clair, dites-vous qu'en Python ou Delphi, vous pouvez remplacer les méthodes de classe?
Erik Eidt
@ErikEidt En Python, à peu près tout est redéfinissable. Dans Delphi, une méthode de classe peut être déclarée explicitement virtualpuis remplacée dans une classe descendante, tout comme les méthodes d'instance.
Mason Wheeler
Donc, ces langages fournissent une sorte de notion de métaclasses, non?
Erik Eidt
1
@ErikEidt Python a de "vraies" métaclasses. Dans Delphi, une référence de classe est un type de données spécial, pas une classe en soi.
Mason Wheeler
3

Tu demandes

Pourquoi les fonctions statiques ne devraient-elles pas être remplaçables?

je demande

Pourquoi les fonctions que vous souhaitez remplacer devraient-elles être statiques?

Certaines langues vous obligent à démarrer le show de manière statique. Mais après cela, vous pouvez vraiment résoudre un grand nombre de problèmes sans aucune méthode statique.

Certaines personnes aiment utiliser des méthodes statiques chaque fois qu'il n'y a pas de dépendance à l'état dans un objet. Certaines personnes aiment utiliser des méthodes statiques pour la construction d'autres objets. Certaines personnes préfèrent éviter autant que possible les méthodes statiques.

Aucune de ces personnes n'a tort.

Si vous en avez besoin pour être remplaçable, arrêtez simplement de le qualifier de statique. Rien ne va se casser parce que vous avez un objet apatride qui vole autour.

candied_orange
la source
Si le langage prend en charge les structures, un type d'unité peut être une structure de taille nulle, qui est transmise à la méthode d'entrée logique. La création de cet objet est un détail d'implémentation. Et si nous commençons à parler des détails de l'implémentation comme de la vérité, je pense que vous devez également considérer que dans une implémentation réelle, un type de taille zéro n'a pas besoin d'être réellement instancié (car aucune instruction n'est nécessaire pour charger ou le stocker).
Theodoros Chatzigiannakis
Et si vous parlez encore plus "en coulisses" (par exemple au niveau de l'exécutable), alors les arguments d'environnement pourraient être définis comme un type dans le langage et le "vrai" point d'entrée du programme pourrait être une méthode d'instance de ce type, agissant sur n'importe quelle instance transmise au programme par le chargeur du système d'exploitation.
Theodoros Chatzigiannakis
Je pense que cela dépend de la façon dont vous le voyez. Par exemple, lorsqu'un programme est démarré, le système d'exploitation le charge en mémoire, puis se rend à l'adresse où réside l'implémentation individuelle du programme du point d'entrée binaire (par exemple, le _start()symbole sous Linux). Dans cet exemple, l'exécutable est l'objet (il a sa propre "table de répartition" et tout) et le point d'entrée est l'opération distribuée dynamiquement. Par conséquent, le point d'entrée d'un programme est intrinsèquement virtuel lorsqu'il est vu de l'extérieur et il peut facilement être rendu virtuel lorsqu'il est également vu de l'intérieur.
Theodoros Chatzigiannakis
1
"Rien ne va se casser parce que tu as un objet apatride qui vole." ++++++
RubberDuck
@TheodorosChatzigiannakis vous faites un argument fort. à moins que vous ne m'ayez convaincu, la ligne n'inspire pas la pensée que je voulais. J'essayais vraiment de donner aux gens la permission de se débarrasser de certaines des pratiques les plus habituelles qu'ils associent aveuglément aux méthodes statiques. J'ai donc mis à jour. Pensées?
candied_orange
2

Pourquoi les méthodes statiques ne devraient-elles pas pouvoir être remplacées?

Ce n'est pas une question de «devrait».

"Remplacement" signifie "répartition dynamique ". "Méthode statique" signifie "répartition statique". Si quelque chose est statique, il ne peut pas être remplacé. Si quelque chose peut être remplacé, ce n'est pas statique.

Votre question revient plutôt à demander: "Pourquoi les tricycles ne devraient-ils pas avoir quatre roues?" La définition de "tricycle" est celle qui a trois roues. Si c'est un tricycle, il ne peut pas avoir quatre roues, s'il a quatre roues, ce ne peut pas être un tricycle. De même, la définition de "méthode statique" est qu'elle est distribuée de manière statique. S'il s'agit d'une méthode statique, elle ne peut pas être distribuée dynamiquement, si elle peut être distribuée dynamiquement, elle ne peut pas être une méthode statique.

Il est bien sûr parfaitement possible d'avoir des méthodes de classe qui peuvent être surchargées. Ou, vous pourriez avoir un langage comme Ruby, où les classes sont des objets comme tout autre objet et peuvent donc avoir des méthodes d'instance, ce qui élimine complètement le besoin de méthodes de classe. (Ruby n'a qu'un seul type de méthodes: les méthodes d'instance. Il n'a pas de méthodes de classe, de méthodes statiques, de constructeurs, de fonctions ou de procédures.)

Jörg W Mittag
la source
1
"Par définition" Bien dit.
candied_orange
1

donc les fonctions statiques en C # ne peuvent pas être virtuelles ou abstraites

En C #, vous appelez toujours des membres statiques en utilisant la classe, par exemple BaseClass.StaticMethod(), non baseObject.StaticMethod(). Donc, finalement, si vous avez ChildClasshérité de BaseClasset childObjectune instance de ChildClass, vous ne pourrez pas appeler votre méthode statique à partir de childObject. Vous aurez toujours besoin d'utiliser explicitement la vraie classe, donc static virtualça n'a pas de sens.

Ce que vous pouvez faire, c'est redéfinir la même staticméthode dans votre classe enfant et utiliser le newmot - clé.

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

S'il était possible d'appeler baseObject.StaticMethod(), votre question aurait du sens.

user276648
la source