Разработка надежной архитектуры для нескольких типов экспорта?

10

Я ищу шаблоны или архитектурное руководство для будущей функции, которую я разрабатываю. По сути, это функция экспорта с несколькими целями экспорта, и я ищу способ сделать его достаточно универсальным, когда подключение новых целей экспорта не требует больших изменений ядра. Что касается целей экспорта, я просто имею в виду различные типы вывода, будь то PDF, презентации PowerPoint, документы Word, RSS и т. Д. У меня есть базовый набор данных, который представлен в JSON и XML. Эти данные используются для построения изображений (с использованием любого числа или типов экспорта [например, PNG, JPG, GIF и т. Д.), Графиков, текстовых представлений, таблиц и многого другого.

Я пытаюсь найти способ абстрагировать весь рендеринг и макет в какой-то механизм рендеринга или макета, который обрабатывает добавление дополнительных целей экспорта. Любая помощь / предложения / ресурсы относительно того, как подойти к этому, будет принята с благодарностью. Заранее спасибо.

Для наглядного представления о том, чего я пытаюсь достичь.

введите описание изображения здесь

naivedeveloper
источник
Можете ли вы описать то, что вы пробовали до сих пор? Каковы требования (обязанности) механизма верстки? Например, ожидается ли обработка страниц и выбор размера страницы?
Руон
Можно ли использовать данные XML / JSON для создания нескольких выходных типов в одном и том же выходном прогоне, т. Е. Ваши XML-данные создают изображения, таблицы и графики в PDF-документе? Или данные XML / JSON можно использовать только для создания таблицы или графика для документа PDF?
Гибсон
Это все о xkcd.com/927 - почему вы пытаетесь изобрести велосипед? DocBook, Markdown / pandoc и т. Д. Уже существуют ...
Охотник на оленей

Ответы:

2

Для меня путь - интерфейсы и фабрика. Тот, который возвращает ссылки на интерфейсы, за которыми могут скрываться различные классы. Все классы, выполняющие фактическую работу, должны быть зарегистрированы в Factory, чтобы он знал, какой класс создать, учитывая набор параметров.

Примечание: вместо интерфейсов вы также можете использовать абстрактные базовые классы, но недостатком является то, что для языков с одним наследованием он ограничивает вас одним базовым классом.

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

Код в синтаксисе Delphi (Pascal), поскольку это язык, с которым я наиболее знаком.

После того, как все реализующие классы зарегистрированы на фабрике, вы сможете запросить ссылку на интерфейс для экземпляра такого класса. Например:

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

должен вернуть ссылку IReader на экземпляр TXMLReader; ссылка IWriter на экземпляр TPowerPointWriter и ссылка IRepresentation на экземпляр THTMLTable.

Теперь все, что нужно сделать движку рендеринга, это связать все вместе:

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;

Интерфейс IReader должен предоставлять методы для чтения данных, необходимых разработчикам IRepresentation для построения представления данных. Аналогично, IRepresentation должен предоставлять методы, которые необходимы разработчикам IWriter для экспорта представления данных в запрошенный формат файла экспорта.

Предполагая, что данные в ваших файлах имеют табличную природу, IReader и его поддерживающие интерфейсы могут выглядеть следующим образом:

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;

Итерация по таблице будет тогда вопросом

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;

Поскольку представления могут быть изображениями, графиками и текстовыми по природе, IRepresentation, вероятно, будет иметь методы, аналогичные IReader для обхода построенной таблицы, и будет иметь методы для получения изображений и графиков, например, в виде потока байтов. Реализаторы IWriter могут кодировать значения таблицы и байты изображения / графика, как того требует цель экспорта.

Марьян Венема
источник
1

Хотя я согласен с тем, что для размышлений об архитектуре требуется больше информации, наиболее простой способ создания объектов различного типа, которые ведут себя одинаково (т. Е. Все они генерируют выходные данные), использует шаблон фабрики. Больше информации здесь

Шаблон фабричного метода - это объектно-ориентированный шаблон творческого проектирования, предназначенный для реализации концепции фабрик, который решает проблему создания объектов (продуктов) без указания точного класса объекта, который будет создан. Суть этого шаблона заключается в том, чтобы «определить интерфейс для создания объекта, но пусть классы, реализующие интерфейс, решат, какой класс создать. Метод Factory позволяет классу отложить создание экземпляров для подклассов». Из википедии

Orposuser
источник
1
Я думаю, что это немного сложнее, чем это. Например, какие протоколы будут использоваться для передачи данных вдоль линий на диаграмме? Может ли быть общее представление данных в механизме рендеринга / компоновки, или же это просто фабрика для полностью пользовательских методов, по одному для каждой линии на диаграмме?
Роберт Харви
Не уверен, понял ли я вашу точку зрения здесь. Потому что, если вам нужно использовать протокол для передачи линий на диаграмме, я думаю, что для генерации экспорта я полагаюсь на набор сервисов (в этом случае вы захотите увидеть некоторые шаблоны soa / интеграции). Даже если это так, решение достаточно гибкое и надежное для использования на заводе. Возможно, вам нужно создать интерфейс преобразователя, который имеет два метода: один для получения данных XML, а другой для данных JSON. Возвращаемый объект для обоих будет преобразованным объектом. Таким образом, вы можете собрать все, что вы хотите.
Orposuser
на самом деле в заголовке два вопроса: о контенте (gif, pdf, html) и о транспорте (локальный файл, http-response-item). Чтобы развернуть ответ @Orposuser (+1): я бы создал поток, используя фабрику, которую можно легко протестировать и легко отобразить для http-ответа.
k3b
0

Вы могли бы закончить с чем-то вроде этого.

Две фабрики основаны вокруг:

1 - для преобразования типа ввода (Json / XML) в конкретную реализацию того, как преобразовать эти данные в изображение / график

2 - Вторая фабрика для принятия решения о том, как визуализировать вывод в документ Word / PDF Document

Полиморфизм использует общий интерфейс для всех визуализированных данных. Таким образом, изображение / таблица может быть перемещена как простой интерфейс.

1 - Фабрика для преобразования данных JSON / XML в конкретную реализацию:

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

Фабрика ниже позволяет вам преобразовать данные XML или Json Data в правильный конкретный тип.

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

Конкретные реализации выполняют всю тяжелую работу по преобразованию данных в соответствующий тип. Они также преобразуют данные в интерфейс IConvertedData, который используется для полиморфизма.

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

Вы можете добавлять эти реализации по мере необходимости, по мере расширения вашего кода.

Интерфейс IConvertedData позволяет передавать один тип в следующую фазу: ПРИМЕЧАНИЕ. Возможно, вы не возвращаете пустоты здесь. Это можно сделать с помощью байта [] для изображений или документа OpenXml для WordDocument. Отрегулируйте по необходимости.

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

Полиморфизм:

Это используется для преобразования данных в соответствующий тип вывода. то есть рендеринг в PDF для данных изображения, может отличаться от данных рендеринга изображения для 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 - Фабрика, чтобы решить выходной формат:

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

Каждая конкретная реализация предоставляет общий метод, который маскирует возвращение экспорта в реализации 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();
    }
}

