Patrón de diseño o soluciones aceptadas para evitar el cambio de tipos

Estoy tratando de encontrar un patrón de diseño bueno y limpio o una implementación comúnmente aceptada para lidiar con una enumeración de tipos donde el tipo individual solo se conoce en tiempo de ejecución.

Sé que se han hecho preguntas similares antes, pero todavía no me queda claro que las implementaciones alternativas tengan ventajas significativas sobre un conmutador o una serie de if-thens.

Primero, demostraré algunas implementaciones, y luego haré la pregunta:¿Son estas implementaciones mejores o preferidas que el simple cambio? Si es así, ¿por qué? ¿Si no, porque no?

En mi aplicación, envío y recibo datos a través de una transmisión. En tiempo de ejecución, recibo una estructura de datos a través de la serialización que describe qué campos se encuentran dentro de mis datos binarios. Esto incluye el tipo de datos en el campo, es decir, Int32, Bool, Double, etc. En el momento del diseño, todo lo que sé es que los datos pueden estar en uno de varios tipos. Necesito leer los campos de la transmisión y manejar los datos de manera adecuada.

Si se permitía encender Tipos, una soluciónpodría ser como sigue:

Código no laboral:

object ReadDataField(byte [] buff, ref int position, 
    Dictionary<int, Type> fields)
{
    object value;
    int field = buff[position];
    position++;

    switch(fields[field])
    {
        case typeof(Int32):
        {
            value = (Int32)BitConverter.ToInt32(buff, position);
            position += sizeof(Int32);
            break;
        }
        case typeof(Int16):
        {
            value = (Int16)BitConverter.ToInt16(buff, position);
            position += sizeof(Int16);
            break;
        }
        // Etc...
    }

    return value;
}

En mi opinión, este código tiene la ventaja de ser sencillo, fácil de leer y fácil de mantener.

Sin embargo, como el encendido de Tipos no está disponible en C #, implementé lo anterior de la siguiente manera:

Código de trabajo

enum RawDataTypes
{
    Int32,
    Int16,
    Double,
    Single,
    etc.
}

object ReadDataField(byte [] buff, ref int position, 
    Dictionary<int, RawDataTypes> fields)
{
    object value;
    int field = buff[position];
    position++;

    switch(fields[field])
    {
        case RawDataTypes.Int32:
        {
            value = (int)BitConverter.ToInt32(buff, position);
            position += sizeof(int);
            break;
        }
        case RawDataTypes.Int16:
        {
            value = (Int16)BitConverter.ToInt16(buff, position);
            position += sizeof(Int16);
            break;
        }
        // Etc.
    }

    return value;
}

Esto es claramente una solución, pero también es sencillo y fácil de mantener.

Sin embargo, hay varios artículos que detallan que la activación de Tipos no está disponible en C #. Y además de la dificultad de lidiar con la herencia de una manera que produce un resultado esperado, etc., he visto muchas respuestas que dicen que hay un enfoque mucho "mejor" que está más en línea con elespíritu de programación orientada a objetos.

Las soluciones comunes propuestas son 1) usar polimorfismo, o 2) usar una búsqueda de diccionario. Pero la implementación tiene sus propios desafíos.

Con respecto al polimorfismo, el siguiente es un ejemplo del código "no sería bueno si funcionara":

Implementación no funcional del polimorfismo:

object ReadDataField(byte [] buff, int position,
    Dictionary<int, Type> fields)
{
    int field = buff[position];
    position++;

    object value = Activator.CreateInstance(fields[field]);
    // Here we're trying to use an extension method on the raw data type.
    value.ReadRawData(buff, ref position);

    return value;
}

public static Int32 ReadRawData(this Int32 value, byte[] buff, ref int position)
{
    value = BitConverter.ToInt32(buff, position);
    position += sizeof(Int32);

    return value;
}

public static Int16 ReadRawData(this Int16 value, byte[] buff, ref int position)
{
    value = BitConverter.ToInt16 (buff, position);
    position += sizeof(Int16 );

    return value;
}

