web-dev-qa-db-fra.com

Remplacement de champs ou de propriétés dans des sous-classes

J'ai une classe de base abstraite et je souhaite déclarer un champ ou une propriété qui aura une valeur différente dans chaque classe qui hérite de cette classe parente.

Je souhaite le définir dans la classe de base afin de pouvoir le référencer dans une méthode de classe de base - par exemple, en substituant ToString pour indiquer "Cet objet est de type propriété/terrain". J'ai trois façons de voir cela, mais je me demandais - quelle est la meilleure façon ou la mieux acceptée de le faire? Question de débutant, désolée.

Option 1:
Utilisez une propriété abstraite et remplacez-la sur les classes héritées. Cela bénéficie d'être appliqué (vous devez le remplacer) et c'est propre. Cependant, il est légèrement erroné de renvoyer une valeur de code physique plutôt que d'encapsuler un champ. Il s'agit de quelques lignes de code plutôt que de simplement. Je dois aussi déclarer un corps pour "set" mais c'est moins important (et il y a probablement un moyen d'éviter ce dont je ne suis pas au courant).

abstract class Father
{
    abstract public int MyInt { get; set;}
}

class Son : Father
{
    public override int MyInt
    {
        get { return 1; }
        set { }
    }
}

Option 2
Je peux déclarer un champ public (ou un champ protégé) et le remplacer explicitement dans la classe héritée. L'exemple ci-dessous me donnera un avertissement pour utiliser "nouveau" et je peux probablement le faire, mais cela semble mal et cela brise le polymorphisme, ce qui était tout. Cela ne semble pas être une bonne idée ...

abstract class Mother
{
    public int MyInt = 0;
}

class Daughter : Mother
{
    public int MyInt = 1;
}

Option
Je peux utiliser un champ protégé et définir la valeur dans le constructeur. Cela semble assez ordonné, mais cela dépend de moi pour que le constructeur règle toujours cela et que, avec plusieurs constructeurs surchargés, il y ait toujours une chance qu'un chemin de code ne définisse pas la valeur.

abstract class Aunt
{
    protected int MyInt;
}

class Niece : Aunt
{
    public Niece()
    {
        MyInt = 1;
    }
}

C’est un peu une question théorique et je suppose que la réponse doit être l’option 1 car c’est le seul sûr mais je commence tout juste à comprendre C # et je voulais poser cette question à des personnes plus expérimentées.

134
Frans

Des trois solutions uniquement Option 1 est polymorphe.

Les champs par eux-mêmes ne peuvent pas être remplacés. C’est exactement pourquoi Option 2 renvoie le nouveau warning.

La solution à l'avertissement ne consiste pas à ajouter le mot-clé "nouveau", mais à mettre en œuvre l'option 1.

Si vous avez besoin que votre champ soit polymorphe, vous devez l'envelopper dans une propriété.

Option est acceptable si vous n’avez pas besoin d’un comportement polymorphe. Vous devez cependant vous rappeler que, lors de l'exécution de la propriété MyInt, la classe dérivée n'a aucun contrôle sur la valeur renvoyée. La classe de base par elle-même est capable de renvoyer cette valeur.

Voici à quoi pourrait ressembler une implémentation véritablement polymorphe de votre propriété, permettant aux classes dérivées de se trouver dans le contrôle .

abstract class Parent
{
    abstract public int MyInt { get; }
}

class Father : Parent
{
    public override int MyInt
    {
        get { /* Apply formula "X" and return a value */ }
    }
}

class Mother : Parent
{
    public override int MyInt
    {
        get { /* Apply formula "Y" and return a value */ }
    }
}
128
Preets

L'option 2 est un non-starter - vous ne pouvez pas remplacer champs, vous pouvez seulement masquer eux.

Personnellement, je choisirais l'option 1 à chaque fois. J'essaie de garder les champs privés à tout moment. C'est bien sûr si vous avez vraiment besoin de pouvoir outrepasser la propriété. Une autre option consiste à avoir une propriété en lecture seule dans la classe de base, définie à partir d'un paramètre de constructeur:

abstract class Mother
{
    private readonly int myInt;
    public int MyInt { get { return myInt; } }

    protected Mother(int myInt)
    {
        this.myInt = myInt;
    }
}

class Daughter : Mother
{
    public Daughter() : base(1)
    {
    }
}

C'est probablement l'approche la plus appropriée si la valeur ne change pas au cours de la durée de vie de l'instance.

18
Jon Skeet

l'option 2 est une mauvaise idée. Il en résultera quelque chose appelé ombrage; En gros, vous avez deux membres "MyInt" différents, l'un chez la mère et l'autre chez la fille. Le problème, c'est que les méthodes mises en œuvre chez la mère référencent le "MyInt" de la mère, tandis que les méthodes implémentées chez la fille font référence au "MyInt" de la fille. Cela peut entraîner de graves problèmes de lisibilité et de confusion par la suite.

Personnellement, je pense que la meilleure option est 3; car il fournit une valeur centralisée claire et peut être référencé en interne par les enfants sans la peine de définir leurs propres champs - ce qui est le problème de l'option 1.

7
Charlie Lavers

Vous pouvez définir quelque chose comme ceci:

abstract class Father
{
    //Do you need it public?
    protected readonly int MyInt;
}

class Son : Father
{
    public Son()
    {
        MyInt = 1;
    }
}

En définissant la valeur sur readonly, la valeur de cette classe reste inchangée pendant la durée de vie de l'objet.

Je suppose que la question suivante est: pourquoi en avez-vous besoin?

4
Ant

