2009-08-25 205 views
10

我想序列化,并且我正面临着抽象类的问题。抽象类的序列化

我搜索了一个答案,我发现this blogitem。 我尝试过那样的工作。

好吧,非常好。但检查出评论的项目:

这种方法似乎是躲在 真正的问题,这是一个不准确的 实现面向对象的设计 模式,即工厂模式。

必须将基类更改为 参考任何新的工厂类是 自我挫败。

随着一点点后认为,代码 可以改变到任何派生 类型可以与 抽象类(通过的 接口奇迹)相关联,并且没有XmlInclude将是需要 。

我建议进一步研究 工厂模式,这似乎是 你在这里试图实现什么。

评论者在说什么?他有点模糊。有人能更详细地解释它吗(举一个例子)?或者他只是在胡说八道?

更新(阅读后第一个答案)

为什么在评注谈论

工厂模式

的代码是可以改变的到任何地方 派生类型可以与 抽象类(通过 奇迹的接口)

关联?

他想制作一个界面吗?

public interface IWorkaround 
{ 
    void Method(); 
} 

public class SomeBase : IWorkaround 
{ 
    public void Method() 
    { 
     // some logic here 
    } 
} 

public class SomeConcrete : SomeBase, IWorkaround 
{ 
    public new void Method() 
    { 
     base.Method(); 
    } 
} 

回答

37

他既是对与否的同时。

随着诸如BinaryFormatter这样的事情,这是一个非问题;序列化流包含了完整的元数据类型,所以如果你有:

[Serializable] abstract class SomeBase {} 
[Serializable] class SomeConcrete : SomeBase {} 
... 
SomeBase obj = new SomeConcrete(); 

和序列obj,那么它包含“我是SomeConcrete”的流。这使得生活变得简单,但冗长,特别是在重复时。它也很脆弱,因为它在反序列化时需要相同的实现;对于不同的客户端/服务器实现或长期存储都不利。

XmlSerializer(我猜这个博客正在讨论),没有元数据 - 但元素名称(或xsi:type属性)用于帮助识别使用哪个元数据。为了这个工作,序列化程序需要事先知道什么名字映射到哪些类型。

最简单的方法是用我们知道的子类修饰基类。然后序列化程序可以检查其中的每一个(以及任何其他特定于xml的属性),以便发现当它看到<someConcreteType>元素时,该元素映射到SomeConcrete实例(请注意,名称不需要匹配,因此它可以'只需按名称查找它)。

[XmlInclude(typeof(SomeConcrete))] 
public abstract class SomeBase {} 
public class SomeConcrete : SomeBase {} 
... 
SomeBase obj = new SomeConcrete(); 
XmlSerializer ser = new XmlSerializer(typeof(SomeBase)); 
ser.Serialize(Console.Out, obj); 

然而,如果他是一个纯粹的(或数据不可用),则存在另一种;您可以通过重载构造函数将所有这些数据分别指定为XmlSerializer。例如,您可以从配置(或者可能是一个IoC容器)中查找一组已知的子类型,并手动设置构造函数。这不是非常棘手,但它是非常棘手,它是不值得的,除非你实际上需要它

public abstract class SomeBase { } // no [XmlInclude] 
public class SomeConcrete : SomeBase { } 
... 
SomeBase obj = new SomeConcrete(); 
Type[] extras = {typeof(SomeConcrete)}; // from config 
XmlSerializer ser = new XmlSerializer(typeof(SomeBase), extras); 
ser.Serialize(Console.Out, obj); 

此外,与XmlSerializer如果你去自定义构造函数的路线,其缓存和重新使用XmlSerializer实例是很重要的;否则每次使用都会加载一个新的动态程序集 - 非常昂贵(它们不能被卸载)。如果使用简单的构造函数,它会缓存并重新使用该模型,因此只使用一个模型。

YAGNI指示我们应该选择最简单的选项;使用[XmlInclude]可以消除对复杂构造函数的需求,并且无需担心缓存序列化程序。另一种选择是在那里,并且完全支持。


重新您的后续问题:

通过“工厂模式”,他在谈论您的代码不知道SomeConcrete的情况,可能是由于的IoC/DI或类似的框架;所以你可能有:

SomeBase obj = MyFactory.Create(typeof(SomeBase), someArgsMaybe); 

哪个计算出相应的SomeBase具体实施,其实例化并把它回来。显然,如果我们的代码不知道具体类型(因为它们只在配置文件中指定),那么我们不能使用XmlInclude;但我们可以解析配置数据并使用ctor方法(如上所述)。实际上,大多数时候XmlSerializer与POCO/DTO实体一起使用,所以这是人为的关注。

和重新接口;同样的事情,但更灵活(一个接口不需要类型层次结构)。但XmlSerializer不支持此模式。坦率地说,艰难;这不是它的工作。它的工作是让你存储和传输数据。未执行。任何xml架构生成的实体都不会方法。数据是具体的,而不是抽象的。只要你认为“DTO”,界面辩论就不是问题。因无法在其边界上使用界面而烦恼的人不会分心,即他们正在尝试做的:

Client runtime entities <---transport---> Server runtime entities 

,而不是限制较少

Client runtime entities <---> Client DTO <--- transport---> 
      Server DTO <---> Server runtime entities 

现在,在许多(大多数?)情况下,DTO和实体可以是相同的;但是如果你想要做一些交通工具不喜欢的事情,那就引入一个DTO;不要与串行器对抗。同样的道理,当人们都在努力写自己的对象适用:

class Person { 
    public string AddressLine1 {get;set;} 
    public string AddressLine2 {get;set;} 
} 

为形式的XML:

<person> 
    <address line1="..." line2="..."/> 
</person> 

如果你想要这个,intoduce对应于运输DTO,和之间进行映射你的实体和DTO:

// (in a different namespace for the DTO stuff) 
[XmlType("person"), XmlRoot("person")] 
public class Person { 
    [XmlElement("address")] 
    public Address Address {get;set;} 
} 
public class Address { 
    [XmlAttribute("line1")] public string Line1 {get;set;} 
    [XmlAttribute("line2")] public string Line2 {get;set;} 
} 

这也适用于所有其他niggles,如:

  • 为什么我需要无参数构造函数?
  • 为什么我需要一个setter用于我的收藏属性?
  • 为什么我不能使用不可变类型?
  • 为什么我的类型必须公开?
  • 我该如何处理复杂的版本控制?
  • 我该如何处理具有不同数据布局的不同客户端?
  • 为什么我不能使用接口?
  • 等,等

你并不总是有这些问题;但如果你这样做 - 引入一个DTO(或几个),你的问题就会消失。回顾关于接口的问题; DTO类型可能不是基于接口的,但是您的运行时/业务类型可以。

+0

非常感谢你的信息。 – Natrium 2009-08-26 13:14:33