web-dev-qa-db-fra.com

Concevoir une architecture robuste pour plusieurs types d'exportation?

Je suis à la recherche de modèles ou d'orientations architecturales pour une fonctionnalité à venir que je concevons. Fondamentalement, il s'agit d'une fonctionnalité d'exportation avec plusieurs cibles d'exportation et je cherche à trouver un moyen de le rendre assez générique où la branche dans de nouveaux objectifs d'exportation ne nécessite pas de changements de base. Par objectifs d'exportation, je fais simplement référence à différents types de production, que ce soit des PDF, des présentations PowerPoint, des documents Word, RSS, etc. J'ai un ensemble de données de base, représenté dans JSON et XML. Ces données sont utilisées pour construire des images (en utilisant des types de nombres ou d'exportation [par exemple, PNG, JPG, GIF, etc.), des graphiques, des représentations textuelles, des tables et plus encore.

J'essaie de trouver un moyen de résumé tout le rendu et la mise en page dans une sorte de rendu ou d'un moteur de présentation qui gère l'ajout d'objectifs d'exportation supplémentaires. Toute aide/suggestions/ressources sur la manière d'approcher cela serait grandement appréciée. Merci d'avance.

Pour une représentation picturale de ce que j'essaie d'atteindre.

enter image description here

10
naivedeveloper

Pour moi, la voie à suivre serait des interfaces et une usine. Celui qui renvoie des références aux interfaces derrière lesquelles diverses classes peuvent cacher. Les classes qui font le travail de grognement réel doivent toutes être enregistrées avec l'usine afin qu'elle sait quelle classe à instancier donnée à un ensemble de paramètres.

Remarque: Au lieu d'interfaces, vous pouvez également utiliser des classes de base abstraites, mais l'inconvénient est que, pour une seule langue d'héritage, cela vous limite à une seule classe de 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');

Le code est dans la syntaxe Delphi (Pascal) comme c'est la langue avec laquelle je suis le plus familier.

Une fois que toutes les classes de mise en œuvre sont enregistrées avec l'usine, vous devriez être en mesure de demander une référence d'interface à une instance d'une telle classe. Par exemple:

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

devrait renvoyer une référence irrégulière à une instance de TXMLReader; Une référence IWRITER à une instance de TowllonWrtretre et une référence d'IrePresentation à une instance de THTMLTABLE.

Maintenant, tout le moteur de rendu doit faire, lie tout ensemble:

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'interface IReader doit fournir des méthodes permettant de lire les données nécessaires à la mise en œuvre de l'IrePrésentation afin de construire la représentation des données. De même, une représentation devrait fournir des méthodes que IWRITER mettant en œuvre la représentation des données au format de fichier d'exportation demandé.

En supposant que les données de vos fichiers soient tabulaires dans la nature, IReader et ses interfaces de support pourraient ressembler à:

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;

Itération sur une table serait alors une question 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;

Au fur et à mesure que les représentations peuvent être des images, des graphiques et de la nature textuelle, IREPRESENTATION aurait probablement des méthodes similaires à IRARELER pour traverser une table construite et il aurait des méthodes pour obtenir les images et les graphiques, par exemple comme un flux d'octets. Il appartiendrait aux implémentations de la navigation de mise en œuvre de IWRITE d'encoder les valeurs de la table et les octets d'image/graphique au besoin par la cible d'exportation.

2
Marjan Venema

Bien que je conviens que plus d'informations sont nécessaires pour penser à une architecture, le moyen le plus simple de créer différents types d'objets qui se comportent de la même manière (c'est-à-dire qu'ils généreront une sortie) utilise le motif d'usine. Plus d'infos ICI

Le modèle de méthode d'usine est un modèle de conception créé par objet pour mettre en œuvre le concept d'usines et traite le problème de la création d'objets (produits) sans spécifier la classe d'objet exacte qui sera créée. L'essence de ce modèle est de "définir une interface pour créer un objet, mais laisser les classes qui implémentent l'interface décide de la classe à instancier. La méthode d'usine permet à une classe d'instanciation à différer des sous-classes". de Wikipedia

1
Orposuser

Vous pourriez vous retrouver avec quelque chose comme ça.

Les deux usines sont basées sur:

1 - Pour convertir le type d'entrée (JSON/XML) en une implémentation concrète de la manière de convertir ces données en une image/graphique

2 - une deuxième usine pour décider de la sortie de la sortie à un document Word Document/PDF

Le polymorphisme utilise une interface commune pour toutes les données rendues. Donc, une image/table peut être déplacée comme une interface facile.

1 - usine pour convertir les données JSON/XML en une implémentation concrète:

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

L'usine ci-dessous vous permet de convertir les données XML de données ou JSON au type de béton correct.

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

Les implémentations concrètes font tout le travail lourd de convertir les données au type correspondant. Ils convertissent également les données à l'interface IconvertedData, utilisée pour le polymorphisme.

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

Vous pouvez ajouter ces implémentations si nécessaire, car votre code se développe.

L'interface iconvertedData vous permet de passer un seul type dans la phase suivante: Remarque: Vous ne retournerez peut-être pas des vides ici. Il pourrait par un octet [] pour des images ou un document openxml pour le WordDocument. Ajuster si nécessaire.

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

Polymorphisme:

Ceci est utilisé pour convertir les données au type de sortie correspondant. C'est-à-dire le rendu à PDF pour les données d'image, peut être différente de données d'image de rendu pour 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 - usine de décider du format de sortie:

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

Chaque mise en œuvre concrète expose une méthode courante qui masque la manière dont l'exportation est renvoyée aux implémentations 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 exemple de client pour tout cela serait:

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
Gibson

Nous avons résolu un problème similaire ici: https://ergebnisse.zensus2011.de/?locale=fr Là, nous avons principalement des "tables" et des "graphiques" à exporter dans différents formats: PDF, Excel, la toile. Notre idée était de spécifier chaque objet pour être rendu comme propre Java Class avec des interfaces pour créer et lire ces classes. Dans votre cas, il y aurait 2 implémentations pour chaque objet de création (XML, JSON ) et 4 implémentations de rendu (lecture).

Exemple: Vous aurez besoin de classes pour tableaux: table de classes (poignées structure de la table, validation et contenu) Créée d'interface (fournit des données de table, des cellules, des couvre-échange, une interface de la carte d'interface (getters pour toutes les données)

Vous n'avez probablement pas besoin d'interfaces (ou un seul) mais je pense qu'il fournit toujours un bon découplage particulièrement utile dans les tests.

0
dermoritz

Je pense que ce que vous recherchez est le modèle stratégie . Vous avez une variété de classes pour émettre les données dans le format souhaité, et vous choisissez simplement la appropriée au moment de l'exécution. L'ajout d'un nouveau format doit être aussi simple que d'ajouter une autre classe qui implémente l'interface requise. J'ai fait cela souvent dans Java à l'aide du ressort pour simplement maintenir une carte de convertisseurs, clés par le type de format.

Comme d'autres l'ont mentionné, cela est généralement accompli par toutes les classes implémentant la même interface (ou descendre de la même classe de base) et en choisissant la mise en œuvre via une usine.

0
TMN