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

10

Sto cercando modelli o indicazioni architettoniche per una prossima funzione che sto progettando. Fondamentalmente, è una funzione di esportazione con più destinazioni di esportazione e sto cercando di trovare un modo per renderlo abbastanza generico in cui inserire nuovi target di esportazione non richiede molte modifiche fondamentali. Per obiettivi di esportazione, mi riferisco semplicemente a diversi tipi di output, che si tratti di PDF, presentazioni di 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 es. 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 gestisce l'aggiunta di ulteriori target di esportazione. Qualsiasi aiuto / suggerimento / risorsa su come affrontarlo sarebbe molto apprezzato. Grazie in anticipo.

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

    
posta naivedeveloper 05.06.2013 - 23:06
fonte

5 risposte

2

Per me, la strada da percorrere sarebbe un'interfaccia e una fabbrica. Uno che restituisce riferimenti alle interfacce dietro cui possono nascondersi varie classi. Le classi che eseguono il lavoro effettivo devono essere registrate con la Factory in modo che sappia quale classe istanziare in base a una serie di parametri.

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

Il codice è in sintassi Delphi (Pascal) in quanto è la lingua con cui sono più familiare.

Dopo che tutte le classi di implementazione sono state registrate in fabbrica, dovresti essere in grado di richiedere un riferimento all'interfaccia per un'istanza di tale classe. Ad 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 a IRepresentation a un'istanza di THTMLTable.

Ora tutto ciò che deve fare il motore di rendering è 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 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 metodi che gli implementatori di IWriter devono 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 avere il seguente aspetto:

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;

L'iterazione 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 probabilmente avrebbe metodi simili per IReader per attraversare una tabella costruita e avrebbe metodi per ottenere immagini e grafici, ad esempio come un flusso di byte. Sarebbe compito degli implementatori di IWriter codificare i valori della tabella e i byte immagine / grafico come richiesto dalla destinazione di esportazione.

    
risposta data 07.06.2013 - 00:51
fonte
1

Anche se sono d'accordo sul fatto che sono necessarie più informazioni per pensare a un'architettura, il modo più semplice per creare diversi tipi di oggetti che si comportano allo stesso modo (cioè tutti genereranno un output) sta usando il modello factory. Maggiori informazioni qui

The factory method pattern is an object-oriented creational design pattern to implement the concept of factories and deals with the problem of creating objects (products) without specifying the exact class of object that will be created. The essence of this pattern is to "Define an interface for creating an object, but let the classes that implement the interface decide which class to instantiate. The Factory method lets a class defer instantiation to subclasses." From wikipedia

    
risposta data 06.06.2013 - 05:22
fonte
0

Potresti finire con qualcosa di simile.

Le due fabbriche sono basate su:

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

2 - Un secondo factory per decidere come eseguire il rendering dell'output in una parola Document / PDF Document

Il polimorfismo utilizza un'interfaccia comune per tutti i dati sottoposti a rendering. 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 sottostante 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 fanno tutto il lavoro pesante di convertire i dati nel tipo pertinente. Inoltre convertono i dati nell'interfaccia IConvertedData, che viene 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;
    }
}

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

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

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

Il polimorfismo:

Questo è usato per convertire i dati nel relativo tipo di output. ovvero il rendering in PDF per i dati delle immagini, potrebbe essere diverso per 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 come viene esportato l'esportazione nelle implementazioni di 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 questo 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);
    }
}
    
risposta data 07.06.2013 - 11:03
fonte
0

Abbiamo risolto un problema simile qui: link Ci sono principalmente "tabelle" e "grafici" da esportare in diversi formati: pdf, excel, web. La nostra idea era di specificare ogni oggetto da rendere come una propria classe Java con interfacce per creare e leggere tali classi. Nel tuo caso ci sarebbero 2 implementazioni per ogni oggetto per la creazione (xml, json) e 4 implementazioni per il rendering (lettura).

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

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

    
risposta data 15.07.2013 - 14:58
fonte
0

Penso che quello che stai cercando sia il modello Strategia . Avete una varietà di classi per produrre i dati nel formato desiderato, e semplicemente scegliete quello appropriato in fase di esecuzione. Aggiungere un nuovo formato dovrebbe essere semplice come aggiungere un'altra classe che implementa l'interfaccia richiesta. L'ho fatto spesso in Java usando Spring per mantenere semplicemente una mappa di convertitori, in base al tipo di formato.

Come altri hanno già accennato, questo in genere si ottiene facendo in modo che tutte le classi implementino la stessa interfaccia (o discendano dalla stessa classe base) e scegliendo l'implementazione tramite una fabbrica.

    
risposta data 15.07.2013 - 16:36
fonte

Leggi altre domande sui tag