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.
fonte
Respostas:
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.
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:
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:
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:
Iterar sobre uma mesa seria uma questão de
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.
fonte
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
fonte
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:
A fábrica abaixo permite converter os dados xml ou Json Data no tipo de concreto correto.
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.
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.
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.
2 - Fábrica para decidir o formato de saída:
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
Um cliente de amostra para tudo isso seria:
fonte
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.
fonte
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.
fonte