Właściwość abstrakcyjna w klasie bazowej, aby zmusić programistę do jej zdefiniowania

10

Koduję za pomocą wzorca stanu dla urządzenia osadzonego. Mam podstawową / abstrakcyjną klasę o nazwie State, a następnie każda dyskretna (konkretna) klasa stanu implementuje abstrakcyjną klasę State.

W klasie państwowej mam kilka metod abstrakcyjnych. Jeśli nie zaimplementuję metod abstrakcyjnych w klasie dyskretnej (konkretnej), Visual Studio wyświetli błąd podobny do tego:

... Błąd 1 „myConcreteState” nie implementuje dziedziczonego elementu abstrakcyjnego „myAbstractState”

Teraz: próbuję utworzyć właściwość String dla każdego stanu o nazwie StateName. Ilekroć tworzę nową konkretną klasę, muszę zdefiniować StateName. Chcę, aby VS zgłosił błąd, jeśli go nie użyję. Czy istnieje prosty sposób to zrobić?

Próbowałem tego w klasie abstrakcyjnej / podstawowej:

public abstract string StateName { get; set; }

Ale nie muszę wdrażać metod Get and Set w każdym stanie.

Zmienione pytanie: W idealnej sytuacji każda klasa stanu musiałaby mieć definicję StateName i być dziedziczona z abstrakcyjnej klasy podstawowej.

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

Jeśli brakuje tej instrukcji, Visual Studio wygeneruje błąd, jak opisano powyżej. Czy to możliwe, a jeśli tak, to w jaki sposób?

GisMofx
źródło
2
Nie rozumiem pytania.
Ben Aaronson
Myślę, że „poprawnym” sposobem na to jest posiadanie chronionego konstruktora w klasie bazowej, który wymaga nazwy stanu jako parametru.
Roman Reiner,
@RomanReiner Myślałem o zrobieniu tego również, ale wydaje się to zbędne, ponieważ za każdym razem, gdy zmieniam / wywołujesz stan, musiałbym wpisać nazwę.
GisMofx
@BenAaronson Wyjaśniłem moje pytanie u dołu.
GisMofx
Czy nazwa stanu jest stała dla typu czy stała dla instancji?
Roman Reiner,

Odpowiedzi:

16

Myślę, że „poprawnym” sposobem na to jest posiadanie chronionego konstruktora w klasie bazowej, który wymaga nazwy stanu jako parametru.

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

Konkretne stany zapewniają publiczny konstruktor, który domyślnie wywołuje konstruktor klasy podstawowej o odpowiedniej nazwie.

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

Ponieważ klasa podstawowa nie ujawnia żadnych innych konstruktorów (ani chronionych, ani publicznych), każda dziedzicząca klasa musi przejść przez ten pojedynczy konstruktor, a zatem musi zdefiniować nazwę.

Pamiętaj, że nie musisz podawać nazwy podczas tworzenia konkretnego stanu, ponieważ zajmuje się tym jego konstruktor:

var someState = new SomeState(); // No need to define the name here
var name = someState.Name; // returns "The name of this state"
Roman Reiner
źródło
1
Dzięki! W rzeczywistości mój konstruktor klasy podstawowej przyjmuje teraz dodatkowy argument; więc mam dwa wejścia. Jak tylko to skonfiguruję, dostaję błędy, że potrzebuję dodatkowego argumentu w konstruktorach klasy stanu ...:base(arg1, arg2)! To było rozwiązanie, którego szukałem. To naprawdę pomaga utrzymać spójność kodowania mojego stanu.
GisMofx,
3

Od wersji C # 6 (uważam, że C #: nowa i ulepszona wersja C # 6.0 ) możesz tworzyć właściwości tylko dla gettera. Abyś mógł tak zadeklarować swoją klasę podstawową -

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

    // Your other functions....
}

A następnie w swojej podklasie możesz Statetak zaimplementować -

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

A nawet dokładniej, używając operatora lambda -

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

Oba zawsze zwracałyby „stan_1” i były niezmienne.

John C.
źródło
1
można to zapisać, public override string name { get; } = "State_1";co nie wymaga za każdym razem ponownej oceny wartości.
Dave Cousineau
2

Twoje wymagania są sprzeczne:

Próbuję utworzyć właściwość String dla każdego stanu o nazwie StateName.

vs.

Ale nie muszę wdrażać tego wszystkiego w każdym stanie.

Nie ma funkcji języka, która pozwala wymusić istnienie członka tylko w kilku podklasach. W końcu celem użycia superklasy jest poleganie na tym, że wszystkie podklasy będą miały wszystkich członków superklasy (i prawdopodobnie więcej). Jeśli chcesz stworzyć klasy, które działają jak Stateale nie mają nazwy, to z definicji nie powinny (/ nie mogą) być podklasami State.

Musisz albo zmienić swoje wymagania, albo użyć czegoś innego niż zwykłe dziedziczenie.

Możliwym rozwiązaniem z obecnym kodem może być uczynienie metody Statenieabstrakcyjną i zwrócenie pustego ciągu.

zero
źródło
Myślę, że wyciągnąłeś to drugie oświadczenie z kontekstu, ale wyjaśnię to. Nie potrzebuję metod Get / Set, po prostu chcę StateName = "MyState1"w każdym stanie. Jeśli nie mam tej instrukcji w klasie stanu, idealnie Visual Studio wygeneruje błąd.
GisMofx
3
@GisMofx Jeśli chcesz wymusić nazwę stanu w każdej podklasie, zrób streszczenie, ale nie wymaga stanu. Następnie każda podklasa będzie musiała podać return "MyState1"jako swoją implementację i może dodać zmienną pamięć dla wartości, jeśli jest to potrzebne. Ale musisz wyjaśnić wymagania w pytaniu - czy każdy typ stanu wymaga StateName, którą można odczytać, i czy jakikolwiek stan wymaga mutacji po utworzeniu?
Pete Kirkham,
@PeteKirkham każdy stan wymaga nazwy, nie należy go mutować po utworzeniu. Nazwy stanów są statyczne / niezmienne.
GisMofx,