Пример клиента для всего этого будет:

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

Мы решили аналогичную проблему здесь: https://ergebnisse.zensus2011.de/?locale=en Там у нас есть в основном «таблицы» и «графики» для экспорта в различные форматы: pdf, excel, web. Наша идея заключалась в том, чтобы указать каждый объект, который будет отображаться как собственный класс Java с интерфейсами для создания и чтения этих классов. В вашем случае будет 2 реализации для каждого объекта для создания (xml, json) и 4 реализации для рендеринга (чтения).

Пример: вам понадобятся некоторые классы для таблиц: Таблица классов (обрабатывает структуру таблицы, проверку и содержимое). Интерфейс CreateTable (предоставляет данные таблицы, ячейки, интервалы, содержимое). Интерфейс ReadTable (получатель для всех данных).

Возможно, вам не нужны интерфейсы (или только один), но я думаю, что это всегда обеспечивает хорошую развязку, особенно полезную при тестировании.

dermoritz
источник
0

Я думаю, что вы ищете шаблон стратегии . У вас есть множество классов для вывода данных в нужном формате, и вы просто выбираете подходящий во время выполнения. Добавление нового формата должно быть таким же простым, как добавление другого класса, реализующего необходимый интерфейс. Я часто делал это в Java, используя Spring, чтобы просто поддерживать карту конвертеров с ключами по типу формата.

Как уже упоминалось, это обычно достигается тем, что все классы реализуют один и тот же интерфейс (или происходят из одного базового класса) и выбирают реализацию через фабрику.

TMN
источник