序列化和反序列化应该由要序列化的类负责吗?


16

我目前处于C#.NET应用程序的几个模型类的(重新)设计阶段。(模型与MVC中的M相同)。模型类已经具有大量精心设计的数据,行为和相互关系。我正在将模型从Python重写为C#。

在旧的Python模型中,我认为我看到了疣。每个模型都知道如何进行序列化,而序列化逻辑与任何类的其余行为无关。例如,假设:

  • Image类的.toJPG(String filePath) .fromJPG(String filePath)方法
  • ImageMetaData.toString().fromString(String serialized)方法的类。

您可以想象这些序列化方法与该类的其余部分之间是否没有凝聚力,但是只能保证该类知道足够的数据来对其自身进行序列化。

上课知道如何序列化和反序列化是一种常见的做法吗?还是我错过了通用模式?

Answers:


16

由于一些原因,我通常避免让类知道如何序列化自身。首先,如果要与其他格式反序列化,现在需要使用额外的逻辑来污染模型。如果通过接口访问模型,那么还会污染合同。

public class Image
{
    public void toJPG(String filePath) { ... }

    public Image fromJPG(String filePath) { ... }
}

但是,如果您想将其序列化到PNG和GIF,或从PNG和GIF序列化呢?现在班级变成

public class Image
{
    public void toJPG(String filePath) { ... }

    public Image fromJPG(String filePath) { ... }

    public void toPNG(String filePath) { ... }

    public Image fromPNG(String filePath) { ... }

    public void toGIF(String filePath) { ... }

    public Image fromGIF(String filePath) { ... }
}

相反,我通常喜欢使用类似于以下内容的模式:

public interface ImageSerializer
{
    void serialize(Image src, Stream outputStream);

    Image deserialize(Stream inputStream);
}

public class JPGImageSerializer : ImageSerializer
{
    public void serialize(Image src, Stream outputStream) { ... }

    public Image deserialize(Stream inputStream) { ... }
}

public class PNGImageSerializer : ImageSerializer
{
    public void serialize(Image src, Stream outputStream) { ... }

    public Image deserialize(Stream inputStream) { ... }
}

public class GIFImageSerializer : ImageSerializer
{
    public void serialize(Image src, Stream outputStream) { ... }

    public Image deserialize(Stream inputStream) { ... }
}

现在,在这一点上,这种设计的注意事项之一是,序列化程序需要知道要identity序列化的对象的。有人会说这是不好的设计,因为实现泄漏到了类之外。风险/报酬实际上取决于您,但是您可以稍微调整课程以做类似的事情

public class Image
{
    public void serializeTo(ImageSerializer serializer, Stream outputStream)
    {
        serializer.serialize(this.pixelData, outputStream);
    }

    public void deserializeFrom(ImageSerializer serializer, Stream inputStream)
    {
        this.pixelData = serializer.deserialize(inputStream);
    }
}

这更像是一个一般的例子,因为图像通常都带有元数据。诸如压缩级别,色彩空间等之类的东西可能会使过程复杂化。


2
我建议从抽象IOStream或二进制格式(文本是一种特定的二进制格式)进行序列化。这样,您不仅可以写入文件。希望通过网络发送数据将是重要的替代输出位置。
unholysampler 2015年

很好的一点。我当时在想,但是脑子放屁了。我将更新代码。
Zymus

我假设随着支持更多的序列化格式(即ImageSerializer编写了更多接口实现),ImageSerializer接口也将需要增长。EX:一种新格式支持可选压缩,以前的格式不->为ImageSerializer接口添加压缩可配置性。但是,其他格式则杂乱了不适用于它们的功能。我考虑的越多,继承在这里的适用范围就越小。
kdbanman 2015年

虽然我知道您来自哪里,但出于几个原因,我认为这不是问题。如果是现有的图像格式,则可能是串行化程序已经知道如何处理压缩级别,如果是新的格式,则无论如何都必须编写它。一种解决方案是重载方法,类似void serialize(Image image, Stream outputStream, SerializerSettings settings);这样。这只是将现有压缩和元数据逻辑连接到新方法的一种情况。
Zymus

3

序列化是一个两部分的问题:

  1. 有关如何实例化类结构的知识
  2. 关于如何持久/传递实例化类力学所需信息的知识。

结构应尽可能与力学分开。这增加了系统的模块化。如果将信息埋在班级中的#2上,则会破坏模块化,因为现在必须修改班级以跟上新的序列化方式(如果有)。

在图像序列化的上下文中,您应将序列化的信息与类本身分开,并应将其保留在可以确定序列化格式的算法中,因此,JPEG,PNG,BMP等将使用不同的类。序列化算法会随您而来,您只需对该算法进行编码即可,并且您的类合同保持不变。

在IPC的上下文中,您可以将类分开,然后有选择地声明序列化所需的信息(通过注释/属性)。然后,您的序列化算法可以决定是使用JSON,Google协议缓冲区还是XML进行序列化。它甚至可以决定是使用Jackson解析器还是使用您的自定义解析器-当您以模块化方式进行设计时,可以轻松获得许多选择!


1
您能举一个例子说明如何将这两件事分离吗?我不确定我是否了解区别。
kdbanman 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.