Zastępowanie pól lub właściwości w podklasach


145

Mam abstrakcyjną klasę bazową i chcę zadeklarować pole lub właściwość, która będzie miała inną wartość w każdej klasie, która dziedziczy z tej klasy nadrzędnej.

Chcę zdefiniować go w klasie bazowej, aby móc odwoływać się do niego w metodzie klasy bazowej - na przykład przesłaniając ToString, aby powiedzieć „Ten obiekt jest typu property / field ”. Mam trzy sposoby na zrobienie tego, ale zastanawiałem się - jaki jest najlepszy lub akceptowany sposób zrobienia tego? Pytanie dla początkujących, przepraszam.

Opcja 1:
Użyj właściwości abstrakcyjnej i zastąp ją w klasach dziedziczonych. Korzysta z tego, że jest wymuszony (musisz to zmienić) i jest czysty. Ale wydaje się nieco niewłaściwe zwracanie wartości kodu stałego zamiast hermetyzacji pola i jest to kilka wierszy kodu zamiast tylko. Muszę też zadeklarować ciało dla „zestawu”, ale jest to mniej ważne (i prawdopodobnie jest sposób na uniknięcie tego, czego nie jestem świadomy).

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

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

Opcja 2
Mogę zadeklarować pole publiczne (lub pole chronione) i jawnie nadpisać je w dziedziczonej klasie. Poniższy przykład ostrzega mnie przed użyciem słowa „nowy” i prawdopodobnie mogę to zrobić, ale wydaje mi się to złe i łamie polimorfizm, o który chodziło. To nie jest dobry pomysł ...

abstract class Mother
{
    public int MyInt = 0;
}

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

Opcja 3
Mogę użyć chronionego pola i ustawić wartość w konstruktorze. Wydaje się to całkiem uporządkowane, ale polega na tym, że konstruktor zawsze to ustawia, a przy wielu przeciążonych konstruktorach zawsze istnieje szansa, że ​​jakaś ścieżka kodu nie ustawi wartości.

abstract class Aunt
{
    protected int MyInt;
}

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

To trochę teoretyczne pytanie i wydaje mi się, że odpowiedzią musi być opcja 1, ponieważ jest to jedyna bezpieczna opcja, ale dopiero zaczynam uporać się z C # i chciałem zapytać o to osoby z większym doświadczeniem.


streszczenie publiczne int MyInt {get; set;} => public abstract string IntentName {get; set;}: D
Navid Golforoushan

Odpowiedzi:


136

Z trzech rozwiązań tylko opcja 1 jest polimorficzna .

Same pola nie mogą zostać nadpisane. Właśnie dlatego opcja 2 zwraca nowe ostrzeżenie dotyczące słowa kluczowego.

Rozwiązaniem problemu nie jest dodanie słowa kluczowego „nowe”, ale wdrożenie Opcji 1.

Jeśli chcesz, aby twoje pole było polimorficzne, musisz owinąć je we właściwości.

Opcja 3 jest OK, jeśli nie potrzebujesz zachowania polimorficznego. Należy jednak pamiętać, że gdy w czasie wykonywania uzyskuje się dostęp do właściwości MyInt, klasa pochodna nie ma kontroli nad zwracaną wartością. Sama klasa bazowa jest w stanie zwrócić tę wartość.

Tak może wyglądać prawdziwie polimorficzna implementacja twojej właściwości, pozwalająca na kontrolę klas pochodnych .

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

153
Na marginesie, naprawdę uważam, że Ojciec powinien zastosować formułę „Y”, a Matka, logicznie, „X”.
Peter - Przywróć Monikę

4
A co jeśli chciałbym podać domyślną implementację w Parent i czy nie jest ona abstrakcyjna?
Aaron Franke,

@AaronFranke Zrób podpis: public virtual int MyInt {get; }
Ted Bigham

@ Peter-ReinstateMonica To byłoby „programowanie genetyczne”
devinbost

18

Opcja 2 nie jest początkowa - nie możesz nadpisywać pól, możesz je tylko ukryć .

Osobiście za każdym razem wybrałbym opcję 1. Staram się, aby pola były zawsze prywatne. Dzieje się tak, jeśli naprawdę musisz w ogóle mieć możliwość nadpisania właściwości. Inną opcją jest posiadanie właściwości tylko do odczytu w klasie bazowej, która jest ustawiana za pomocą parametru konstruktora:

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

