Serialização e desserialização devem ser de responsabilidade da classe que está sendo serializada?


16

Atualmente, estou na fase de (re) design de várias classes de modelo de um aplicativo C # .NET. (Modele como em M do MVC). As classes de modelo já possuem muitos dados, comportamentos e inter-relações bem projetados. Estou reescrevendo o modelo do Python para C #.

No antigo modelo Python, acho que vejo uma verruga. Cada modelo sabe como se serializar, e a lógica de serialização não tem nada a ver com o restante do comportamento de qualquer uma das classes. Por exemplo, imagine:

  • Imageclasse com um .toJPG(String filePath) .fromJPG(String filePath)método
  • ImageMetaDataclasse com a .toString()e .fromString(String serialized)método

Você pode imaginar como esses métodos de serialização não são coesos com o restante da classe, mas somente a classe pode garantir dados suficientes para se serializar.

É prática comum para uma classe saber como serializar e desserializar a si mesma? Ou estou perdendo um padrão comum?

Respostas:


16

Geralmente, evito que a classe saiba como se serializar por alguns motivos. Primeiro, se você deseja (des) serializar de / para um formato diferente, agora precisa poluir o modelo com essa lógica extra. Se o modelo for acessado por meio de uma interface, você também poluirá o contrato.

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

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

Mas e se você quiser serializá-lo para / de um PNG e GIF? Agora a turma se torna

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) { ... }
}

Em vez disso, normalmente gosto de usar um padrão semelhante ao seguinte:

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) { ... }
}

Agora, neste ponto, uma das advertências com esse design é que os serializadores precisam conhecer identityo objeto que está serializando. Alguns diriam que esse design é ruim, pois a implementação vaza para fora da classe. O risco / recompensa é realmente de sua responsabilidade, mas você pode ajustar levemente as aulas para fazer algo como

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);
    }
}

Este é mais um exemplo geral, pois as imagens geralmente têm metadados que os acompanham; coisas como nível de compactação, espaço de cores etc. que podem complicar o processo.


2
Eu recomendo que serialize de / para um IOStream abstrato ou o formato binário (o texto é um tipo específico de formato binário). Dessa forma, você não está restrito a gravar em um arquivo. Desejar enviar os dados pela rede seria um local de saída alternativo importante.
Unholysampler

Muito bom ponto. Eu estava pensando sobre isso, mas tive um peido cerebral. Vou atualizar o código.
Zymus 04/07/2015

Presumo que, à medida que mais formatos de serialização são suportados (ou seja, mais implementações da ImageSerializerinterface são gravadas), a ImageSerializerinterface também precisará crescer. EX: Um novo formato suporta compactação opcional, os anteriores não -> adicionaram configurabilidade de compactação à ImageSerializerinterface. Porém, os outros formatos estão repletos de recursos que não se aplicam a eles. Quanto mais eu penso sobre isso, menos acho que a herança se aplica aqui.
Kdbanman 6/07/15

Embora compreenda de onde você vem, acho que não é um problema, por alguns motivos. Se houver um formato de imagem existente, é provável que o serializador já saiba lidar com os níveis de compactação e, se for um novo, precisará escrevê-lo de qualquer maneira. Uma solução é sobrecarregar os métodos, algo como void serialize(Image image, Stream outputStream, SerializerSettings settings);Então é apenas um caso de conectar a lógica de compressão e metadados existente ao novo método.
Zymus 06/07

3

A serialização é um problema em duas partes:

  1. Conhecimento sobre como instanciar uma classe aka estrutura .
  2. Conhecimento sobre como persistir / transferir as informações necessárias para instanciar uma classe, também conhecida como mecânica .

Na medida do possível, a estrutura deve ser mantida separada da mecânica . Isso aumenta a modularidade do seu sistema. Se você enterrar as informações no 2 da sua classe, interromperá a modularidade, porque agora sua classe deve ser modificada para acompanhar as novas formas de serialização (se elas surgirem).

No contexto da serialização de imagens, você deve manter as informações sobre serialização separadas da própria classe e mantê-las nos algoritmos que podem determinar o formato da serialização - portanto, diferentes classes para JPEG, PNG, BMP etc. o algoritmo de serialização aparece, você simplesmente codifica esse algoritmo e seu contrato de classe permanece inalterado.

No contexto do IPC, você pode manter sua classe separada e, em seguida, declarar seletivamente as informações necessárias para serialização (por anotações / atributos). Em seguida, seu algoritmo de serialização pode decidir se deseja usar JSON, Google Protocol Buffers ou XML para serialização. Ele pode até decidir se deve usar o analisador Jackson ou o analisador personalizado - há muitas opções que você obteria facilmente ao projetar de forma modular!


1
Você pode me dar um exemplo de como essas duas coisas podem ser dissociadas? Não sei se entendi a distinção.
Kdbanman
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.