Abstrakte Eigenschaft in der Basisklasse, um den Programmierer zu zwingen, sie zu definieren

10

Ich codiere mit einem Statusmuster für ein eingebettetes Gerät. Ich habe eine Basis- / abstrakte Klasse namens State und dann implementiert jede diskrete (konkrete) Zustandsklasse die abstrakte Zustandsklasse.

In der Staatsklasse habe ich mehrere abstrakte Methoden. Wenn ich die abstrakten Methoden nicht in der diskreten (konkreten) Klasse implementiere, gibt Visual Studio einen Fehler wie den folgenden aus:

... Fehler 1 'myConcreteState' implementiert kein geerbtes abstraktes Mitglied 'myAbstractState'

Jetzt: Ich versuche, für jeden Status eine String-Eigenschaft namens StateName zu erstellen. Immer wenn ich eine neue konkrete Klasse erstelle, muss ich StateName definieren. Ich möchte, dass VS einen Fehler auslöst, wenn ich ihn nicht verwende. Gibt es eine einfache Möglichkeit, dies zu tun?

Ich habe dies in der abstrakten / Basisklasse versucht:

public abstract string StateName { get; set; }

Ich muss die Methoden Get und Set jedoch nicht in jedem Status implementieren.

Überarbeitete Frage: In einer idealen Situation müsste für jede Statusklasse StateName definiert und von der abstrakten Basisklasse geerbt werden.

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

Wenn diese Anweisung fehlt, generiert Visual Studio einen Fehler wie oben beschrieben. Ist das möglich und wenn ja, wie?

GisMofx
quelle
2
Ich verstehe die Frage nicht.
Ben Aaronson
Ich denke, der "richtige" Weg, dies zu tun, besteht darin, einen geschützten Konstruktor für die Basisklasse zu haben, der den Statusnamen als Parameter benötigt.
Roman Reiner
@RomanReiner Ich habe auch darüber nachgedacht. Aber es scheint überflüssig, weil ich jedes Mal, wenn ich einen Status ändere / aufrufe, den Namen eingeben müsste.
GisMofx
@ BenAaronson Ich habe meine Frage unten geklärt.
GisMofx
Ist der Statusname pro Typ konstant oder pro Instanz konstant?
Roman Reiner

Antworten:

16

Ich denke, der "richtige" Weg, dies zu tun, besteht darin, einen geschützten Konstruktor für die Basisklasse zu haben, der den Statusnamen als Parameter benötigt.

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

Die konkreten Zustände stellen dann einen öffentlichen Konstruktor bereit, der den Basisklassenkonstruktor implizit mit dem entsprechenden Namen aufruft.

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

Da die Basisklasse keine anderen Konstruktoren (weder geschützt noch öffentlich) verfügbar macht, muss jede erbende Klasse diesen einzelnen Konstruktor durchlaufen und daher einen Namen definieren.

Beachten Sie, dass Sie den Namen nicht angeben müssen, wenn Sie einen konkreten Status instanziieren, da der Konstruktor dies übernimmt:

var someState = new SomeState(); // No need to define the name here
var name = someState.Name; // returns "The name of this state"
Roman Reiner
quelle
1
Vielen Dank! In Wirklichkeit nimmt mein Basisklassenkonstruktor jetzt ein zusätzliches Argument an; Ich habe also zwei Eingänge. Sobald ich dies eingerichtet habe, erhalte ich Fehler, dass ich ein zusätzliches Argument in den State-Class-Konstruktoren benötige ...:base(arg1, arg2)! Dies ist eine Lösung, nach der ich gesucht habe. Dies hilft wirklich dabei, meine Statuscodierung konsistenter zu halten.
GisMofx
3

Ab C # 6 (ich glaube - C #: Das neue und verbesserte C # 6.0 ) können Sie nur Getter-Eigenschaften erstellen. Sie könnten also Ihre Basisklasse so deklarieren -

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

    // Your other functions....
}

Und dann können Sie in Ihrer Unterklasse so implementieren State-

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

Oder noch ordentlicher mit dem Lambda-Operator -

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

Beide würden immer "State_1" zurückgeben und unveränderlich sein.

John C.
quelle
1
Dies kann geschrieben werden, public override string name { get; } = "State_1";wodurch der Wert nicht jedes Mal neu bewertet werden muss.
Dave Cousineau
2

Ihre Anforderungen sind ein Widerspruch:

Ich versuche, für jeden Status eine String-Eigenschaft namens StateName zu erstellen .

vs.

Aber ich muss das alles nicht in jedem Staat umsetzen .

Es gibt keine Sprachfunktion, mit der Sie die Existenz eines Mitglieds in nur wenigen Unterklassen erzwingen können. Schließlich besteht der Sinn der Verwendung einer Superklasse darin, sich auf die Tatsache zu verlassen, dass alle Unterklassen alle Mitglieder der Superklasse (und möglicherweise mehr) haben. Wenn Sie Klassen erstellen möchten, die als Statesolche fungieren, aber keinen Namen haben, sollten sie per Definition keine Unterklassen von sein (/ können) State.

Sie müssen entweder Ihre Anforderungen ändern oder etwas anderes als einfache Vererbung verwenden.

Eine mögliche Lösung mit dem Code könnte darin bestehen, die Methode Statenicht abstrakt zu machen und eine leere Zeichenfolge zurückzugeben.

Null
quelle
Ich denke, Sie haben diese zweite Aussage aus dem Zusammenhang gerissen, aber ich werde sie klarstellen. Ich brauche die Get / Set-Methoden nicht, ich möchte einfach StateName = "MyState1"in jedem Zustand. Wenn ich diese Anweisung nicht in der Statusklasse habe, generiert Visual Studio im Idealfall einen Fehler.
GisMofx
3
@GisMofx Wenn Sie den Statusnamen in jeder Unterklasse erzwingen möchten, machen Sie den Abruf abstrakt, benötigen Sie jedoch keinen Status. Dann muss jede Unterklasse return "MyState1"als Implementierung bereitstellen und kann bei Bedarf veränderlichen Speicher für den Wert hinzufügen. Sie müssen jedoch die Anforderungen in der Frage klarstellen: Erfordert jeder Zustandstyp einen lesbaren Statusnamen und muss er nach der Erstellung mutiert werden?
Pete Kirkham
@PeteKirkham Jeder Staat benötigt einen Namen, er sollte nach der Erstellung nicht mutiert werden. Die Namen der Staaten sind statisch / unveränderlich.
GisMofx