【问题标题】:C# inheritance design-pattern questionC#继承设计模式问题
【发布时间】:2010-12-06 20:41:43
【问题描述】:

我正在存储不同格式和长度的数据。我有一个类层次结构来表示这一点:

abstract class BaseDataFormat{ 
    abstract void InitalizeFromBytes(byte [] );
}

class DataFormat1 : BaseDataFormat{...}  // data stored in 3 bytes
class DataFormat2 : BaseDataFormat{...} /// data stored in 4 bytes

当我读取数据时(比如从字节 []),我需要知道长度(字节数)以适当地读取和创建相应的类型。 DataFormat1 和 DataFormat2 的长度不同,那么如何在运行时获取这些信息呢?即。

Fcn<DATAFORMATTYPE>(...)
 where DATAFORMATTYPE: BaseDataFormat, new();
{

DATAFORMATTYPE tmp = new DATAFORMATTYPE();
tmp.InitalizeFromBytes(ReadFromByteBuffer( ... someLength));

}

如何根据 DATAFORMATTYPE 对要读取的正确字节数进行编码?每个的长度感觉应该是数据格式类型的静态属性,但是静态属性不能被派生类覆盖,所以我不知道该怎么做。

长度可以编码为实例属性,但这似乎应该是在类级别(即静态)编码的知识。有没有设计模式可以解决这个问题?

谢谢

【问题讨论】:

    标签: c# design-patterns inheritance static


    【解决方案1】:

    也许在BaseDataFormat 中有一个名为DataLength 的属性。要强制所有继承者设置一个值,请在 BaseDataFormat 的构造函数中获取长度,然后将该属性设置为数据长度。

    例子:

    abstract class BaseDataFormat
    { 
        BaseDataFormat(int dataLength)
        {
            DataLength = dataLength;
        }
    
        public int DataLength { get; private set; }
        abstract void InitalizeFromBytes(byte [] );
    }
    
    class DataFormat1 : BaseDataFormat
    {
        public DataFormat1() : base(3)
        {
            // ...
        }
    }
    

    当然,它不是在静态级别,但它是强制所有继承者的东西。

    另一种方法是如 VirtualBlackFox 建议的那样,用属性装饰类。唯一的问题是 AFAIK 你不能将属性强制到一个类上,就像抽象成员一样。

    【讨论】:

      【解决方案2】:

      你怎么知道读取数据时要实例化哪个子类?从串行数据创建对象的更通用的解决方案是使用工厂模式。

      这方面的一个例子是在:http://en.wikipedia.org/wiki/Factory_method_pattern#Encapsulation

      【讨论】:

        【解决方案3】:

        我将为每个 DataFormat 构建 Factory 对象。或者也许让DataFormat 成为工厂,并调用实际的数据对象Datum 或类似的东西。然后你可以实例化工厂,并将字节缓冲区传递给它,它可以读取必要的字节并构造实际的数据对象。

        【讨论】:

        • 我喜欢这种方法。当您不想被实例化的细节束缚时的好模式;并且可以将字节缓冲区传递给它并让它弄清楚。
        • 无论特定问题(在哪里存储字节数)的内部解决方案是什么,工厂模式都非常明显。
        【解决方案4】:

        如果您真的想要强制它是类的属性而不是实例的属性,那么最好在类上使用属性。

        但是您需要从中构建缓存,因为与直接访问相比,反射非常慢。

        [AttributeUsage(AttributeTargets.Class)]
        class ByteCountAttribute : Attribute
        {
            public int Value { get; private set; }
            public ByteCountAttribute(int count)
            {
                Value = count;
            }
        }
        
        [ByteCount(5)]
        class DataFormat1 : BaseDataFormat{}  // data stored in 3 bytes
        [ByteCount(6)]
        class DataFormat2 : BaseDataFormat{} /// data stored in 4 bytes
        
        static Dictionary<Type, int> s_typeCache = new Dictionary<Type,int>();
        public static int GetByteCount<T>() where T : BaseDataFormat
        {
            int result;
            var type = typeof(T);
            if (!s_typeCache.TryGetValue(type, out result))
            {
                var atts = type.GetCustomAttributes(typeof(ByteCountAttribute), false);
                result = ((ByteCountAttribute)atts[0]).Value;
                s_typeCache.Add(type, result);
            }
            return result;
        }
        

        (代码没有错误管理,但可以工作)

        正如 darkassassin93 所说,这种方法的一个问题是你不能强制它的存在,所以你必须证明这个属性是必要的,而不是编译器为你完成工作的抽象属性。

        【讨论】:

          【解决方案5】:

          您所追求的称为通用值适配器。您可以定义一堆只公开某些常量的类,例如:

          public static class Dimensions
          {
            public readonly struct Two : IInteger
            {
              public int Value => 2;
            }
          
            public readonly struct Three : IInteger
            {
              public int Value => 3;
            }
          }
          

          然后,您可以定义一个将这些IInteger 类型之一作为类型参数的类,并在运行时简单地提取它们的值:

          public class Vector<T, D>
            where D : IInteger, new()
          {
            protected T[] data;
          
            public Vector()
            {
              data = new T[new D().Value];
            }
          }
          

          你可以用这个

          var x = new Vector<float, Dimensions.Two>();
          

          【讨论】: