Шаблон проектирования или принятые решения для предотвращения переключения типов

Я пытаюсь найти хороший, чистый шаблон проектирования или общепринятую реализацию для работы со списком типов, где отдельный тип известен только во время выполнения.

Я знаю, что подобные вопросы задавались ранее, но мне все еще не ясно, что альтернативные реализации имеют существенные преимущества по сравнению с коммутатором или серией if-thens.

Сначала я собираюсь продемонстрировать несколько реализаций, а затем я задам вопрос:Являются ли эти реализации лучше или предпочтительнее простого переключателя? Если так, то почему? Если нет, то почему?

В моем приложении я отправляю и получаю данные через поток. Во время выполнения я получаю структуру данных через сериализацию, которая описывает, какие поля находятся в моих двоичных данных. Это включает в себя тип данных в поле, т. Е. Int32, Bool, Double и т. Д. Во время разработки все, что я знаю, это то, что данные могут относиться к одному из нескольких типов. Мне нужно прочитать поля из потока и обработать данные соответствующим образом.

Если разрешено включение типов, решениеможет быть следующее:

Нерабочий код:

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

На мой взгляд, этот код имеет то преимущество, что он прост, удобен для чтения и прост в обслуживании.

Однако, поскольку включение типов не доступно в C #, я реализовал вышеизложенное следующим образом:

Рабочий код:

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

Это явно обходной путь, но он также прост и прост в обслуживании.

Тем не менее, есть несколько статей, детализирующих включение типов, недоступных в C #. И помимо трудностей, связанных с наследованием таким образом, чтобы получить ожидаемый результат и т. Д., Я видел много ответов, в которых говорилось, что существует гораздо более «лучший» подход, который больше соответствуетдух объектно-ориентированного программирования.

Предлагаемые общие решения: 1) использовать полиморфизм или 2) использовать поиск по словарю. Но реализация любого из них имеет свои проблемы.

Что касается полиморфизма, ниже приведен пример кода «не было бы хорошо, если бы он работал»:

Нерабочая реализация полиморфизма:

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

Если вы попытаетесь скомпилировать приведенный выше код, вы получите:

'object' не содержит определения для 'ReadRawData', а наилучшая перегрузка метода расширения 'RawDataFieldExtensions.ReadRawData (short, byte [], ref int)' имеет несколько недопустимых аргументов в бла-бла ...

Вы не можете создавать подклассы необработанных типов данных для добавления функциональности, потому что они запечатаны, поэтому методы расширения выглядели как вариант. Однако методы расширения не преобразуются из «объекта» в фактический тип, даже если вызов value.GetType () возвращает базовый тип: System.Int32, System.Int16 и т. Д. Использование ключевого слова «dynamic» не помочь, потому чтовы не можете использовать методы расширения для динамического типа.

Вышеможет быть сделано работать, передавая экземпляр самого объекта в качестве параметра методам с полиморфными параметрами:

Рабочая реализация полиморфизма:

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

Приведенный выше код работает и все еще прост и удобен в обслуживании, и, вероятно, более «в духе объектно-ориентированного программирования».

Но действительно ли это лучше? Я бы сказал, что это делаетБольше Сложно поддерживать, так как требуется больше поиска, чтобы увидеть, какие типы были реализованы.

Альтернативный подход заключается в использовании поиска по словарю. Такой код может выглядеть так:

Реализация словаря:

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

На мой взгляд, преимущество реализации словаря перед полиморфными решениями состоит в том, что в нем перечислены все типы, которые могут обрабатываться в одном легко читаемом месте. Это полезно для удобства обслуживания.

Однако, учитывая эти примеры, существуют ли какие-либо лучшие, более чистые, более приемлемые и т. Д. Реализации, которые имеют существенное преимущество по сравнению с вышеизложенным? Являются ли эти реализации, использующие полиморфизм или поиск в словаре, более предпочтительными, чем использование переключателя? Я на самом деле не сохраняю какой-либо код, и я не уверен, что вообще увеличил удобство сопровождения кода.

В любом случае, мне все еще нужно перечислить каждый из типов с его собственным методом. Полиморфизм - это откладывание условного выражения до самого языка, а не явное указание с помощью переключателя или if-then. Использование словаря полагается на внутренние условные выражения, чтобы выполнить свой собственный поиск. В конце дня, в чем разница?

Ответы на вопрос(1)

Ваш ответ на вопрос