Toutes les méthodes publiques d'une classe abstraite doivent-elles être marquées virtuelles?

12

J'ai récemment dû mettre à jour une classe de base abstraite sur un OSS que j'utilisais pour qu'il soit plus testable en les rendant virtuels (je ne pouvais pas utiliser une interface car elle en combinait deux). Cela m'a fait penser si je devais marquer toutes les méthodes dont j'avais besoin comme virtuelles, ou si je devrais marquer chaque méthode / propriété publique virtuelle. Je suis généralement d' accord avec Roy Osherove pour dire que chaque méthode doit être rendue virtuelle, mais je suis tombé sur cet article qui m'a fait réfléchir à savoir si c'était nécessaire ou non . Je vais cependant limiter cela aux classes abstraites pour plus de simplicité (si toutes les méthodes publiques concrètes doivent être virtuelles est particulièrement discutable, j'en suis sûr).

Je pouvais voir où vous pourriez autoriser une sous-classe à utiliser une méthode, mais ne pas vouloir qu'elle remplace l'implémentation. Cependant, tant que vous croyez que le principe de substitution de Liskov sera respecté, pourquoi ne permettriez-vous pas qu'il soit annulé? En la marquant abstraite, vous forcez de toute façon une certaine surcharge, donc, il me semble que toutes les méthodes publiques à l'intérieur d'une classe abstraite devraient en effet être marquées virtuelles.

Cependant, je voulais demander au cas où il y aurait quelque chose auquel je ne penserais peut-être pas. Toutes les méthodes publiques d'une classe abstraite doivent-elles être rendues virtuelles?

Justin Pihony
la source
Cela peut aider: stackoverflow.com/questions/391483/…
NoChance
1
Vous devriez peut-être vous demander pourquoi en C #, la valeur par défaut n'est pas virtuelle, mais en Java, elle l'est. La raison pour laquelle vous donnerait probablement un aperçu de la réponse.
Daniel Little

Réponses:

9

Cependant, tant que vous croyez que le principe de substitution de Liskov sera respecté, pourquoi ne permettriez-vous pas qu'il soit annulé?

Par exemple, parce que je veux que l'implémentation squelette d'un algorithme soit fixe, et que seules des parties spécifiques soient (re) définies par des sous-classes. Ceci est largement connu sous le nom de modèle de méthode de modèle (souligné ci-dessous par moi):

La méthode de modèle gère ainsi l'image plus large de la sémantique des tâches et des détails d'implémentation plus raffinés de la sélection et de la séquence des méthodes. Cette image plus large appelle des méthodes abstraites et non abstraites pour la tâche à accomplir. Les méthodes non abstraites sont entièrement contrôlées par la méthode du modèle mais les méthodes abstraites, mises en œuvre dans les sous-classes, fournissent le pouvoir expressif et le degré de liberté du motif. Certaines ou toutes les méthodes abstraites peuvent être spécialisées dans une sous-classe, permettant à l'auteur de la sous-classe de fournir un comportement particulier avec des modifications minimes à la plus grande sémantique. La méthode modèle (qui n'est pas abstraite) reste inchangée dans ce modèle, garantissant que les méthodes non abstraites subordonnées et les méthodes abstraites sont appelées dans la séquence initialement prévue.

Mise à jour

Quelques exemples concrets de projets sur lesquels j'ai travaillé:

  1. communiquer avec un ancien système mainframe via différents "écrans". Chaque écran a un tas de champs, de nom fixe, de position et de longueur, contenant des bits de données spécifiques. Une demande remplit certains champs avec des données spécifiques. Une réponse renvoie des données dans un ou plusieurs autres champs. Chaque transaction suit la même logique de base, mais les détails sont différents sur chaque écran. Nous avons utilisé la méthode des modèles dans plusieurs projets différents pour implémenter le squelette fixe de la logique de gestion de l'écran, tout en permettant aux sous-classes de définir les détails spécifiques à l'écran.
  2. exporter / importer des données de configuration dans des tables DB vers / depuis des fichiers Excel. Encore une fois, le schéma de base du traitement d'un fichier Excel et de l'insertion / mise à jour des enregistrements de base de données ou du vidage des enregistrements dans Excel est le même pour chaque table, mais les détails de chaque table sont différents. La méthode de modèle est donc un choix très évident pour éliminer les duplications de code et rendre le code plus facile à comprendre et à maintenir.
  3. Génération de documents PDF. Chaque document a la même structure, mais leur contenu est différent à chaque fois, en fonction de nombreux facteurs. Encore une fois, la méthode de modèle facilite la séparation du squelette fixe de l'algorithme de génération des détails modifiables spécifiques au cas. En réalité. il s'applique même à plusieurs niveaux ici, car le document se compose de plusieurs sections , chacune composée de zéro ou plusieurs champs . La méthode du modèle est appliquée à 3 niveaux distincts ici.

Dans les deux premiers cas, l'implémentation héritée d'origine utilisait la stratégie , ce qui a donné lieu à de nombreux codes dupliqués, qui bien sûr au fil des ans ont augmenté de subtiles différences ici et là, contenaient de nombreux bogues dupliqués ou légèrement différents et étaient très difficiles à maintenir. La refactorisation vers la méthode de modèle (et certaines autres améliorations, comme l'utilisation d'annotations Java) a réduit la taille du code d'environ 40 à 70%.

