Абстрактное свойство в базовом классе, чтобы заставить программиста определить его

10

Я кодирую с шаблоном состояния для встроенного устройства. У меня есть базовый / абстрактный класс, называемый State, и затем каждый дискретный (конкретный) класс состояний реализует абстрактный State State.

В государственном классе у меня есть несколько абстрактных методов. Если я не реализую абстрактные методы в дискретном (конкретном) классе, Visual Studio выдаст ошибку примерно так:

... Ошибка 1 'myConcreteState' не реализует унаследованный абстрактный член 'myAbstractState'

Теперь: я пытаюсь создать свойство String для каждого состояния с именем StateName. Всякий раз, когда я создаю новый конкретный класс, мне нужно определить StateName. Я хочу, чтобы VS выдавал ошибку, если я ее не использую. Есть ли простой способ сделать это?

Я пробовал это в абстрактном / базовом классе:

public abstract string StateName { get; set; }

Но мне не нужно реализовывать методы Get и Set в каждом государстве.

Пересмотренный вопрос: в идеальной ситуации каждый класс состояний должен иметь определения StateName и наследоваться от абстрактного базового класса.

StateName = "MyState1"; //or whatever the state's name is

Если это утверждение отсутствует, Visual Studio выдаст ошибку, как описано выше. Возможно ли это, и если да, то как?

GisMofx
источник
2
Я не понимаю вопроса.
Бен Ааронсон
Я предполагаю, что «правильный» способ сделать это - иметь защищенный конструктор в базовом классе, который требует имя состояния в качестве параметра.
Роман Райнер
@RomanReiner Я тоже думал об этом ... но это кажется излишним, потому что каждый раз, когда меняют / вызывают состояние, мне нужно было бы вводить имя.
GisMofx
@BenAaronson Я уточнил свой вопрос в нижней части.
GisMofx
Является ли имя состояния постоянным для каждого типа или постоянным для каждого экземпляра?
Роман Райнер

Ответы:

16

Я предполагаю, что «правильный» способ сделать это - иметь защищенный конструктор в базовом классе, который требует имя состояния в качестве параметра.

public abstract class State
{
    private readonly string _name;

    protected State(string name)
    {
        if(String.IsNullOrEmpty(name))
            throw new ArgumentException("Must not be empty", "name");

        _name = name;
    }

    public string Name { get { return _name; } }
}

Затем конкретные состояния предоставляют открытый конструктор, который неявно вызывает конструктор базового класса с соответствующим именем.

public abstract class SomeState : State
{
    public SomeState() : base("The name of this state")
    {
        // ...
    }
}

Поскольку базовый класс не предоставляет никаких других конструкторов (ни защищенных, ни общедоступных), каждый наследующий класс должен пройти через этот единственный конструктор и, следовательно, должен определить имя.

Обратите внимание, что вам не нужно указывать имя при создании конкретного состояния, потому что его конструктор позаботится об этом:

var someState = new SomeState(); // No need to define the name here
var name = someState.Name; // returns "The name of this state"
Римский рейнер
источник
1
Спасибо! В действительности мой конструктор базового класса теперь принимает дополнительный аргумент; поэтому у меня есть два входа. Как только я это настрою, я получаю ошибки, которые требуют дополнительного аргумента в конструкторах класса состояния ...:base(arg1, arg2)! Это решение, которое я искал. Это действительно помогает сделать кодирование моего состояния более последовательным.
GisMofx
3

Начиная с C # 6 (я верю - C #: новый и улучшенный C # 6.0 ), вы можете создавать свойства только для геттеров. Таким образом, вы можете объявить свой базовый класс следующим образом:

public abstract class State
{
    public abstract string name { get; }

    // Your other functions....
}

И тогда в вашем подклассе вы можете реализовать Stateтак -

public class SomeState : State
{
    public override string name { get { return "State_1"; } }
}

Или даже аккуратнее, используя лямбда-оператор -

public class SomeState : State
{
    public override string name => "State_1";
}

Оба всегда возвращали бы «State_1» и были бы неизменными.

Джон С
источник
1
это может быть написано, public override string name { get; } = "State_1";что тогда не нужно переоценивать значение каждый раз.
Дейв Кузино
2

Ваши требования противоречат:

Я пытаюсь создать свойство String для каждого состояния с именем StateName.

против

Но мне не нужно реализовывать все это в каждом штате.

Там нет языковой функции, которая позволяет вам форсировать существование члена только в нескольких подклассах. В конце концов, смысл использования суперкласса заключается в том, чтобы полагаться на тот факт, что у всех подклассов будут все члены суперкласса (и, возможно, больше). Если вы хотите создать классы, которые действуют как, Stateно не имеют имени, то по определению они не должны (/ могут) быть подклассами State.

Вы должны либо изменить свои требования, либо использовать что-то кроме простого наследования.

Возможное решение с использованием кода в том виде, в каком оно есть, - сделать метод Stateне абстрактным и вернуть пустую строку.

значение NULL
источник
Я думаю, что вы вынули это второе утверждение из контекста, но я уточню. Мне не нужны методы Get / Set, я просто хочу StateName = "MyState1"в каждом государстве. Если у меня нет этого оператора в классе состояний, то в идеале Visual Studio выдаст ошибку.
GisMofx
3
@GisMofx Если вы хотите форсировать имя состояния в каждом подклассе, сделайте его абстрактным, но не требуйте состояния. Тогда каждый подкласс должен будет предоставить в return "MyState1"качестве своей реализации и может добавить изменяемое хранилище для значения, если оно им нужно. Но вам нужно уточнить требования в вопросе - требует ли каждый тип состояния StateName, которое может быть прочитано, и требует ли какой-либо тип состояния его мутирование после создания?
Пит Киркхам
@PeteKirkham каждое государство требует имя, оно не должно быть видоизменено после создания. Имена состояний являются статическими / неизменяемыми.
GisMofx