【问题标题】:Creating an extensible properties class (OOP)创建可扩展属性类 (OOP)
【发布时间】:2009-07-01 08:57:25
【问题描述】:

我有一个应用程序支持某些设备的多种类型和版本。它可以连接到这些设备并检索各种信息。

根据设备的类型,我(除其他外)有一个可以包含各种属性的类。有些属性是所有设备共有的,有些是特定设备独有的。

这个数据被序列化为xml。

什么是实现类的首选方法,该类将支持这些设备的未来版本中的未来属性,并向后兼容以前的应用程序版本?

我可以想到几种方法,但我觉得没有一个很好:

  • 使用名称-值对的集合:
    • 优点:良好的向后兼容性(我的应用程序的 xml 和以前版本)和可扩展性,
    • 缺点:没有类型安全,没有智能感知,需要实现自定义 xml 序列化(以处理不同的 value 对象)
  • 为每个新设备创建派生属性类:
    • 优点:类型安全
    • 缺点:必须使用XmlInclude 或自定义序列化来反序列化派生类,与以前的 xml 架构没有向后兼容性(尽管通过实现自定义序列化我可以跳过未知属性?) , 需要强制转换才能访问派生类中的属性。
  • 另一种方法?

顺便说一句,我正在使用 C#。

【问题讨论】:

  • 是否需要 XML?你的两个缺点都说必须处理 XML。感觉好像不想用 XML……
  • 是的,这就是客户知道流行语的要求之一。 :)
  • 我会选择“为每个新设备创建派生属性类”...

标签: c# oop inheritance xml-serialization


【解决方案1】:

类似PropertyBag 的东西怎么样?

【讨论】:

  • 嗯,这基本上是一个名称-值对的集合,我不太关心内部实现(列表、字典等)。但它有同样的缺点。
  • +1 用于使用久经考验且值得信赖、很好理解但不是很性感的解决方案 :)
【解决方案2】:

如果您不限于与外部架构的互操作性,那么您应该使用运行时序列化和 SoapFormatter。运行时序列化模式允许派生类指定它们的哪些属性需要序列化,以及在反序列化时如何处理它们。

XML 序列化程序需要 XmlInclude,因为实际上它需要定义要使用的架构。

【讨论】:

  • 谢谢,这可能会更好。但是我必须看看删除 XmlSerializer 特定的东西并用运行时序列化属性替换它们并进行所有必要的更改需要多少时间。这也很有趣,因为我可以轻松地序列化不可变类型(现在我必须一直实现 IXmlSerializable 并通过反射来实现)。
【解决方案3】:

我喜欢这类东西的名称/值集。

您的许多缺点都可以处理 - 考虑一个用作通用名称/值集的基类,它具有用于验证传入名称/值对的无操作方法。对于已知的名称集(即键),您可以创建实现验证方法的派生类。

例如,Printer 可能有一个已知键“PrintsColor”,该键只能为“true”或“false”。如果有人尝试加载 PrintsColor = "CMYK",您的 Printer 类会抛出异常。

根据您正在做的事情,您可以采用几种不同的方式来使验证更方便——基类中的实用方法(例如 checkForValidBoolean())或接受名称/类型信息的基类在它的构造函数中,以便在派生类中使用更清晰的代码,并且可能是大部分自动化的 XML 序列化。

对于智能感知——您的派生类可以具有根据键查找实现的基本访问器。 Intellisense 会显示这些访问器名称。

这种方法对我来说效果很好——经典的 OO 设计有点短视,尤其是对于带有插件组件的大型系统。 IMO,这里笨重的类型检查很麻烦,但灵活性使它值得。

【讨论】:

  • 谢谢。我仍然需要使用 XmlSerializer 进行一些自定义序列化,但至少我会得到一些运行时检查。而且它向后兼容所有以前的应用程序版本,这也是一件好事。我希望有一堆名称-值派生类来进行验证,否则我需要放置某种字典来在父属性类中通过键查找正确的委托(谓词)(如果我没有这里有什么遗漏吗?)。而且我会避免在父类中放置一堆方法(checkForThis、checkForThat)。
【解决方案4】:

我相信创建派生属性是最好的选择。

您可以使用 xml 架构设计新类。然后使用xsd.exe 生成类代码。

使用 .net 开发一个可以序列化和反序列化与 xml 之间的所有类型的通用类并不难。

public static String toXmlString<T>(T value)
{
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
    StringWriter stringWriter = new StringWriter();
    try { xmlSerializer.Serialize(stringWriter, value); }
    catch (Exception e)
    {
        throw(e);
    }
    finally { stringWriter.Dispose(); }
    String xml = stringWriter.ToString();
    stringWriter.Dispose();
    return xml;
}

public static T fromXmlFile<T>(string fileName, Encoding encoding)
{
    Stream stream;
    try { stream = File.OpenRead(fileName); }
    catch (Exception e)
    {

      e.Data.Add("File Name", fileName);
      e.Data.Add("Type", typeof(T).ToString());
      throw(e);
    }

    BufferedStream bufferedStream = new BufferedStream(stream);
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));

    TextReader textReader;
    if (encoding == null)
        textReader = new StreamReader(bufferedStream);
    else
        textReader = new StreamReader(bufferedStream, encoding);

    T value;
    try { value = (T)xmlSerializer.Deserialize(textReader); }
    catch (Exception e)
    {
        e.Data.Add("File Name", fileName);
        e.Data.Add("Type", typeof(T).ToString());
        throw(e);
    }
    finally
    {
        textReader.Dispose();
        bufferedStream.Dispose();
    }
    return value;
}

【讨论】:

  • 问题在于向后兼容性 - 您无法使用 XmlSerializer 保留它,因为它是为特定类型创建的。如果将属性设置为派生类的实例,则需要添加 [XmlInclude()] 以指定 XmlSerializer 可能遇到的所有派生类。
【解决方案5】:

从编程上讲,这听起来可能是Decorator Pattern 的工作。本质上,您有一个超类,它为所有这些类型的设备定义了一个通用接口。然后你有装饰器类,它们具有设备可能具有的其他属性。并且,在创建这些设备时,您可以动态添加这些装饰以定义设备的新属性。图形化:

您可以查看维基百科页面以获得更详细的说明。之后,只需进行一些序列化来告诉程序要加载哪些装饰器。

【讨论】:

    【解决方案6】:

    您在这里尝试完成的总体思路正是EAV 模式解决的问题。 EAV 是数据库开发中最常用的一种模式,但该概念同样适用于应用程序。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-10-14
      • 1970-01-01
      • 1970-01-01
      • 2018-04-15
      • 1970-01-01
      • 2021-01-18
      相关资源
      最近更新 更多