Tu pourrais faire ça

class x
{
    private int _myInt;
    public virtual int myInt { get { return _myInt; } set { _myInt = value; } }
}

class y : x
{
    private int _myYInt;
    public override int myInt { get { return _myYInt; } set { _myYInt = value; } }
}

virtual vous permet d'obtenir un corps qui fait quelque chose, tout en laissant les sous-classes le remplacer.

4
Joshua G

Si vous construisez une classe et que vous voulez qu'il y ait une valeur de base pour la propriété, utilisez le mot clé virtual dans la classe de base. Cela vous permet éventuellement de remplacer la propriété.

En utilisant votre exemple ci-dessus:

//you may want to also use interfaces.
interface IFather
{
    int MyInt { get; set; }
}


public class Father : IFather
{
    //defaulting the value of this property to 1
    private int myInt = 1;

    public virtual int MyInt
    {
        get { return myInt; }
        set { myInt = value; }
    }
}

public class Son : Father
{
    public override int MyInt
    {
        get {

            //demonstrating that you can access base.properties
            //this will return 1 from the base class
            int baseInt = base.MyInt;

            //add 1 and return new value
            return baseInt + 1;
        }
        set
        {
            //sets the value of the property
            base.MyInt = value;
        }
    }
}

Dans un programme:

Son son = new Son();
//son.MyInt will equal 2
1
Keith Aymar

J'irais avec l'option 3, mais j'ai une méthode abstraite setMyInt que les sous-classes sont forcées d'implémenter. De cette façon, vous n'aurez pas le problème d'une classe dérivée en oubliant de la définir dans le constructeur.

abstract class Base 
{
 protected int myInt;
 protected abstract void setMyInt();
}

class Derived : Base 
{
 override protected void setMyInt()
 {
   myInt = 3;
 }
}

À propos, avec l'option 1, si vous ne spécifiez pas set; dans votre propriété de classe de base abstraite, la classe dérivée n'aura pas à l'implémenter.

abstract class Father
{
    abstract public int MyInt { get; }
}

class Son : Father
{
    public override int MyInt
    {
        get { return 1; }
    }
}
0
Winston Smith

Vous pouvez choisir l'option 3 si vous modifiez votre classe de base abstraite pour exiger la valeur de la propriété dans le constructeur, vous ne manquerez aucun chemin. Je considérerais vraiment cette option.

abstract class Aunt
{
    protected int MyInt;
    protected Aunt(int myInt)
    {
        MyInt = myInt;
    }

}

Bien entendu, vous avez toujours la possibilité de rendre le champ privé et, selon les besoins, d’exposer un getter de propriété protégée ou publique.

0
Blair Conrad

J'ai fait ça...

namespace Core.Text.Menus
{
    public abstract class AbstractBaseClass
    {
        public string SELECT_MODEL;
        public string BROWSE_RECORDS;
        public string SETUP;
    }
}

namespace Core.Text.Menus
{
    public class English : AbstractBaseClass
    {
        public English()
        {
            base.SELECT_MODEL = "Select Model";
            base.BROWSE_RECORDS = "Browse Measurements";
            base.SETUP = "Setup Instrument";
        }
    }
}

De cette façon, vous pouvez toujours utiliser les champs.

0
8r13n

L'exemple d'implémentation lorsque vous souhaitez avoir une classe abstraite avec implémentation. Les sous-classes doivent:

  1. Paramétrer l'implémentation d'une classe abstraite.
  2. Hériter pleinement de l'implémentation de la classe abstraite;
  3. Ayez votre propre implémentation.

Dans ce cas, les propriétés nécessaires à l'implémentation ne doivent pas être disponibles, à l'exception de la classe abstraite et de sa propre sous-classe.

    internal abstract class AbstractClass
    {
        //Properties for parameterization from concrete class
        protected abstract string Param1 { get; }
        protected abstract string Param2 { get; }

        //Internal fields need for manage state of object
        private string var1;
        private string var2;

        internal AbstractClass(string _var1, string _var2)
        {
            this.var1 = _var1;
            this.var2 = _var2;
        }

        internal void CalcResult()
        {
            //The result calculation uses Param1, Param2, var1, var2;
        }
    }

    internal class ConcreteClassFirst : AbstractClass
    {
        private string param1;
        private string param2;
        protected override string Param1 { get { return param1; } }
        protected override string Param2 { get { return param2; } }

        public ConcreteClassFirst(string _var1, string _var2) : base(_var1, _var2) { }

        internal void CalcParams()
        {
            //The calculation param1 and param2
        }
    }

    internal class ConcreteClassSecond : AbstractClass
    {
        private string param1;
        private string param2;

        protected override string Param1 { get { return param1; } }

        protected override string Param2 { get { return param2; } }

        public ConcreteClassSecond(string _var1, string _var2) : base(_var1, _var2) { }

        internal void CalcParams()
        {
            //The calculation param1 and param2
        }
    }

    static void Main(string[] args)
    {
        string var1_1 = "val1_1";
        string var1_2 = "val1_2";

        ConcreteClassFirst concreteClassFirst = new ConcreteClassFirst(var1_1, var1_2);
        concreteClassFirst.CalcParams();
        concreteClassFirst.CalcResult();

        string var2_1 = "val2_1";
        string var2_2 = "val2_2";

        ConcreteClassSecond concreteClassSecond = new ConcreteClassSecond(var2_1, var2_2);
        concreteClassSecond.CalcParams();
        concreteClassSecond.CalcResult();

        //Param1 and Param2 are not visible in main method
    }
0
Belomestnykh Sergey