To prawdopodobnie najbardziej odpowiednie podejście, jeśli wartość nie zmienia się przez cały okres istnienia instancji.


Czy możemy teraz powiedzieć, że to nie jest poprawne na podstawie tego msdn.microsoft.com/en-us/library/9fkccyh4.aspx Artykuł msdn pokazuje, że można zastąpić właściwości
codingbiz

1
@codingbiz: gdzie moja odpowiedź dotyczy nieruchomości? Pola i właściwości to nie to samo.
Jon Skeet,

@codingbiz: (Moja odpowiedź teraz mówi o właściwościach, co prawda - ale nigdy nie było powiedziane, że nie można ich zastąpić. Mówi - i mówi - że nie można zastąpić pól , co nadal jest poprawne.)
Jon Skeet

7

opcja 2 to zły pomysł. Spowoduje to coś, co nazywa się shadowing; Zasadniczo masz dwóch różnych członków „MyInt”, jednego w matce, a drugiego w córce. Problem polega na tym, że metody zaimplementowane w matce będą odwoływać się do „MyInt” matki, podczas gdy metody zaimplementowane w córce będą odwoływać się do „MyInt” córki. może to spowodować poważne problemy z czytelnością, a później zamieszanie.

Osobiście uważam, że najlepszą opcją jest 3; ponieważ zapewnia wyraźną scentralizowaną wartość i może być wewnętrznie przywoływana przez dzieci bez konieczności definiowania własnych pól - co jest problemem w przypadku opcji 1.


6

Możesz to zrobić

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 pozwala uzyskać właściwość, która coś robi i nadal pozwala podklasom zastąpić to.


4

Możesz zdefiniować coś takiego:

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

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

Ustawiając wartość jako tylko do odczytu, zapewnia, że ​​wartość dla tej klasy pozostanie niezmieniona przez cały okres istnienia obiektu.

Przypuszczam, że następne pytanie brzmi: dlaczego tego potrzebujesz?


Statyczny to kiepski wybór słów, ponieważ oznacza, że ​​wartość jest następnie dzielona między wszystkie wystąpienia klasy, co oczywiście nie jest.
Winston Smith

3

Jeśli tworzysz klasę i chcesz, aby dla właściwości istniała wartość podstawowa, użyj virtualsłowa kluczowego w klasie bazowej. Pozwala to opcjonalnie zastąpić właściwość.

Korzystając z powyższego przykładu:

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

W programie:

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

0

Wybrałbym opcję 3, ale mam abstrakcyjną metodę setMyInt, którą podklasy są zmuszone do implementacji. W ten sposób nie będziesz mieć problemu z zapominaniem klasy pochodnej o ustawieniu jej w konstruktorze.

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

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

Przy okazji, z opcją pierwszą, jeśli nie określisz set; we właściwości abstrakcyjnej klasy bazowej, klasa pochodna nie będzie musiała jej implementować.

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

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

0

Możesz skorzystać z opcji 3, jeśli zmodyfikujesz abstrakcyjną klasę bazową, aby wymagała wartości właściwości w konstruktorze, nie przegapisz żadnych ścieżek. Naprawdę rozważyłbym tę opcję.

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

}

Oczywiście nadal możesz ustawić pole jako prywatne, a następnie, w zależności od potrzeb, ujawnić obiekt pobierający chronioną lub publiczną własność.


0

Ja to zrobiłem...

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

W ten sposób nadal możesz używać pól.


Wydaje mi się, że jest to fajne rozwiązanie ad-hoc do prototypowania lub demonstrowania.
Zimano,

0

Przykładowa implementacja, gdy chcesz mieć klasę abstrakcyjną z implementacją. Podklasy muszą:

  1. Sparametryzuj implementację klasy abstrakcyjnej.
  2. W pełni dziedzicz implementację klasy abstrakcyjnej;
  3. Miej własną implementację.

W takim przypadku właściwości, które są niezbędne do implementacji, nie powinny być dostępne do użytku, z wyjątkiem klasy abstrakcyjnej i jej własnej podklasy.

    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
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.