Padrão de design ou soluções aceitas para evitar a ativação de tipos

Estou tentando encontrar um padrão de design limpo e bom ou uma implementação comumente aceita para lidar com uma enumeração de tipos em que o tipo individual é conhecido apenas em tempo de execução.

Sei que perguntas semelhantes foram feitas antes, mas ainda não está claro para mim que as implementações alternativas têm vantagens significativas em relação a um switch ou a uma série de if-thens.

Primeiro, vou demonstrar algumas implementações e depois vou fazer a pergunta:Essas implementações são melhores ou preferidas do que a simples troca? Se sim, por quê? Se não, por que não?

Na minha inscrição, envio e recebo dados por um fluxo. Em tempo de execução, recebo uma estrutura de dados via serialização que descreve quais campos estão localizados nos meus dados binários. Isso inclui o Tipo de dados no campo, ou seja, Int32, Bool, Double, etc. Em tempo de design, tudo o que sei é que os dados podem estar em um dos vários tipos. Preciso ler os campos do fluxo e lidar com os dados adequadamente.

Se a ativação de tipos fosse permitida, uma soluçãopoderia ser do seguinte modo:

Código que não trabalha:

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

Na minha opinião, esse código tem a vantagem de ser direto, fácil de ler e simples de manter.

No entanto, como ativar tipos não está disponível no C #, implementei o seguinte, como a seguir:

Código de trabalho:

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

Esta é claramente uma solução alternativa, mas também é direta e fácil de manter.

No entanto, existem vários artigos detalhando a ativação de Tipos não disponível em C #. Além da dificuldade em lidar com a herança de maneira a produzir um resultado esperado, etc., já vi muitas respostas que disseram que há uma abordagem muito "melhor" que está mais alinhada com aespírito de programação orientada a objetos.

As soluções comuns propostas são 1) usar polimorfismo ou 2) usar uma pesquisa de dicionário. Mas a implementação de ambos tem seus próprios desafios.

Em relação ao polimorfismo, a seguir é apresentado um exemplo do código "não seria bom se funcionasse":

Implementação não poluente do 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...

Se você tentar compilar o código acima, receberá:

'object' não contém uma definição para 'ReadRawData' e o melhor método de extensão sobrecarrega 'RawDataFieldExtensions.ReadRawData (short, byte [], ref int)' possui alguns argumentos inválidos em blá blá ...

Você não pode subclassificar os tipos de dados brutos para adicionar a funcionalidade, porque eles são selados, portanto os métodos de extensão pareciam uma opção. No entanto, os métodos de extensão não serão convertidos de 'objeto' para o tipo real, mesmo que chamar value.GetType () retorne o tipo subjacente: System.Int32, System.Int16 etc. O uso da palavra-chave 'dynamic' não ajuda também, porquevocê não pode usar métodos de extensão em um tipo dinâmico.

O de cimapode ser feito para trabalhar passando uma instância do próprio objeto como parâmetro para métodos com parâmetros polimórficos:

Implementação de trabalho do 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...

O código acima funciona e ainda é simples e sustentável, e provavelmente mais "no espírito da programação orientada a objetos".

Mas é realmente melhor? Eu argumentaria que isso fazMais difícil de manter, pois exige mais pesquisa para ver quais tipos foram implementados.

Uma abordagem alternativa é usar uma pesquisa de dicionário. Esse código pode ficar assim:

Implementação de dicionário:

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...

Uma vantagem da implementação do dicionário, na minha opinião, sobre as soluções polimórficas é que ele lista todos os tipos que podem ser manipulados em um local de fácil leitura. Isso é útil para manutenção.

No entanto, dados esses exemplos, existem implementações melhores, mais limpas, mais aceitas etc. que tenham uma vantagem significativa sobre as opções acima? Essas implementações usando polimorfismo ou uma pesquisa de dicionário são preferidas a usar um comutador? Não estou realmente salvando nenhum código e não tenho certeza de ter aumentado a capacidade de manutenção do código.

De qualquer forma, ainda preciso enumerar cada um dos tipos com seu próprio método. O polimorfismo está adiando a condicionalidade para a própria linguagem, em vez de ser explícito com uma opção ou um if-then. O uso de um dicionário depende dos condicionais internos para fazer sua própria pesquisa. No final do dia, qual é a diferença?

questionAnswers(2)

yourAnswerToTheQuestion