他是对的,也是错的。
像这样的事情
BinaryFormatter
,这不是问题;序列化流包含完整类型的元数据,因此如果您有:
[Serializable] abstract class SomeBase {}
[Serializable] class SomeConcrete : SomeBase {}
...
SomeBase obj = new SomeConcrete();
序列化
obj
包括“我是一个
SomeConcrete
“在小溪里。这使生活变得简单,但很冗长,尤其是在重复的时候。它也很脆弱,因为它在反序列化时需要相同的实现;对于不同的客户机/服务器实现或长期存储都不好。
用
XmlSerializer
(我想是博客所说的),没有元数据——只有元素名(或者
xsi:type
属性)用于帮助识别使用的对象。为了使其工作,序列化程序
需要提前知道
什么名字对应什么类型。
最简单的方法是用我们知道的子类修饰基类。然后,序列化程序可以检查其中的每一个(以及任何其他特定于XML的属性),以确定当它看到
<someConcreteType>
元素,映射到
局部混凝土
实例(请注意,名称不需要匹配,因此它不能只按名称查找)。
[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);
但是,如果他是一个纯粹主义者(或者数据不可用),那么有一个替代方法;您可以指定所有这些数据
分别地
通过重载的构造函数
XML序列化程序
. 例如,您可以从配置中查找已知子类型集(或IOC容器),然后手动设置构造函数。这不是很棘手,但很棘手,除非你
实际上需要它
.
public abstract class SomeBase { }
public class SomeConcrete : SomeBase { }
...
SomeBase obj = new SomeConcrete();
Type[] extras = {typeof(SomeConcrete)};
XmlSerializer ser = new XmlSerializer(typeof(SomeBase), extras);
ser.Serialize(Console.Out, obj);
此外,还有
XML序列化程序
如果使用自定义ctor路由,则缓存并重新使用
XML序列化程序
实例;否则,每次使用都会加载一个新的动态程序集-非常昂贵(无法卸载它们)。如果使用简单的构造函数,它将缓存并重新使用模型,因此只使用单个模型。
Yagni规定我们应该选择最简单的选项;使用
[XmlInclude]
消除了对复杂构造函数的需要,并消除了对缓存序列化程序的担心。另一种选择是存在的,并且得到了完全支持。
重新回答您的后续问题:
根据“工厂模式”,他说的是你的代码
不知道
局部混凝土
可能是由于IOC/DI或类似的框架;所以您可能有:
SomeBase obj = MyFactory.Create(typeof(SomeBase), someArgsMaybe);
哪一个合适
SomeBase
具体实现,实例化并返回。显然,如果我们的代码不知道具体类型(因为它们只在配置文件中指定),那么我们就不能使用
XmlInclude
但是我们
可以
分析配置数据并使用ctor方法(如上所述)。实际上,大多数时候
XML序列化程序
与POCO/DTO实体一起使用,因此这是一个人为问题。
重新接口;同样的事情,但更灵活(接口不需要类型层次结构)。但是
XML序列化程序
不支持此模型。坦率地说,这不是它的工作。它的工作是允许您存储和传输数据。不是实现。任何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>
如果需要,请生成与传输相对应的DTO,并在实体和DTO之间映射:
[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;}
}
这也适用于所有其他的麻烦,比如:
-
为什么我需要一个无参数的构造函数?
-
为什么我需要集合属性的setter?
-
为什么不能使用不可变类型?
-
为什么我的类型必须公开?
-
如何处理复杂的版本控制?
-
如何处理具有不同数据布局的不同客户机?
-
为什么我不能使用接口?
-
等
你并不总是有这些问题,但是如果你有——介绍一个DTO(或几个),你的问题就消失了。回到关于接口的问题;DTO类型可能不是基于接口的,但您的运行时/业务类型可以是。