Projetando uma arquitetura robusta para vários tipos de exportação?

10

Estou procurando padrões ou orientações de arquitetura para um próximo recurso que estou criando. Basicamente, é um recurso de exportação com vários destinos de exportação, e estou procurando uma maneira de torná-lo genérico o suficiente para que a inserção de novos destinos de exportação não exija muitas alterações principais. Por metas de exportação, estou simplesmente me referindo a diferentes tipos de saída, sejam PDFs, apresentações do PowerPoint, documentos do Word, RSS etc. Eu tenho um conjunto de dados base, representado em JSON e XML. Esses dados são usados ​​para construir imagens (usando qualquer número ou tipos de exportação [por exemplo, PNG, JPG, GIF, etc.), gráficos, representações textuais, tabelas e muito mais.

Estou tentando encontrar uma maneira de abstrair toda a renderização e layout em algum tipo de mecanismo de renderização ou layout que lida com a adição de outros destinos de exportação. Qualquer ajuda / sugestões / recursos sobre como abordar isso seria muito apreciada. Desde já, obrigado.

Para uma representação pictórica do que estou tentando alcançar.

insira a descrição da imagem aqui

naivedeveloper
fonte
Você pode descrever o que você tentou até agora? Quais são os requisitos (responsabilidades) do mecanismo de layout? Por exemplo, espera-se lidar com a paginação e a seleção do tamanho da página?
rwong
Os dados XML / JSON podem ser usados ​​para criar vários tipos de saída na mesma execução de saída, ou seja, seus dados XML produzem imagens, tabelas e gráficos em um documento PDF? Ou os dados XML / JSON podem ser usados ​​apenas para criar uma tabela ou um gráfico para um documento PDF?
Gibson
É tudo sobre xkcd.com/927 - por que você está tentando reinventar a roda? DocBook, Markdown / pandoc etc. já existem ...
Deer Hunter

Respostas:

2

Para mim, o caminho a seguir seria interfaces e uma fábrica. Um que retorna referências a interfaces por trás das quais várias classes podem se esconder. Todas as classes que funcionam com o grunhido real precisam ser registradas na fábrica para saber qual classe instanciar, com base em um conjunto de parâmetros.

Nota: em vez de interfaces, você também pode usar classes base abstratas, mas a desvantagem é que, para linguagens de herança únicas, limita-o a uma única classe base.

TRepresentationType = (rtImage, rtTable, rtGraph, ...);

Factory.RegisterReader(TJSONReader, 'json');
Factory.RegisterReader(TXMLReader, 'xml');

Factory.RegisterWriter(TPDFWriter, 'pdf');
Factory.RegisterWriter(TPowerPointWriter, 'ppt');
Factory.RegisterWriter(TWordWriter, 'doc');
Factory.RegisterWriter(TWordWriter, 'docx');

Factory.RegisterRepresentation(TPNGImage, rtImage, 'png');
Factory.RegisterRepresentation(TGIFImage, rtImage, 'gif');
Factory.RegisterRepresentation(TJPGImage, rtImage, 'jpg');
Factory.RegisterRepresentation(TCsvTable, rtTable, 'csv');
Factory.RegisterRepresentation(THTMLTable, rtTable, 'html');
Factory.RegisterRepresentation(TBarChart, rtTGraph, 'bar');
Factory.RegisterRepresentation(TPieChart, rtTGraph, 'pie');

O código está na sintaxe Delphi (Pascal), pois é o idioma com o qual estou mais familiarizado.

Depois que todas as classes de implementação forem registradas na fábrica, você poderá solicitar uma referência de interface para uma instância dessa classe. Por exemplo:

Factory.GetReader('SomeFileName.xml');
Factory.GetWriter('SomeExportFileName.ppt');
Factory.GetRepresentation(rtTable, 'html');

deve retornar uma referência IReader para uma instância de TXMLReader; uma referência IWriter para uma instância do TPowerPointWriter e uma referência IRepresentation para uma instância do THTMLTable.

Agora, tudo o que o mecanismo de renderização precisa fazer é unir tudo:

procedure Render(
  aDataFile: string; 
  aExportFile: string;
  aRepresentationType: TRepresentationType;
  aFormat: string;
  );
var
  Reader: IReader;
  Writer: IWriter;
  Representation: IRepresentation;
begin
  Reader := Factory.GetReaderFor(aDataFile);
  Writer := Factory.GetWriterFor(aExportFile);
  Representation := Factory.GetRepresentationFor(aRepresentationType, aFormat);

  Representation.ConstructFrom(Reader);
  Writer.SaveToFile(Representation);
end;

A interface IReader deve fornecer métodos para ler os dados necessários aos implementadores de IRepresentation para construir a representação dos dados. Da mesma forma, a IRepresentation deve fornecer métodos que os implementadores de IWriter precisam para exportar a representação de dados para o formato de arquivo de exportação solicitado.

Supondo que os dados em seus arquivos sejam de natureza tabular, o IReader e suas interfaces de suporte podem ter a seguinte aparência:

IReader = interface(IInterface)
  function MoveNext: Boolean;
  function GetCurrent: IRow;
end;

IRow = interface(IInterface)
  function MoveNext: Boolean;
  function GetCurrent: ICol;
end;

ICol = interface(IInterface)
  function GetName: string;
  function GetValue: Variant;
end;

Iterar sobre uma mesa seria uma questão de

while Reader.MoveNext do
begin
  Row := Reader.GetCurrent;
  while Row.MoveNext do
  begin
    Col := Row.GetCurrent;
    // Do something with the column's name or value
  end;
end;

Como as representações podem ter imagens, gráficos e natureza textual, a IRepresentation provavelmente teria métodos semelhantes ao IReader para percorrer uma tabela construída e teria métodos para obter as imagens e gráficos, por exemplo, como um fluxo de bytes. Caberia aos implementadores do IWriter codificar os valores da tabela e os bytes de imagem / gráfico, conforme exigido pelo destino de exportação.

Marjan Venema
fonte
1

Embora eu concorde que são necessárias mais informações para pensar em uma arquitetura, a maneira mais simples de criar diferentes tipos de objetos que se comportam da mesma forma (ou seja, todos eles gerarão uma saída) usando o padrão de fábrica. Mais informações aqui

O padrão de método de fábrica é um padrão de design criacional orientado a objetos para implementar o conceito de fábricas e lida com o problema de criar objetos (produtos) sem especificar a classe exata de objeto que será criado. A essência desse padrão é "Definir uma interface para criar um objeto, mas deixe as classes que implementam a interface decidirem qual classe instanciar. O método Factory permite que uma classe adie a instanciação para subclasses". Da wikipedia

Orposuser
fonte
11
Eu acho que é um pouco mais envolvido do que isso. Por exemplo, quais protocolos serão usados ​​para comunicar dados ao longo das linhas do diagrama? Pode haver uma representação de dados comum no mecanismo de renderização / layout, ou esse mecanismo é apenas uma fábrica para métodos completamente personalizados, um para cada linha do diagrama?
9788 Robert Downey
Não tenho certeza Se eu entendi o seu ponto aqui. Porque, se precisar usar um protocolo para comunicar as linhas no diagrama, estou pensando em contar com um conjunto de serviços para gerar as exportações (nesse caso, você gostaria de ver alguns padrões de integração / integração). Mesmo se isso for verdade, a solução é flexível e robusta o suficiente para usar a fábrica. Talvez o que você queira fazer seja criar uma interface de conversor com dois métodos: um que receba os dados XML e outro para os dados JSON. O objeto de retorno para ambos será o objeto convertido. Dessa forma, você pode montar o que quiser.
Orposuser
na verdade, existem duas perguntas no cabeçalho: sobre conteúdo (gif, pdf, html) e sobre transporte (arquivo local, http-response-item). Para expandir a resposta do @Orposuser (+1): eu criaria um fluxo usando uma fábrica que pode ser facilmente não testada e renderizada facilmente para a resposta http.
K3b
0

Você pode acabar com algo assim.

As duas fábricas são baseadas em torno de:

1 - para converter o tipo de entrada (Json / XML) em uma implementação concreta de como converter esses dados em uma imagem / gráfico

2 - Uma segunda fábrica para decidir como renderizar a saída em uma palavra Documento / Documento PDF

O polimorfismo usa uma interface comum para todos os dados renderizados. Assim, uma imagem / tabela pode ser movida como uma interface fácil.

1 - Fábrica para converter dados JSON / XML em uma implementação concreta:

public enum DataTypeToConvertTo
{
    Image,
    Table,
    Graph,
    OtherData
}

public interface IDataConverter
{
    IConvertedData ConvertJsonDataToOutput(Json jsonData);
    IConvertedData ConvertXmlDataToOutput(XDocument xmlData);
}

public abstract class DataConverter : IDataConverter
{
    public DataConverter()
    {

    }

    public abstract IConvertedData ConvertDataToOutput(string data);
}

A fábrica abaixo permite converter os dados xml ou Json Data no tipo de concreto correto.

public class DataConverterFactory
{
    public static IDataConverter GetDataConverter(DataTypeToConvertTo dataType)
    {
        switch(dataType)
        {
            case DataTypeToConvertTo.Image:
                return new ImageDataConverter();
            case DataTypeToConvertTo.Table:
                return new TableDataConverter();
            case DataTypeToConvertTo.OtherData:
                return new OtherDataConverter();
            default:
                throw new Exception("Unknown DataTypeToConvertTo");
        }
    }
}

As implementações concretas fazem todo o trabalho pesado de converter os dados para o tipo relevante. Eles também convertem os dados na interface IConvertedData, que é usada para o polimorfismo.

public sealed class ImageDataConverter : DataConverter
{
    public ImageDataConverter()
        : base()
    {

    }

    public override IConvertedData ConvertJsonDataToOutput(Json jsonData)
    {
        var convertedData = new ImageConvertedData();
        //Logic to convert to necessary datatype

        return convertedData;
    }

    public override IConvertedData ConvertXmlDataToOutput(XDocument xmlData)
    {
        var convertedData = new ImageConvertedData();
        //Logic to convert to necessary datatype

        return convertedData;
    }
}

public sealed class TableDataConverter : DataConverter
{
    public TableDataConverter()
        : base()
    {

    }

    public override IConvertedData ConvertJsonDataToOutput(Json jsonData)
    {
        var convertedData = new TableConvertedData();
        //Logic to convert to necessary datatype

        return convertedData;
    }

    public override IConvertedData ConvertXmlDataToOutput(XDocument xmlData)
    {
        var convertedData = new ImageConvertedData();
        //Logic to convert to necessary datatype

        return convertedData;
    }
}

public sealed class OtherDataConverter : DataConverter
{
    public OtherDataConverter()
        : base()
    {

    }

    public override IConvertedData ConvertJsonDataToOutput(Json jsonData)
    {
        var convertedData = new OtherConvertedData();
        //Logic to convert to necessary datatype

        return convertedData;
    }

    public override IConvertedData ConvertXmlDataToOutput(XDocument xmlData)
    {
        var convertedData = new OtherConvertedData();
        //Logic to convert to necessary datatype

        return convertedData;
    }
}

Você pode adicionar essas implementações conforme necessário, à medida que seu código se expande.

A interface IConvertedData permite passar um único tipo para a próxima fase: NOTA: Você pode não estar retornando espaços aqui. Poderia por um byte [] para imagens ou um documento OpenXml para o WordDocument. Ajuste conforme necessário.

public interface IConvertedData
{
    void RenderToPdf();
    void RenderToDocument();
    void RenderToOhter();
    void RenderToPowerPoint();
}

Polimorfismo:

Isso é usado para converter os dados no tipo de saída relevante. ou seja, a renderização em PDF para dados de imagem pode ser diferente para renderizar dados de imagem para o PowerPoint.

public sealed class ImageConvertedData : IConvertedData
{
    public void RenderToPdf()
    {
        //Logic to render Images
    }

    public void RenderToDocument()
    {
        //Logic to render Images
    }
}
public sealed class TableConvertedData : IConvertedData
{
    public void RenderToPdf()
    {
        //Logic to render Document
    }

    public void RenderToDocument()
    {
        //Logic to render Document
    }
}

public sealed class OtherConvertedData : IConvertedData
{
    public void RenderToPdf()
    {
        //Logic to render PDF
    }

    public void RenderToDocument()
    {
        //Logic to render PDF
    }
}

2 - Fábrica para decidir o formato de saída:

public enum ExportOutputType
{
    PDF,
    PowerPoint,
    Word,
    Other
}

public interface IOutputExporter
{
    void ExportData(IConvertedData data);
}


public class OutputExporterFactory
{
    public static IOutputExporter GetExportOutputType(ExportOutputType exportOutputType)
    {
        switch(exportOutputType)
        {
            case ExportOutputType.PDF:
                return new OutputExporterPdf();
            case ExportOutputType.PowerPoint:
                return new OutputExporterPowerPoint();
            case ExportOutputType.Other:
                return new OutputExporterOther();
            default:
                throw new Exception ("Unknown ExportOutputType");
        }
    }
}

Cada implementação concreta expõe um método comum que mascara como a exportação está sendo lançada de volta para as implementações do IConvertedData

public abstract class OutputExporter : IOutputExporter
{
    //Other base methods...
    public virtual void ExportData(IConvertedData data)
    {

    }
}

public sealed class OutputExporterPdf : OutputExporter
{
    public OutputExporterPdf()
        : base()
    {

    }

    public override void ExportData(IConvertedData data)
    {
        //Functionality to Export to Pdf
        data.RenderToPdf();
    }
}

public sealed class OutputExporterPowerPoint : OutputExporter
{
    public OutputExporterPowerPoint()
        : base()
    {

    }

    public override void ExportData(IConvertedData data)
    {
        //Functionality to Export to PowerPoint
        data.RenderToPowerPoint();
    }
}

public sealed class OutputExporterOther : OutputExporter
{
    public OutputExporterOther()
        : base()
    {

    }

    public override void ExportData(IConvertedData data)
    {
        //Functionality to Export to PowerPoint
        data.RenderToOhter();
    }
}

Um cliente de amostra para tudo isso seria:

public class Client
{
    public Client()
    {

    }
    public void StartExportProcess(XDocument data)
    {
        IDataConverter converter = DataConverterFactory.GetDataConverter(DataTypeToConvertTo.Graph);

        IConvertedData convertedData = converter.ConvertXmlDataToOutput(data);


        IOutputExporter exportOutputer = OutputExporterFactory.GetExportOutputType(ExportOutputType.PDF);
        exportOutputer.ExportData(convertedData);
    }
}
Gibson
fonte
0

Resolvemos um problema semelhante aqui: https://ergebnisse.zensus2011.de/?locale=en Lá temos principalmente "tabelas" e "gráficos" a serem exportados em diferentes formatos: pdf, excel, web. Nossa ideia era especificar cada objeto a ser renderizado como uma própria classe Java com interfaces para criar e ler essas classes. No seu caso, haveria 2 implementações para cada objeto para criação (xml, json) e 4 implementações para renderização (leitura).

Exemplo: Você precisará de algumas classes para Tabelas: Tabela de Classe (lida com estrutura, validação e conteúdo da tabela) Interface CreateTable (fornece dados, células, extensões, conteúdo da tabela) Interface ReadTable (getters para todos os dados)

Provavelmente você não precisa das interfaces (ou apenas uma), mas acho que sempre fornece uma boa dissociação, especialmente útil nos testes.

dermoritz
fonte
0

Eu acho que o que você está procurando é o padrão de estratégia . Você tem uma variedade de classes para gerar os dados no formato desejado e simplesmente escolhe a apropriada no tempo de execução. Adicionar um novo formato deve ser tão simples quanto adicionar outra classe que implemente a interface necessária. Fiz isso frequentemente em Java usando o Spring para simplesmente manter um mapa de conversores, codificado pelo tipo de formato.

Como outros já mencionaram, isso geralmente é feito com todas as classes implementando a mesma interface (ou descendo da mesma classe base) e escolhendo a implementação através de uma fábrica.

TMN
fonte