Propiedad abstracta en la clase base para forzar al programador a definirla

10

Estoy codificando con un patrón de estado para un dispositivo incrustado. Tengo una clase base / abstracta llamada Estado y luego cada clase de estado discreta (concreta) implementa la Clase de estado abstracta.

En la clase de estado tengo varios métodos abstractos. Si no implemento los métodos abstractos en la clase discreta (concreta), Visual Studio dará un error similar a este:

... El error 1 'myConcreteState' no implementa el miembro abstracto heredado 'myAbstractState'

Ahora: estoy tratando de crear una propiedad de cadena para cada estado llamada StateName. Cada vez que creo una nueva clase concreta, necesito definir StateName. Quiero que VS arroje un error si no lo uso. ¿Hay una manera simple de hacer esto?

He intentado esto en la clase abstracta / base:

public abstract string StateName { get; set; }

Pero no necesito implementar los métodos Get y Set en cada estado.

Pregunta revisada: en una situación ideal, se requeriría que cada clase de estado tenga StateName define y se herede de la clase base abstracta.

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

Si falta esa declaración, Visual Studio generará un error como se describió anteriormente. ¿Es posible? y si lo es, cómo?

GisMofx
fuente
2
No entiendo la pregunta.
Ben Aaronson
Supongo que la forma "correcta" de hacer esto es tener un constructor protegido en la clase base que requiera el nombre del estado como parámetro.
Roman Reiner
@RomanReiner Pensé en hacer eso también ... pero parece redundante porque cada vez que cambio / llamo a un estado, tendría que escribir el nombre.
GisMofx
@BenAaronson He aclarado mi pregunta cerca del fondo.
GisMofx
¿El nombre del estado es constante por tipo o constante por instancia?
Roman Reiner

Respuestas:

16

Supongo que la forma "correcta" de hacer esto es tener un constructor protegido en la clase base que requiera el nombre del estado como parámetro.

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

Los estados concretos proporcionan un constructor público que llama implícitamente al constructor de la clase base con el nombre apropiado.

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

Como la clase base no expone ningún otro constructor (ni protegido ni público), cada clase heredada debe pasar por este único constructor y, por lo tanto, debe definir un nombre.

Tenga en cuenta que no necesita proporcionar el nombre cuando crea una instancia de un estado concreto porque su constructor se encarga de eso:

var someState = new SomeState(); // No need to define the name here
var name = someState.Name; // returns "The name of this state"
Reiner romano
fuente
1
¡Gracias! En realidad, mi constructor de clase base ahora toma un argumento adicional; entonces tengo dos entradas. ¡Tan pronto como configuro esto, recibo errores que necesito un argumento adicional en los constructores de clase de estado ...:base(arg1, arg2)! Esta es una solución que estaba buscando. Esto realmente ayuda a mantener mi codificación de estado más coherente.
GisMofx
3

A partir de C # 6 (creo - C #: El nuevo y mejorado C # 6.0 ) puede crear propiedades solo getter. Entonces podrías declarar tu clase base de esta manera:

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

    // Your other functions....
}

Y luego, en su subclase, puede implementar Stateasí:

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

O incluso más ordenado con el operador lambda:

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

Ambos siempre devolverán "Estado_1" y serán inmutables.

John C
fuente
1
esto se puede escribir, public override string name { get; } = "State_1";lo que no necesita reevaluar el valor cada vez.
Dave Cousineau el
2

Sus requisitos son una contradicción:

Estoy tratando de crear una propiedad de cadena para cada estado llamada StateName.

vs.

Pero no necesito implementar todo eso en cada estado.

No hay una función de lenguaje que le permita forzar la existencia de un miembro en solo unas pocas subclases. Después de todo, el objetivo de utilizar una superclase es confiar en el hecho de que todas las subclases tendrán todos los miembros de la superclase (y posiblemente más). Si desea crear clases que actúen como un Statepero no tengan un nombre, entonces, por definición, no deberían (/ pueden) ser subclases de State.

Tiene que cambiar sus requisitos o usar algo que no sea simple herencia.

Una posible solución con el código tal como es podría ser hacer que el método Stateno sea abstracto y devolver una cadena vacía.

nulo
fuente
Creo que tomaste esa segunda declaración fuera de contexto, pero aclararé. No necesito los métodos Get / Set, simplemente quiero StateName = "MyState1"en cada estado. Si no tengo esa declaración en la clase de estado, idealmente Visual Studio generará un error.
GisMofx
3
@GisMofx Si desea forzar el nombre del estado en cada subclase, haga el resumen pero no requiera un estado. Luego, cada subclase tendrá que proporcionar return "MyState1"como su implementación, y puede agregar almacenamiento mutable para el valor si lo necesitan. Pero debe aclarar los requisitos de la pregunta: ¿cada tipo de estado requiere un StateName que se pueda leer, y cualquier tipo de estado requiere que se mute después de la creación?
Pete Kirkham
@PeteKirkham cada estado requiere un nombre, no debe ser mutado después de la creación. Los nombres de los estados son estáticos / inmutables.
GisMofx