Progettare un'architettura solida per più tipi di esportazione?


10

Sto cercando modelli o indicazioni architettoniche per un'imminente caratteristica che sto progettando. Fondamentalmente, è una funzionalità di esportazione con più target di esportazione e sto cercando di trovare un modo per renderlo abbastanza generico dove collegare nuovi target di esportazione non richiede molte modifiche di base. Per obiettivi di esportazione, mi riferisco semplicemente a diversi tipi di output, siano essi PDF, presentazioni PowerPoint, documenti Word, RSS, ecc. Ho un set di dati di base, che è rappresentato in JSON e XML. Questi dati vengono utilizzati per costruire immagini (utilizzando qualsiasi numero o tipo di esportazione [ad esempio PNG, JPG, GIF, ecc.), Grafici, rappresentazioni testuali, tabelle e altro.

Sto cercando di trovare un modo per astrarre tutto il rendering e il layout in una sorta di motore di rendering o layout che gestisca l'aggiunta di ulteriori obiettivi di esportazione. Qualsiasi aiuto / suggerimento / risorse su come affrontare questo sarebbe molto apprezzato. Grazie in anticipo.

Per una rappresentazione pittorica di ciò che sto cercando di ottenere.

inserisci qui la descrizione dell'immagine


Puoi descrivere quello che hai provato finora? Quali sono i requisiti (responsabilità) del motore di layout? Ad esempio, dovrebbe gestire l'impaginazione e la selezione delle dimensioni della pagina?
rwong

I dati XML / JSON possono essere utilizzati per creare più tipi di output nella stessa esecuzione di output, ovvero i dati XML producono immagini, tabelle e grafici in un documento PDF? Oppure i dati XML / JSON possono essere utilizzati solo per creare una tabella o un grafico per un documento PDF?
Gibson,

Questo è tutto su xkcd.com/927 - perché stai cercando di reinventare la ruota? DocBook, Markdown / pandoc ecc. Esistono già ...
Deer Hunter,

Risposte:


2

Per me, la strada da percorrere sarebbe interfacce e una fabbrica. Uno che restituisce riferimenti a interfacce dietro le quali possono nascondersi varie classi. Le classi che eseguono il vero grugnito devono essere tutte registrate con Factory in modo che sappia quale classe creare un'istanza dato un set di parametri.

Nota: al posto delle interfacce è possibile utilizzare anche classi di base astratte, ma lo svantaggio è che per i singoli linguaggi di ereditarietà si limita a una singola classe di 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');

Il codice è nella sintassi di Delfi (Pascal) in quanto quella è la lingua con cui ho più familiarità.

Dopo che tutte le classi di implementazione sono state registrate in fabbrica, si dovrebbe essere in grado di richiedere un riferimento all'interfaccia a un'istanza di tale classe. Per esempio:

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

dovrebbe restituire un riferimento IReader a un'istanza di TXMLReader; un riferimento IWriter a un'istanza di TPowerPointWriter e un riferimento IRepresentation a un'istanza di THTMLTable.

Ora tutto ciò che il motore di rendering deve fare è legare tutto insieme:

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;

L'interfaccia di IReader dovrebbe fornire metodi per leggere i dati necessari agli implementatori di IRepresentation per costruire la rappresentazione dei dati. Allo stesso modo IRepresentation dovrebbe fornire i metodi di cui hanno bisogno gli implementatori di IWriter per esportare la rappresentazione dei dati nel formato di file di esportazione richiesto.

Supponendo che i dati nei tuoi file siano di natura tabellare, IReader e le sue interfacce di supporto potrebbero apparire come:

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;

Iterare su un tavolo sarebbe quindi una questione di

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;

Poiché le rappresentazioni possono essere immagini, grafici e di natura testuale, IRepresentation avrebbe probabilmente metodi simili a IReader per attraversare una tabella costruita e avrebbe metodi per ottenere immagini e grafici, ad esempio come un flusso di byte. Spetterebbe agli implementatori di IWriter codificare i valori della tabella e i byte immagine / grafico come richiesto dalla destinazione di esportazione.


1

Mentre sono d'accordo che sono necessarie ulteriori informazioni per pensare a un'architettura, il modo più semplice per creare diversi tipi di oggetti che si comportano allo stesso modo (ovvero tutti genereranno un output) è utilizzare il modello di fabbrica. Maggiori informazioni qui

Il modello di metodo di fabbrica è un modello di progettazione creazionale orientato agli oggetti per implementare il concetto di fabbriche e affronta il problema della creazione di oggetti (prodotti) senza specificare la classe esatta di oggetti che verranno creati. L'essenza di questo modello è "Definire un'interfaccia per la creazione di un oggetto, ma lasciare che le classi che implementano l'interfaccia decidano quale classe creare un'istanza. Il metodo Factory consente a una classe di rinviare l'istanza alle sottoclassi". Da Wikipedia


