Перевизначення полів або властивостей у підкласах


145

У мене є абстрактний базовий клас, і я хочу оголосити поле або властивість, яке матиме інше значення у кожному класі, який успадковується від цього батьківського класу.

Я хочу визначити його в базовому класі, щоб я міг посилатися на нього методом базового класу - наприклад, переосмисливши ToString, щоб сказати "Цей об'єкт має властивість / поле ". У мене є три способи, які я бачу робити це, але мені було цікаво - який найкращий чи прийнятий спосіб зробити це? Питання новачка, вибачте.

Варіант 1:
Використовуйте абстрактне властивість та замініть його на успадкованих класах. Це виграє від примусового виконання (ви маєте це перекрити), і це чисто. Але повернути значення жорсткого коду, а не інкапсулювати поле, вважається трохи неправильним, і це кілька рядків коду, а не просто. Я також повинен оголосити орган для "набору", але це менш важливо (і, мабуть, є спосіб уникнути того, про що я не знаю).

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

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

Варіант 2
Я можу оголосити загальнодоступне поле (або захищене поле) і явно перекрити його у спадок. Наведений нижче приклад подасть мені попередження використовувати "нове", і я, мабуть, можу це зробити, але це почувається неправильно, і це порушує поліморфізм, про який і було все. Це не здається гарною ідеєю ...

abstract class Mother
{
    public int MyInt = 0;
}

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

Варіант 3
Я можу використовувати захищене поле і встановити значення в конструкторі. Це здається досить охайним, але покладається на мене тим, що конструктор завжди встановлює це, і при кількох перевантажених конструкторах завжди є ймовірність, що якийсь шлях коду не встановить значення.

abstract class Aunt
{
    protected int MyInt;
}

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

Це трохи теоретичне запитання, і я думаю, що відповідь має бути варіантом 1, оскільки це єдиний безпечний варіант, але я просто стикаюся з C # і хотів би задати це людям з більшим досвідом.


абстрактний публічний int MyInt {get; set;} => публічний абстрактний рядок IntentName {get; сет;}: D
Навід Голфорушан

Відповіді:


136

З трьох рішень лише варіант 1 є поліморфним .

Поля самостійно неможливо змінити. Саме тому варіант 2 повертає нове попередження про ключові слова.

Рішенням попередження є не додавання "нового" ключового слова, а реалізація Варіанту 1.

Якщо вам потрібно, щоб ваше поле було поліморфним, вам потрібно загорнути його у властивість.

Варіант 3 добре, якщо вам не потрібна поліморфна поведінка. Ви повинні пам’ятати, що коли під час виконання доступу до властивості MyInt, отриманий клас не контролює повернене значення. Базовий клас сам по собі може повернути це значення.

Це як дійсно поліморфний реалізація вашої власності може виглядати, дозволяючи похідним класам бути в контролі .

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
Як осторонь, я дійсно думаю, що Отець повинен застосувати формулу "Y", а мати, логічно, "X".
Пітер - Відновити Моніку

4
Що робити, якщо я хотів би поставити реалізацію за замовчуванням у Parent і якщо вона не буде абстрактною?
Аарон Франке

@AaronFranke Зробіть підпис: public virtual int MyInt {get; }
Тед Бігхем

@ Peter-ReinstateMonica Це було б "генетичне програмування"
devinbost

18

Варіант 2 - це нестартер - ви не можете змінювати поля, ви можете їх лише заховати .

Особисто я б кожен раз пішов на варіант 1. Я намагаюся постійно зберігати поля приватними. Це, якщо вам дійсно потрібно мати змогу взагалі замінити майно. Інший варіант - мати властивість лише для читання в базовому класі, встановленому з параметра конструктора:

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

Це, мабуть, найбільш підходящий підхід, якщо значення не змінюється протягом життя екземпляра.


Чи можемо ми сказати, що це зараз неправильно, виходячи з цього msdn.microsoft.com/en-us/library/9fkccyh4.aspx У статті msdn показано, що ви можете змінити властивості
codingbiz

1
@codingbiz: де моя відповідь говорить про властивості? Поля та властивості - це не одне і те ж.
Джон Скіт

@codingbiz: (Моя відповідь зараз говорить про властивості, правда, - але ніколи не говорив, що ти не можеш їх перекрити. Він сказав - і каже - що ти не можеш змінити поля , що все-таки правильно.)
Джон Скіт

7

варіант 2 - це погана ідея. Це призведе до того, що називається затіненням; В основному у вас є два різні члени "MyInt", один у матері, а другий у дочки. Проблема в цьому полягає в тому, що методи, реалізовані в матері, будуть посилатися на "MyInt" матері, тоді як методи, реалізовані в доньці, посилаються на "MyInt" дочки. це може спричинити серйозні проблеми з читабельністю та плутанину згодом.

Особисто я вважаю, що найкращий варіант - 3; тому що це забезпечує чітке централізоване значення, і діти можуть посилатися внутрішньо без клопоту з визначенням власних полів - у чому полягає проблема з варіантом 1.


6

Ви могли це зробити

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

Віртуальна дозволяє отримати властивість органу, який щось робить, і все ж дозволяє підкласам заміняти це.


4

Ви можете визначити щось подібне:

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

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

Встановлюючи значення як тільки прочитане, воно гарантує, що значення для цього класу залишається незмінним протягом життя об'єкта.

Я припускаю, що наступне питання: навіщо це вам потрібно?


Статичний - це поганий вибір слів, оскільки це означає, що значення потім розподіляється між усіма примірниками класу, що, звичайно, не є.
Вінстон Сміт

3

Якщо ви будуєте клас і хочете, щоб у ньому було базове значення, використовуйте virtualключове слово в базовому класі. Це дозволяє необов'язково змінювати властивість.

Використовуючи приклад вище:

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

У програмі:

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

0

Я б пішов з варіантом 3, але маю абстрактний метод setMyInt, який підкласи змушені реалізовувати. Таким чином, у вас не буде проблеми похідного класу, забувши встановити його в конструкторі.

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

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

До речі, з варіантом один, якщо ви не вказали набір; у вашому абстрактному властивості базового класу похідному класу не доведеться його реалізовувати.

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

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

0

Можна скористатися варіантом 3, якщо ви модифікуєте свій абстрактний базовий клас, щоб вимагати значення властивості в конструкторі, ви не пропустите жодних шляхів. Я б справді розглядав цей варіант.

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

}

Звичайно, у вас все ще є можливість зробити поле приватним, а потім, залежно від потреби, викрити захищеного або публічного власника.


0

Я зробив це ...

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

Таким чином ви все ще можете використовувати поля.


Я відчуваю, що це приємно як спеціальне рішення для складання прототипів чи демонстрації.
Зімано

0

Приклад реалізації, коли потрібно мати абстрактний клас з реалізацією. Підкласи повинні:

  1. Параметризуйте реалізацію абстрактного класу.
  2. Повністю успадкувати реалізацію абстрактного класу;
  3. Майте власну реалізацію.

У цьому випадку властивості, необхідні для реалізації, не повинні бути доступними для використання, крім абстрактного класу та власного підкласу.

    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
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.