用于避免开启类型的设计模式或接受的解决方案

tfj*_*eld 6 c# polymorphism design-patterns switch-statement

我正在尝试找到一个好的,干净的设计模式或普遍接受的实现来处理类型的枚举,其中仅在运行时知道单个类型.

我之前已经问过类似的问题,但我仍然不清楚替代实现相比交换机或一系列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;
}
Run Code Online (Sandbox Code Playgroud)

在我看来,这段代码的优点是简单易读,易于维护.

但是,由于在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;
}
Run Code Online (Sandbox Code Playgroud)

这显然是一种解决方法,但它也很简单,易于维护.

但是,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...
Run Code Online (Sandbox Code Playgroud)

如果您尝试编译上面的代码,您将获得:

'object'不包含'ReadRawData'的定义,最好的扩展方法重载'RawDataFieldExtensions.ReadRawData(short,byte [],ref int)'在blah blah中有一些无效的参数...

您不能将原始数据类型子类化以添加功能,因为它们是密封的,因此扩展方法似乎是一种选择.但是,扩展方法不会从'object'转换为实际类型,即使调用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...
Run Code Online (Sandbox Code Playgroud)

上面的代码工作,并且仍然是直截了当的,可维护的,可能更多"本着面向对象编程的精神".

但它真的有"更好吗?" 我认为它使维护更加困难,因为它需要更多的搜索来查看已经实现了哪些类型.

另一种方法是使用字典查找.这样的代码可能如下所示:

字典实现:

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...
Run Code Online (Sandbox Code Playgroud)

在我看来,字典实现对多态解决方案的一个优点是,它列出了可以在一个易于阅读的位置处理的所有类型.这对可维护性很有用.

但是,鉴于这些例子,是否有更好,更清洁,更容易接受等实现,这些实现具有明显的优势?这些实现是使用多态还是字典查找比使用交换机更受欢迎?我并没有真正保存任何代码,我不确定我是否已经增加了代码的可维护性.

无论如何,我仍然需要使用自己的方法枚举每个类型.多态性将条件推迟到语言本身,而不是用开关或if-then显式.使用字典依赖于内部条件来进行自己的查找.在一天结束时,有什么区别?

Dav*_*rne 3

  1. 使用全部实现通用接口的“转换器”(或其他)的静态集合。然后,您可以迭代该集合,询问每个集合是否处理该类型。如果他们这样做,那就要求他们这样做。每个转换器只知道它的类型。
  2. 使用相同的静态转换器“集合”,但将它们保存在按类型键入的字典中。然后从字典中按类型请求转换器并要求它为您进行转换。

  • 我喜欢 2。集合元素可以是委托,动态定义,因为它们是单行代码(基本上是 Convert() 调用)。-- 通常,开关的答案是由开关标准索引的字典。它将复杂性从代码转移到数据。这是否更具可读性或可维护性还有待商榷——没有什么比一个漂亮的老式交换机更容易理解和维护的了。但是,一旦您在某处编写了“第二个”开关,就可以将信息存储在数据中。 (2认同)