1
Penso che sia un po 'più coinvolto di così. Ad esempio, quali protocolli verranno utilizzati per comunicare i dati lungo le linee nel diagramma? Può esserci una rappresentazione comune dei dati nel motore di rendering / layout o quel motore è solo una fabbrica per metodi completamente personalizzati, uno per ogni riga nel diagramma?
Robert Harvey,

Non sono sicuro se ottengo il tuo punto qui. Perché se è necessario utilizzare un protocollo per comunicare le linee nel diagramma, sto pensando di fare affidamento su un insieme di servizi per generare le esportazioni (in questo caso ti piacerebbe vedere alcuni schemi di integrazione / soa). Anche se questo è vero, la soluzione è abbastanza flessibile e robusta da poter essere utilizzata in fabbrica. Forse la cosa che vuoi fare è creare un'interfaccia di conversione che abbia due metodi: uno che riceve i dati XML e un altro per i dati JSON. L'oggetto di ritorno per entrambi sarà l'oggetto convertito. In questo modo puoi assemblare quello che vuoi.
Orposuser,

ci sono in realtà due domande nell'intestazione: sul contenuto (gif, pdf, html) e sul trasporto (file locale, http-response-item). Per espandere la risposta di @Orposuser (+1): Vorrei creare un flusso utilizzando una factory che può essere facilmente non testata e resa facilmente per la risposta http.
k3b,

0

Potresti finire con qualcosa del genere.

Le due fabbriche si basano su:

1 - per convertire il tipo di input (Json / XML) in un'implementazione concreta di come convertire questi dati in un'immagine / grafico

2 - Una seconda fabbrica per decidere come rendere l'output in un documento Word / Documento PDF

Il polimorfismo utilizza un'interfaccia comune per tutti i dati renderizzati. Quindi un'immagine / tabella può essere spostata come un'interfaccia semplice.

1 - Factory per convertire i dati JSON / XML in un'implementazione 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);
}

La Factory di seguito consente di convertire i dati xml o Json Data nel tipo di calcestruzzo corretto.

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

Le implementazioni concrete svolgono tutto il pesante lavoro di conversione dei dati nel tipo pertinente. Inoltre convertono i dati nell'interfaccia IConvertedData, utilizzata per il 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;
    }
}

È possibile aggiungere queste implementazioni se necessario, man mano che il codice si espande.

L'interfaccia IConvertedData ti consente di passare un singolo tipo nella fase successiva: NOTA: qui potresti non restituire vuoti. Potrebbe essere un byte [] per immagini o un documento OpenXml per WordDocument. Regolare se necessario.

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

Polimorfismo:

Questo è usato per convertire i dati nel tipo di output rilevante. cioè il rendering in PDF per i dati di immagine, può essere diverso il rendering dei dati di immagine per 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 - Factory per decidere il formato di output:

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

Ogni implementazione concreta espone un metodo comune che maschera il modo in cui l'esportazione viene restituita alle implementazioni 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();
    }
}

Un client di esempio per tutto ciò sarebbe:

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

0

Abbiamo risolto un problema simile qui: https://ergebnisse.zensus2011.de/?locale=en Esistono principalmente "tabelle" e "grafici" da esportare in diversi formati: pdf, excel, web. La nostra idea era di specificare ogni oggetto da renderizzare come una propria classe Java con interfacce per creare e leggere quelle classi. Nel tuo caso ci sarebbero 2 implementazioni per ogni oggetto per la creazione (xml, json) e 4 implementazioni per il rendering (lettura).

Esempio: sono necessarie alcune classi per le tabelle: Tabella classi (gestisce la struttura della tabella, la convalida e il contenuto) Interface CreateTable (fornisce dati di tabella, celle, span, contenuto) Interface ReadTable (getter per tutti i dati)

Probabilmente non hai bisogno delle interfacce (o solo di una) ma penso che fornisca sempre un buon disaccoppiamento particolarmente utile nei test.


0

Penso che quello che stai cercando sia il modello di strategia . Sono disponibili varie classi per l'output dei dati nel formato desiderato e si sceglie semplicemente quella appropriata in fase di esecuzione. L'aggiunta di un nuovo formato dovrebbe essere semplice come l'aggiunta di un'altra classe che implementa l'interfaccia richiesta. L'ho fatto spesso in Java usando Spring per mantenere semplicemente una mappa di convertitori, codificata dal tipo di formato.

Come altri hanno già detto, ciò si ottiene generalmente facendo in modo che tutte le classi implementino la stessa interfaccia (o discendano dalla stessa classe base) e scelgano l'implementazione tramite una factory.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.