// Additional methods for each type...

Si intenta compilar el código anterior, obtendrá:

'object' no contiene una definición para 'ReadRawData' y la mejor sobrecarga del método de extensión 'RawDataFieldExtensions.ReadRawData (short, byte [], ref int)' tiene algunos argumentos no válidos en bla bla ...

No puede subclasificar los tipos de datos sin procesar para agregar la funcionalidad, porque están sellados, por lo que los métodos de extensión parecían una opción. Sin embargo, los métodos de extensión no se convertirán de 'objeto' al tipo real, incluso si se llama value.GetType () devuelve el tipo subyacente: System.Int32, System.Int16, etc. El uso de la palabra clave 'dinámica' no ayuda, porqueno puede usar métodos de extensión en un tipo dinámico.

Lo anteriorPuede ser hecho para trabajar pasando una instancia del propio objeto como parámetro a métodos con parámetros polimórficos:

Implementación de trabajo del polimorfismo:

object ReadDataField(byte [] buff, int position,
    Dictionary<int, Type> fields)
{
    int field = buff[position];
    position++;

    dynamic value = Activator.CreateInstance(fields[field]);
    // Here the object is passed to an overloaded method.
    value = ReadRawData(value, buff, ref position);

    return value;
}

public static Int32 ReadRawData(Int32 value, byte[] buff, ref int position)
{
    value = BitConverter.ToInt32(buff, position);
    position += sizeof(Int32);

    return value;
}

public static Int16 ReadRawData(Int16 value, byte[] buff, ref int position)
{
    value = BitConverter.ToInt16 (buff, position);
    position += sizeof(Int16 );

    return value;
}

// Additional methods for each type...

El código anterior funciona y sigue siendo sencillo y mantenible, y probablemente más "en el espíritu de la programación orientada a objetos".

Pero, ¿es realmente "mejor"? Yo diría que lo hacemás difícil de mantener, ya que requiere más búsqueda para ver qué tipos se han implementado.

Un enfoque alternativo es utilizar una búsqueda en el diccionario. Tal código podría verse así:

Implementación del diccionario:

delegate object ReadDelegate(byte [] buff, ref int position);

static Dictionary<Type, ReadDelegate> readers = new Dictionary<Type, ReadDelegate>
{
    { typeof(Int32), ReadInt32 },
    { typeof(Int16), ReadInt16 },
    // Etc...
};

object ReadDataField(byte [] buff, int position,
    Dictionary<int, Type> fields)
{
    int field = buff[position];
    position++;

    object value = readers[fields[field]](buff, ref position);

    return value;
}

public static object ReadInt32(byte[] buff, ref int position)
{
    Int32 value = BitConverter.ToInt32(buff, position);
    position += sizeof(Int32);

    return value;
}

public static object ReadInt16(byte[] buff, ref int position)
{
    return BitConverter.ToInt16(buff, position);
    position += sizeof(Int16);

    return value;
}

// Additional methods for each type...

Una ventaja de la implementación del diccionario, en mi opinión, sobre las soluciones polimórficas es que enumera todos los tipos que se pueden manejar en una ubicación fácil de leer. Esto es útil para la mantenibilidad.

Sin embargo, dados estos ejemplos, ¿hay implementaciones mejores, más limpias, más aceptadas, etc. que tengan una ventaja significativa sobre lo anterior? ¿Se prefieren estas implementaciones con polimorfismo o una búsqueda de diccionario en lugar de usar un interruptor? Realmente no estoy guardando ningún código, y no estoy seguro de haber aumentado la capacidad de mantenimiento del código.

En cualquier caso, todavía necesito enumerar cada uno de los tipos con su propio método. El polimorfismo es diferir el condicional al lenguaje en sí mismo, en lugar de ser explícito con un interruptor o un if-then. Usar un diccionario se basa en los condicionales internos para hacer su propia búsqueda. Al final del día, ¿cuál es la diferencia?

Respuestas a la pregunta(2)

Su respuesta a la pregunta