web-dev-qa-db-fra.com

Comment forcer la substitution d'une méthode dans un descendant, sans avoir de classe de base abstraite?

L'en-tête de la question semble peu déroutant, mais je vais essayer de clarifier ma question ici.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public abstract class Employee
    {
        private string name;
        private int empid;
        BenefitPackage _BenefitPackage = new BenefitPackage();
        public string Name
         {
             get { return this.name; }
             set { this.name = value; }
            }
        public int EmpId
        {
            get { return this.empid; }
            set
            {
                if (value == 1)
                    return;
                this.empid = value; }
        }
        public Employee(string Name, int EmpId)
        {
            this.Name = Name;
            this.EmpId = EmpId;
        }
        public Employee()
        { }

        public abstract void GiveBonus();

    }

    public class Manager : Employee
    {
        private int noofstockoptions;
        public override void GiveBonus()
        {
            Console.WriteLine("Manger GiveBonus Override");
        }
        public int NoOfStockOptions
        {
            get { return this.noofstockoptions; }
            set { this.noofstockoptions = value; }
        }

        public Manager(string Name,int EmpId, int NoOfStockOptions):base(Name,EmpId)
        {
            this.NoOfStockOptions=NoOfStockOptions;
        }

    }
    public class SalesPerson:Employee
    {
        private int noofsales;
        public int NoOfSales
        {
            get { return this.noofsales; }
            set { this.noofsales = value; }
        }

        public SalesPerson(string Name, int EmpId, int NoOfSales):base(Name,EmpId)
        {
            this.NoOfSales = NoOfSales;
        }
        public override void GiveBonus()
        {
            Console.WriteLine("Hi from salesperson");
        }
    }
    public sealed class PTSalesPerson : SalesPerson
    {
        private int noofhrworked;
        public int NoOfHrWorked
        {
            get { return this.noofhrworked; }
            set { this.noofhrworked = value; }

        }
        public PTSalesPerson(string Name, int EmpId, int NoOfSales,int NoOfHrWorked):base(Name,EmpId,NoOfSales)
        {
            this.NoOfHrWorked = NoOfHrWorked;

        }
        //public new void GiveBonus()
        //{
        //    Console.WriteLine("hi from ptsalesperson");
        //} 
    }

    class BenefitPackage
    {
        public int Bonus;
        public int GiveBonus()
        {
            int i = 200;
            return i;
        }

        private class innerPublic
        {
            public int innerBonus;

        }


    }

    class MainClass
    {
        public static void Main()
        { 
        Manager _Manager=new Manager("Vaibhav",1,50);
        PTSalesPerson _PTSalesPerson = new PTSalesPerson("Shantanu", 1, 4, 6);
        _Manager.GiveBonus();

        Employee _emp;
        //_emp = new Employee("new emp",4);
        //_emp.GiveBonus();
        _PTSalesPerson.GiveBonus();
        ((SalesPerson)_PTSalesPerson).GiveBonus();
        Console.ReadLine();    
        }

    }
}

Veuillez ne pas essayer de comprendre tout le code. Je le résume.

  1. Employé est une classe abstraite, qui a une méthode abstraite GiveBonus
  2. SalesPerson est un dérivé de Employee. SalesPerson doit donner la définition de la méthode abstraite GiveBonus. (SalesPerson ne peut pas être abstrait)
  3. PTSalesPerson dérive de SalesPerson.

Maintenant ma question est, comment puis-je forcer PTSalesPerson à avoir sa propre implémentation de GiveBonus.

42
Vaibhav Jain

Je pense que vous pensez à cela dans le mauvais sens. Les concepteurs de langage ne se sont pas dit "ce dont nous avons vraiment besoin est un moyen de marquer une méthode comme doit être remplacé, inventons cette chose appelée abstract". Ils ont dit "Une méthode virtuelle nous permet de représenter l'idée que chaque type dérivé de ce type de base devrait être capable de faire cette méthode. Mais que faire s'il y a pas de code sensé que peut éventuellement aller dans la version de classe de base de la méthode? Je sais, inventons cette chose appelée une méthode abstraite pour cette circonstance. "

C'est le problème que les méthodes abstraites étaient censées résoudre: vous avez une méthode commune à toutes les classes dérivées mais pas d'implémentation de classe de base sensible, PAS "J'ai besoin d'un moyen pour forcer mes types dérivés à fournir une implémentation". Le fait que les types dérivés soient forcés de fournir une implémentation est un conséquence du solution, mais pas le problème destiné à être résolu en premier lieu.

