Sostituzione di campi o proprietà nelle sottoclassi


145

Ho una classe base astratta e voglio dichiarare un campo o una proprietà che avrà un valore diverso in ogni classe che eredita da questa classe genitore.

Voglio definirlo nella baseclass in modo da poterlo fare riferimento in un metodo di classe base - ad esempio sovrascrivendo ToString per dire "Questo oggetto è di tipo proprietà / campo ". Ho tre modi in cui posso vedere di farlo, ma mi chiedevo: qual è il modo migliore o accettato per farlo? Domanda per principianti, scusa.

Opzione 1:
utilizzare una proprietà astratta e sovrascriverla sulle classi ereditate. Questo beneficia di essere applicato (devi sovrascriverlo) ed è pulito. Tuttavia, sembra leggermente sbagliato restituire un valore di codice fisso anziché incapsulare un campo ed è poche righe di codice anziché solo. Devo anche dichiarare un corpo per "set" ma questo è meno importante (e probabilmente c'è un modo per evitare ciò di cui non sono a conoscenza).

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

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

Opzione 2
Posso dichiarare un campo pubblico (o un campo protetto) e sostituirlo esplicitamente nella classe ereditata. L'esempio che segue mi avvertirà di usare "nuovo" e probabilmente posso farlo, ma sembra sbagliato e rompe il polimorfismo, che era il punto. Non sembra una buona idea ...

abstract class Mother
{
    public int MyInt = 0;
}

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

Opzione 3
Posso usare un campo protetto e impostare il valore nel costruttore. Questo sembra abbastanza ordinato ma si basa su di me per garantire che il costruttore lo imposti sempre e con più costruttori sovraccaricati c'è sempre la possibilità che un percorso del codice non imposti il ​​valore.

abstract class Aunt
{
    protected int MyInt;
}

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

È un po 'una domanda teorica e immagino che la risposta debba essere l'opzione 1 in quanto è l'unica opzione sicura , ma sto solo facendo i conti con C # e volevo chiederlo a persone con più esperienza.


abstract public int MyInt {get; set;} => stringa astratta pubblica IntentName {get; set;}: D
Navid Golforoushan,

Risposte:


136

Delle tre soluzioni solo l' opzione 1 è polimorfica .

I campi da soli non possono essere ignorati. Questo è esattamente il motivo per cui l' opzione 2 restituisce la nuova avvertenza per la parola chiave.

La soluzione all'avviso non è quella di aggiungere la "nuova" parola chiave, ma di implementare l'opzione 1.

Se hai bisogno che il tuo campo sia polimorfico, devi avvolgerlo in una Proprietà.

L'opzione 3 è OK se non hai bisogno di un comportamento polimorfico. Tuttavia, è necessario ricordare che quando si esegue l'accesso alla proprietà MyInt, la classe derivata non ha alcun controllo sul valore restituito. La classe base da sola è in grado di restituire questo valore.

Ecco come potrebbe apparire un'implementazione veramente polimorfica della tua proprietà, consentendo alle classi derivate di avere il controllo .

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 */ }
    }
}

154
Per inciso, penso davvero che il Padre dovrebbe applicare la formula "Y" e la Madre, logicamente, "X".
Peter - Ripristina Monica il

4
E se volessi fornire un'implementazione predefinita in Parent e non fosse astratta?
Aaron Franke il

@AaronFranke Crea la firma: public virtual int MyInt {get; }
Ted Bigham,

@ Peter-ReinstateMonica Sarebbe una "programmazione genetica"
devinbost il

18

L'opzione 2 non è un antipasto: non puoi ignorare i campi, puoi solo nasconderli .

Personalmente, sceglierei l'opzione 1 ogni volta. Cerco di mantenere i campi privati ​​in ogni momento. Questo è se hai davvero bisogno di essere in grado di scavalcare la proprietà, ovviamente. Un'altra opzione è avere una proprietà di sola lettura nella classe base impostata da un parametro del costruttore:

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)
    {
    }
}

Questo è probabilmente l'approccio più appropriato se il valore non cambia nel corso della vita dell'istanza.


Possiamo dire che ora non è corretto sulla base di questo msdn.microsoft.com/en-us/library/9fkccyh4.aspx L'articolo di msdn mostra che puoi sovrascrivere le proprietà
codingbiz

1
@codingbiz: dove parla la mia risposta sulle proprietà? I campi e le proprietà non sono la stessa cosa.
Jon Skeet,

@codingbiz: (La mia risposta ora parla di proprietà, è vero - ma non ha mai detto che non si può scavalcare. Ha detto - e dice - che non è possibile scavalcare i campi , il che è ancora corretto.)
Jon Skeet

7

l'opzione 2 è una cattiva idea. Si tradurrà in qualcosa chiamato ombreggiamento; Fondamentalmente hai due membri "MyInt" diversi, uno nella madre e l'altro nella figlia. Il problema è che i metodi implementati nella madre faranno riferimento al "MyInt" della madre mentre i metodi implementati nella figlia faranno riferimento al "MyInt" della figlia. questo può causare seri problemi di leggibilità e confusione in seguito.

Personalmente, penso che l'opzione migliore sia 3; perché fornisce un chiaro valore centralizzato e può essere referenziato internamente dai bambini senza il fastidio di definire i propri campi - che è il problema con l'opzione 1.


6

Potresti farlo

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 ti permette di ottenere una proprietà di un corpo che fa qualcosa e lascia comunque che le sottoclassi la sovrastino.


4

Potresti definire qualcosa del genere:

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

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

Impostando il valore in sola lettura, garantisce che il valore per quella classe rimanga invariato per la durata dell'oggetto.

Suppongo che la prossima domanda sia: perché ne hai bisogno?


Statico è una cattiva scelta di parole poiché implica che il valore sia quindi condiviso tra tutte le istanze della classe, il che ovviamente non lo è.
Winston Smith,

3

Se stai costruendo una classe e vuoi che ci sia un valore base per la proprietà, usa la virtualparola chiave nella classe base. Ciò consente di sostituire facoltativamente la proprietà.

Usando il tuo esempio sopra:

//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;
        }
    }
}

In un programma:

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

0

Andrei con l'opzione 3, ma ho un metodo setMyInt astratto che le sottoclassi sono costrette a implementare. In questo modo non si avrà il problema di una classe derivata che si dimentica di impostarla nel costruttore.

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

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

A proposito, con l'opzione uno, se non si specifica set; nella proprietà della classe base astratta, la classe derivata non dovrà implementarla.

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

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

0

Puoi andare con l'opzione 3 se modifichi la tua classe base astratta per richiedere il valore della proprietà nel costruttore, non perderai alcun percorso. Considererei davvero questa opzione.

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

}

Naturalmente, hai ancora la possibilità di rendere il campo privato e quindi, a seconda delle necessità, esporre un getter di proprietà pubblica o protetto.


0

L'ho fatto...

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";
        }
    }
}

In questo modo è ancora possibile utilizzare i campi.


Mi sembra che sia una soluzione ad hoc per la prototipazione o la dimostrazione.
Zimano,

0

L'implementazione di esempio quando si desidera avere una classe astratta con implementazione. Le sottoclassi devono:

  1. Parametrizzare l'implementazione di una classe astratta.
  2. Ereditare completamente l'implementazione della classe astratta;
  3. Hai la tua implementazione.

In questo caso, le proprietà necessarie per l'implementazione non dovrebbero essere disponibili per l'uso ad eccezione della classe astratta e della sua sottoclasse.

    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
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.