Ce ne sont que les exemples les plus récents qui me viennent à l'esprit. Je pourrais citer plus de cas de presque tous les projets sur lesquels j'ai travaillé jusqu'à présent.

Péter Török
la source
Le simple fait de citer le GoF n'est pas une réponse. Vous auriez besoin de donner une raison réelle de faire une telle chose.
DeadMG
@DeadMG, j'ai utilisé la méthode des modèles régulièrement au cours de ma carrière, j'ai donc pensé qu'il était évident que c'est un modèle très pratique et utile (comme la plupart des modèles du GoF sont ... ce ne sont pas des exercices académiques théoriques, mais collectés de l'expérience du monde réel). Mais apparemment, tout le monde n'est pas d'accord ... alors j'ajoute quelques exemples concrets à ma réponse.
Péter Török
2

Il est parfaitement raisonnable et parfois souhaitable d'avoir des méthodes non virtuelles dans une classe de base abstraite; ce n'est pas parce que c'est une classe abstraite que toutes ses parties doivent être utilisables de manière polymorphe.

Par exemple, vous souhaiterez peut-être utiliser l'idiome `` polymorphisme non virtuel '', selon lequel une fonction est appelée de manière polymorphe à partir d'une fonction membre non virtuelle, afin de garantir que certaines conditions préalables ou postconditions sont remplies avant l'appel de la fonction virtuelle.

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

    void CallMe()
    {
        CallMeFirst();
        OverrideMe();
    }
};
Ben Cottrell
la source
Je dirais que, tant que le comportement global reste le même, cela pourrait être rendu virtuel. Vous pouvez remplir les conditions pré / post en utilisant une implémentation différente (base de données vs en mémoire). Sinon, ce devrait être une fonction privée?
Justin Pihony
2

Il suffit qu'une classe contienne UNE méthode virtuelle pour que la classe devienne abstraite. Vous voudrez peut-être faire attention aux méthodes que vous souhaitez virtualiser et aux autres, selon le type de polymorphisme que vous prévoyez d'utiliser.

apaiser
la source
1

Demandez-vous à quoi sert une méthode non virtuelle dans une classe abstraite. Une telle méthode devrait avoir une implémentation pour la rendre utile. Mais si la classe a une implémentation, peut-elle encore être appelée une classe abstraite? Même si le langage / compilateur le permet, cela a-t-il un sens? Personnellement, je ne pense pas. Vous auriez une classe normale avec des méthodes abstraites que les descendants devraient implémenter.

Mon langage principal est Delphi, pas C #. Dans Delphi, si vous marquez un résumé de méthode, vous devez également le marquer comme virtuel ou le compilateur se plaint. Je n'ai pas suivi les derniers changements de langage de trop près, mais si des classes abstraites sont dans ou viendraient à Delphi, je m'attendrais à ce que le compilateur se plaigne de toutes les méthodes non virtuelles, de toutes les méthodes privées et de toutes les implémentations de méthodes pour une classe marquée abstraite au niveau de la classe.

Marjan Venema
la source
2
Je pense qu'il est parfaitement logique d'avoir une classe abstraite qui a des méthodes abstraites, des méthodes virtuelles et des méthodes non virtuelles. Je pense que cela dépend toujours de ce que vous voulez faire exactement.
svick
3
Je vais d'abord passer en revue vos C # q. Une méthode abstraite en C # est implicitement virtuelle et ne peut pas avoir d'implémentation. Quant à votre premier point, une classe abstraite en C # peut et devrait avoir une implémentation quelconque (sinon vous devriez simplement utiliser une interface). Le point d'une classe étant abstraite est qu'elle DOIT être sous-classée, mais elle contient une logique que (théoriquement) toutes les sous-classes utiliseront. Cela réduit la duplication de code. Ce que je demande, c'est si l'une de ces implémentations ne doit pas être annulée (en disant essentiellement que la méthode de base est la seule solution).
Justin Pihony
@JustinPihony: merci. Dans Delphi, lorsque vous marquez un résumé de méthode, le compilateur se plaindra si vous lui fournissez une implémentation. Intéressant de voir comment les différentes langues implémentent les concepts différemment et créent ainsi des attentes différentes chez leurs utilisateurs.
Marjan Venema
@svick: oui, c'est parfaitement logique, je n'appellerais pas ça une classe abstraite, mais une classe avec des méthodes abstraites ... Mais je suppose que c'est peut-être juste mon interprétation.
Marjan Venema
@MarjanVenema, mais ce n'est pas la terminologie utilisée par C #. Si vous marquez des méthodes dans une classe abstract, vous devez également marquer toute la classe abstract.
svick
0

Cependant, tant que vous croyez que le principe de substitution de Liskov sera suivi, alors pourquoi ne pas permettre qu'il soit annulé?

Vous ne rendez pas certaines méthodes virtuelles parce que vous ne croyez pas que ce soit le cas. De plus, en rendant certaines méthodes non virtuelles, vous signalez aux héritiers quelles méthodes doivent être implémentées.

Personnellement, je ferai souvent des surcharges de méthodes qui existent pour des raisons de commodité non virtuelles afin que les utilisateurs de la classe puissent avoir des valeurs par défaut cohérentes et que les implémenteurs ne soient même pas capables de commettre l'erreur de briser ce comportement implicite.

Telastyn
la source