Le langage C # n'a pas de mécanisme pour le problème "Je dois forcer mon sous-type à fournir sa propre implémentation de cette méthode" car ce n'est pas un problème que les concepteurs de langage, à ma connaissance, aient jamais considéré serait un problème pour la majorité des nos clients.

Donc, ma question est la suivante: pourquoi voulez-vous faire cela? Il appartient certainement au développeur de la classe dérivée de déterminer si l'implémentation de la classe de base est correcte ou non pour la classe dérivée. Cela ne dépend pas de vous. Et même si vous aviez un moyen de le faire, ce qui empêcherait le développeur de simplement dire

override void M() { base.M(); }

?

Pouvez-vous expliquer quel but vous avez pour tenter d'imposer ce travail aux développeurs de vos classes dérivées? Il y a peut-être une meilleure façon de réaliser ce que vous voulez.

Mais plus généralement: je ne suis pas sûr que votre hiérarchie soit conçue de manière sensée en premier lieu. Quand je vois une méthode GiveBonus sur un employé, je suppose que cela signifie que "un employé peut donner un bonus", et non "un employé peut recevoir un bonus". Un manager donne sûrement un bonus et un employé reçoit un bonus. Je pense que vous pourriez faire trop la hiérarchie des employés.

93
Eric Lippert

Vous ne pouvez pas, à moins que vous ne rendiez SalesPerson abstrait ou que vous changiez la hiérarchie.

Que diriez-vous:

                       Employee*
                           ^
                           |
                       SalesPersonBase* (have all the code except GiveBonus)
                        ^           ^
                        |           |
                 SalesPerson      PTSalesPerson

Employé et SalesPersonBase sont désormais marqués comme abstraits.

Cependant, si vous avez besoin qu'un PTSalesPerson hérite non seulement du comportement, mais hérite également de la relation is-a (un PTSalesPerson est également un SalesPerson), vous n'avez aucun moyen de forcer cela.

Remarque, le texte ci-dessus n'est valide que si vous ne considérez que les vérifications à la compilation. En d'autres termes, si vous voulez que le compilateur se plaigne si vous n'avez pas ajouté de substitution à la classe PTSalesPerson, vous ne pouvez pas le faire, sauf si vous faites ce que j'ai décrit ci-dessus.

Cependant, rien ne vous empêche d'utiliser la réflexion pour examiner les méthodes lors de l'exécution et de lever une exception si la méthode dans PTSalesPerson n'est pas explicitement remplacée là-bas, mais je considérerais cela comme un hack.

10

Déclarez la classe Employee comme abstraite mais fournissez et implémentez GiveBonus() qui lève une exception d'exécution avec un message comme "Doit être implémenté par des sous-classes". C'est une vieille pratique Smalltalk ... Je ne sais pas si c'est utile pour C #. Je l'ai utilisé dans Java code.

6
JuanZe

Un WA peut utiliser une "Interface" donc vous en définissez un comme IBonusGiver {void GiveBonus (); } Et puis, au lieu de la méthode abstraite et des substitutions, vous implémentez dans toutes vos classes cette nouvelle interface. par exemple. PTSalesPerson: SalesPerson, IBonusGiver forçant une nouvelle implémentation dans chaque classe.

5
mind_overflow

C'est un fil très ancien mais la réponse donnée sur cette question peut être utile. Vous pouvez forcer une classe dérivée à avoir sa propre implémentation d'une méthode/propriété virtuelle.

public class D
{
    public virtual void DoWork(int i)
    {
        // Original implementation.
    }
}

public abstract class E : D
{
    public abstract override void DoWork(int i);
}

public class F : E
{
    public override void DoWork(int i)
    {
        // New implementation.
    }
}

Si une méthode virtuelle est déclarée abstraite, elle est toujours virtuelle pour toute classe héritant de la classe abstraite. Une classe héritant d'une méthode abstraite ne peut pas accéder à l'implémentation d'origine de la méthode - dans l'exemple précédent, DoWork sur la classe F ne peut pas appeler DoWork sur la classe D. De cette façon, une classe abstraite peut forcer les classes dérivées à fournir de nouvelles implémentations de méthode pour les méthodes virtuelles .

5
Vincent

