Propriété abstraite dans la classe de base pour forcer le programmeur à la définir

10

Je code avec un modèle d'état pour un périphérique intégré. J'ai une classe de base / abstraite appelée État, puis chaque classe d'état discrète (concrète) implémente la classe d'État abstraite.

Dans la classe d'état, j'ai plusieurs méthodes abstraites. Si je n'implémente pas les méthodes abstraites dans la classe discrète (concrète), Visual Studio donnera une erreur quelque chose comme ceci:

... Erreur 1 'myConcreteState' n'implémente pas le membre abstrait hérité 'myAbstractState'

Maintenant: j'essaie de créer une propriété String pour chaque État appelé StateName. Chaque fois que je crée une nouvelle classe concrète, je dois définir StateName. Je veux que VS génère une erreur si je ne l'utilise pas. Existe-t-il un moyen simple de procéder?

J'ai essayé cela dans la classe abstract / base:

public abstract string StateName { get; set; }

Mais je n'ai pas besoin d'implémenter les méthodes Get et Set dans chaque état.

Question révisée: Dans une situation idéale, chaque classe d'état aurait besoin que StateName définisse et soit héritée de la classe de base abstraite.

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

Si cette instruction est manquante, Visual Studio générera une erreur comme décrit ci-dessus. Est-ce possible, et si oui comment?

GisMofx
la source
2
Je ne comprends pas la question.
Ben Aaronson
Je suppose que la façon "correcte" de le faire est d'avoir un constructeur protégé sur la classe de base qui nécessite le nom de l'état comme paramètre.
Roman Reiner
@RomanReiner J'ai pensé à faire ça aussi ... mais cela semble redondant car chaque fois que je change / appelle un état, je dois taper le nom.
GisMofx
@BenAaronson J'ai clarifié ma question vers le bas.
GisMofx
Le nom d'état est-il constant par type ou constant par instance?
Roman Reiner

Réponses:

16

Je suppose que la façon "correcte" de le faire est d'avoir un constructeur protégé sur la classe de base qui nécessite le nom de l'état comme paramètre.

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

Les états concrets fournissent alors un constructeur public qui appelle implicitement le constructeur de la classe de base avec le nom approprié.

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

Puisque la classe de base n'expose aucun autre constructeur (ni protégé ni public), chaque classe héritée doit passer par ce constructeur unique et doit donc définir un nom.

Notez que vous n'avez pas besoin de fournir le nom lorsque vous instanciez un état concret car son constructeur s'en charge:

var someState = new SomeState(); // No need to define the name here
var name = someState.Name; // returns "The name of this state"
Roman Reiner
la source
1
Merci! En réalité, mon constructeur de classe de base prend maintenant un argument supplémentaire; j'ai donc deux entrées. Dès que j'ai configuré cela, je reçois des erreurs indiquant que j'ai besoin d'un argument supplémentaire dans les constructeurs de classes d'état ...:base(arg1, arg2)! C'est une solution que je cherchais. Cela aide vraiment à garder mon codage d'État plus cohérent.
GisMofx
3

À partir de C # 6 (je crois - C #: le nouveau et amélioré C # 6.0 ), vous pouvez créer des propriétés getter uniquement. Vous pouvez donc déclarer votre classe de base comme ceci -

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

    // Your other functions....
}

Et puis dans votre sous-classe, vous pouvez implémenter Statecomme ça -

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

Ou encore plus net en utilisant l'opérateur lambda -

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

Les deux renverraient toujours "State_1" et seraient immuables.

John C
la source
1
cela peut être écrit public override string name { get; } = "State_1";qui n'a alors pas besoin de réévaluer la valeur à chaque fois.
Dave Cousineau
2

Vos exigences sont une contradiction:

J'essaie de créer une propriété String pour chaque État appelé StateName.

contre.

Mais je n'ai pas besoin de mettre tout cela en œuvre dans chaque État.

Il n'y a pas de fonction de langue qui vous permet de forcer l'existence d'un membre dans seulement quelques sous-classes. Après tout, l'intérêt d'utiliser une super classe est de s'appuyer sur le fait que toutes les sous-classes auront tous les membres de la super classe (et éventuellement plus). Si vous voulez créer des classes qui agissent comme un Statemais qui n'ont pas de nom, alors par définition, elles ne doivent pas (/ peuvent) être des sous-classes de State.

Vous devez soit modifier vos besoins, soit utiliser autre chose qu'un simple héritage.

Une solution possible avec le code tel qu'il est pourrait être de rendre la méthode Statenon abstraite et de renvoyer une chaîne vide.

nul
la source
Je pense que vous avez sorti cette deuxième déclaration de son contexte, mais je vais clarifier. Je n'ai pas besoin des méthodes Get / Set, je veux simplement StateName = "MyState1"dans chaque état. Si je n'ai pas cette instruction dans la classe d'état, alors, idéalement, Visual Studio générera une erreur.
GisMofx
3
@GisMofx Si vous souhaitez forcer le nom de l'état dans chaque sous-classe, créez le résumé mais ne nécessite pas d'état. Ensuite, chaque sous-classe devra fournir return "MyState1"leur implémentation et peut ajouter un stockage modifiable pour la valeur si elle en a besoin. Mais vous devez clarifier les exigences de la question - chaque type d'état nécessite-t-il un StateName qui peut être lu, et tout type d'état nécessite-t-il une mutation après sa création?
Pete Kirkham
@PeteKirkham chaque état nécessite un nom, il ne doit pas être muté après sa création. Les noms des États sont statiques / immuables.
GisMofx