13

我正在尝试序列化,但我正面临抽象类的问题。

我用谷歌搜索了答案,我发现了这个 blogitem。我试过那个和那个工作。

好的,很不错。但请查看对该项目的评论:

这种方法似乎隐藏了真正的问题,那就是OO设计模式的不准确实现,即工厂模式。

不得不改变基类来引用任何新的工厂类是弄巧成拙的。

稍加考虑,代码可以更改为任何派生类型都可以与抽象类关联(通过接口的奇迹),并且不需要 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();
    }
}
4

2 回答 2

46

他既是对的又是错的。

对于类似的东西BinaryFormatter,这不是问题;序列化的流包含完整的类型元数据,所以如果你有:

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

和 serialize obj,然后它SomeConcrete在流中包含“I'm a”。这使生活变得简单,但很冗长,尤其是在重复时。它也很脆弱,因为它在反序列化时需要相同的实现;对于不同的客户端/服务器实现或长期存储都不好。

使用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如果您使用自定义 ctor 路由,缓存和重用XmlSerializer实例很重要;否则每次使用都会加载一个新的动态程序集 - 非常昂贵(它们无法卸载)。如果您使用简单的构造函数,它会缓存并重用模型,因此只使用一个模型。

YAGNI 规定我们应该选择最简单的选项;using[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>

如果你想要这个,引入一个对应于传输的 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;}
}

这也适用于所有其他小问题,例如:

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

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

于 2009-08-26T10:53:37.120 回答
-1
**Example of Enum Abstract Serializer

Simple example of an abstract enum ...(Java)(Spring-Boot) 
----------------------------------------------------------------------------------**


@JsonSerialize(using = CatAbstractSerializer.class)
public enum CatTest implements Tes{

    TYPE1(1, "Type 1"), TYPE2(2, "Type 2");

    private int id;
    private String nome;

    private CatTest(int id, String nome) {
        // TODO Auto-generated constructor stub

        this.id = id;
        this.nome = nome;


    }
    @JsonValue
    public int getId() {
        return id;
    }
    @JsonSetter
    public void setId(int id) {
        this.id = id;
    }
    @JsonValue
    public String getNome() {
        return nome;
    }
    @JsonSetter
    public void setNome(String nome) {
        this.nome = nome;
    }
    @Override
     @JsonValue
     public String toString() {
            return nome;
     }
     @JsonCreator
        public static CatTest fromValueString(String nome) {
            if(nome == null) {
                throw new IllegalArgumentException();
            }
            for(CatTest nomeSalvo : values()) {
                if(nome.equals(nomeSalvo.getNome())) {
                    return nomeSalvo;
                }
            }
            throw new IllegalArgumentException();
        }


}


public interface Tes {

    @JsonValue
    int getId();

    @JsonValue
    String getNome();

    @JsonSetter
    void setId(int id);

    @JsonSetter
    void setNome(String nome);


}

public class CatAbstractSerializer<T extends Tes> extends JsonSerializer<T> {

    @Override
    public void serialize(T value, JsonGenerator gen, SerializerProvider serializers)
            throws IOException, JsonProcessingException {
        // TODO Auto-generated method stub

        gen.writeStartObject();
        gen.writeFieldName("id");
        gen.writeNumber(value.getId());
        gen.writeFieldName("name");
        gen.writeString(value.getNome());
        gen.writeEndObject();

    }

}
于 2018-11-04T04:22:44.683 回答