J'ai l'impression que cela indique que votre implémentation a vraiment deux parties: une implémentation partielle qui va dans la classe de base, et une fin manquante à cette implémentation que vous voulez dans la sous-classe. Ex:

public abstract class Base
{
   public virtual void PartialImplementation()
   {
      // partial implementation
   }
}

public sealed class Sub : Base
{
   public override void PartialImplementation()
   {
      base.PartialImplementation();
      // rest of implementation
   }
}

Vous souhaitez forcer le remplacement, car l'implémentation initiale est incomplète. Ce que vous pouvez faire est de diviser votre implémentation en deux parties: la partie partielle implémentée et la partie manquante en attente d'implémentation. Ex:

public abstract class Base
{
   public void PartialImplementation()
   {
      // partial implementation

      RestOfImplementation();
   }

   // protected, since it probably wouldn't make sense to call by itself
   protected abstract void RestOfImplementation();      
}

public sealed class Sub : Base
{
   protected override void RestOfImplementation()
   {
      // rest of implementation
   }
}
2
Dave Cousineau

La seule façon que je peux voir de faire ce travail, si vous ne pouvez pas créer un résumé SalesPerson, est la suivante:

1) dans SalesPerson.GiveBonus (...) utilisez la réflexion pour déterminer si 'this' est un SalesPerson, ou une classe dérivée a) si ce n'est pas une classe dérivée, faites le code actuel dans SalesPerson.GiveBonus b) sinon appelez GiveBonusDerived. (déclarez ceci comme virtuel et faites en sorte que l'implémentation dans SalesPerson lève une exception.)

Les inconvénients ici sont, la réflexion est lente. Aucune erreur de temps de compilation si GiveBonusDerived n'est pas déclaré, etc.

1
FallenAvatar

Vous ne pouvez pas utiliser la configuration que vous avez décrite. PTSalesPerson aura déjà une implémentation de GiveBonus car elle hérite de SalesPerson.

1
hackerhasid

Si vous n'allez pas créer directement une instance SalesPerson, alors la chose la plus simple à faire serait de rendre SalesPerson abstrait et cela forcerait toutes les classes enfants à l'implémenter à la place (ou être elles-mêmes abstraites).

S'il y a une logique dans cette méthode commune à tous les vendeurs, alors vous pouvez implémenter GiveBonus sur SalesPerson en tant que méthode de modèle . Ce qui appelle une méthode abstraite requise dans toutes les sous-classes:

Quelle que soit la façon dont vous décidez, la seule façon de forcer une implémentation dans une classe enfant est de rendre la classe de base abstraite.

1
Lee

Vous pouvez toujours faire en sorte que l'implémentation de SalesPerson lève une exception NotImplementedException. : V Mais contractuellement, non, vous ne pouvez pas faire ça.

0
Stephen

C'est assez ancien, mais nous avons une situation quelque peu similaire. La classe de base charge un fichier de configuration et définit les valeurs par défaut de la classe de base. Cependant, le fichier de configuration peut également contenir des valeurs par défaut pour les classes héritées.

C'est ainsi que nous avons obtenu la fonctionnalité combinée.

Méthodes de classe de base

private void processConfigurationFile()
{
    // Load and process the configuration file
    // which happens to be an xml file.
    var xmlDoc = new XmlDocument();
    xmlDoc.Load(configurationPath);

    // This method is abstract which will force
    // the inherited class to override it.
    processConfigurationFile(xmlDoc);
}

protected abstract void processConfigurationFile(XmlDocument document);
0
j2associates

D'accord, je sais que ce message est ancien, cependant, j'ai récemment eu besoin de rechercher cette réponse, donc pour tous ceux qui le recherchent: (J'utilise VS 2012 .net 4.5, donc je ne suis pas sûr des anciennes versions)

J'ai créé une classe abstraite et utilisé la substitution sur les deux classes enfants:

public abstract class Person
{
    public string Name { get; protected set; }
    public abstract void GiveName(string inName);
}

public class Employee : Person
{
    public override void GiveName(string inName)
    {
        Name = inName;
    }
}

public class SalesPerson:Employee
{
    public override void GiveName(string inName)
    {
        Name = "Sales: "+inName;
    }
}

Essai:

SalesPerson x = new SalesPerson();
x.GiveName("Mark"); //Name="Sales: Mark"
Employee e = x;
e.GiveName("Mark"); //Name="Sales: Mark"
Employee m = new Employee();
m.GiveName("Mark"); //Name="Mark"
0
Arkaine80