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

时间:2015-02-18 07:19:38

标签: c# design-patterns switch-statement polymorphism

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

我之前已经问过类似的问题,但我仍然不清楚替代实现相比交换机或一系列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)'在blah blah中有一些无效的参数......

您不能将原始数据类型子类化以添加功能,因为它们是密封的,因此扩展方法似乎是一种选择。但是,扩展方法不会从'object'转换为实际类型,即使调用value.GetType()返回基础类型:System.Int32,System.Int16等。使用'dynamic'关键字不帮忙,因为you can't use extension methods on a dynamic type

上面的可以通过将对象本身的实例作为参数传递给具有多态参数的方法来实现

多态性的工作实施:

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显式。使用字典依赖于内部条件来进行自己的查找。在一天结束时,有什么区别?

2 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:0)

使用 Strategy设计模式:

定义单独的转换器对象(具有通用接口) 它封装了不同的转换算法。 客户端在运行时将转换委托给适当的转换器对象。

这大大减少了实现依赖性。客户端代码是 与转换的实施方式无关。

我同意@David Osborne的回答。 即使您使用switch语句实现单个转换器对象, 此实现被封装并从客户端隐藏。