为多种出口类型设计健壮的体系结构?


10

我正在为即将设计的功能寻找图案或体系结构指导。基本上,这是一个具有多个导出目标的导出功能,我正在寻找一种方法,使之足够通用,以便在插入新的导出目标时不需要进行很多核心更改。通过导出目​​标,我只是指的是不同类型的输出,无论是PDF,PowerPoint演示文稿,Word文档,RSS等。我都有一组基本数据,以JSON和XML表示。此数据用于构造图像(使用任何数量或导出类型(例如,PNG,JPG,GIF等),图形,文本表示形式,表格等。

我正在尝试找到一种将所有渲染和布局抽象为某种渲染或布局引擎的方法,该引擎可以处理其他导出目标的添加。任何有关如何解决此问题的帮助/建议/资源将不胜感激。提前致谢。

对于我要实现的目标的图形表示。

在此处输入图片说明


您能描述一下您到目前为止所尝试的吗?布局引擎的要求(职责)是什么?例如,是否可以处理分页和页面大小选择?
rwong

可以使用XML / JSON数据在同一输出运行上创建多种输出类型,即您的XML数据在PDF文档中生成图像,表格和图形吗?还是只能将XML / JSON数据用于为PDF文档创建表格或图表?
吉布森

这一切都与xkcd.com/927有关-为什么要重新发明轮子?DocBook,Markdown / pandoc等已经存在...
Deer Hunter

Answers:


2

对我来说,要走的路是接口和工厂。一种返回对接口的引用,各种类可以在其后隐藏。进行实际grunt工作的类都需要向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');

应该返回对TXMLReader实例的IReader引用;一个对TPowerPointWriter实例的IWriter引用和对THTMLTable实例的IRepresentation引用。

现在,渲染引擎需要做的就是将所有内容捆绑在一起:

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方法允许类将实例化推迟到子类。” 来自维基百科


1
我认为这要涉及的更多。例如,将使用哪些协议沿图中的线进行数据通信?渲染/布局引擎中是否可以有一个通用的数据表示形式,还是该引擎只是用于完全自定义方法的工厂,图中的每一行都有一个?
罗伯特·哈维

不知道我能否在这里表达您的意思。因为如果需要使用协议来传达图中的线条,那么我想我要依靠一组服务来生成导出(在这种情况下,您将希望看到一些soa /集成模式)。即使这是真的,解决方案也足够灵活和强大,可以在工厂使用。也许您想要做的事情是创建一个具有两种方法的转换器接口:一种接收XML数据,另一种接收JSON数据。两者的返回对象都是转换后的对象。这样,您就可以组装所需的任何东西。
Orposuser 2013年

标头中实际上有两个问题:关于内容(gif,pdf,html)和关于传输(本地文件,http-response-item)。扩展@Orposuser(+1)答案:我将使用一个工厂创建流,该工厂可以轻松进行单元测试并轻松呈现以用于http响应。
k3b

0

您可能最终会遇到类似这样的情况。

这两个工厂的基础是:

1-用于将输入类型(Json / XML)转换为如何将此数据转换为图像/图形的具体实现

2-第二工厂,用于决定如何将输出呈现为word Document / 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);
}

下面的Factory允许您将xml数据或Json数据转换为正确的具体类型。

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接口允许您将单个类型传递到下一阶段:注意:您可能不会在此处返回空值。对于图像,它可以是一个byte [],对于WordDocument,它可以是一个OpenXml文档。根据需要进行调整。

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=zh- CN我们主要以不同的格式导出“表格”和“图形”:pdf,excel,web。我们的想法是指定每个要呈现为自己的Java类的对象,并带有创建和读取这些类的接口。在您的情况下,将为每个对象创建2个实现(xml,json),并为渲染(读取)提供4个实现。

示例:您将需要一些表类:类表(处理表结构,验证和内容)接口CreateTable(提供表数据,单元格,跨度,内容)接口ReadTable(获取所有数据的获取器)

也许您不需要接口(或仅一个接口),但我认为它总是提供良好的去耦,在测试中特别有用。


0

我认为您正在寻找的是策略模式。您可以使用多种类以所需的格式输出数据,并且只需在运行时选择适当的类即可。添加新格式应该与添加另一个实现所需接口的类一样简单。我经常在Java中使用Spring来执行此操作,以简单地维护由格式类型作为键的转换器映射。

正如其他人提到的,这通常是通过让所有类实现相同的接口(或从相同的基类派生)并通过工厂选择实现来实现的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.