การออกแบบสถาปัตยกรรมที่แข็งแกร่งสำหรับการส่งออกหลายประเภท?


10

ฉันกำลังมองหารูปแบบหรือคำแนะนำทางสถาปัตยกรรมสำหรับคุณสมบัติที่กำลังจะมาถึงฉันกำลังออกแบบ โดยพื้นฐานแล้วมันเป็นคุณสมบัติการส่งออกที่มีเป้าหมายการส่งออกหลายรายการและฉันกำลังมองหาวิธีที่จะทำให้เป็นเรื่องง่ายพอที่การเสียบเป้าหมายการส่งออกใหม่ไม่ต้องการการเปลี่ยนแปลงหลักมากมาย ตามเป้าหมายการส่งออกฉันเพียงแค่อ้างถึงประเภทต่างๆของการส่งออกไม่ว่าจะเป็น PDF, งานนำเสนอ PowerPoint, เอกสาร Word, RSS, ฯลฯ ฉันมีชุดฐานของข้อมูลซึ่งแสดงใน JSON และ XML ข้อมูลนี้ใช้ในการสร้างภาพ (โดยใช้หมายเลขหรือประเภทการส่งออกใด ๆ [เช่น PNG, JPG, GIF, ฯลฯ ), กราฟ, การแสดงข้อความ, ตารางและอื่น ๆ

ฉันพยายามหาวิธีทำให้นามธรรมการเรนเดอร์และเลย์เอาต์ลงในการเรนเดอร์หรือเอ็นจิ้นเลย์เอาต์ที่รองรับการเพิ่มเป้าหมายการส่งออกเพิ่มเติม ความช่วยเหลือ / คำแนะนำ / แหล่งข้อมูลเกี่ยวกับวิธีการเข้าถึงสิ่งนี้จะได้รับการชื่นชมอย่างมาก ขอบคุณล่วงหน้า.

สำหรับการแสดงรูปภาพของสิ่งที่ฉันพยายามทำ

ป้อนคำอธิบายรูปภาพที่นี่


คุณสามารถอธิบายสิ่งที่คุณได้ลองไปแล้ว ข้อกำหนด (ความรับผิดชอบ) ของเอ็นจิ้นเลย์เอาต์คืออะไร? ตัวอย่างเช่นคาดว่าจะจัดการการแบ่งหน้าและการเลือกขนาดหน้าได้หรือไม่
วุน

สามารถใช้ข้อมูล XML / JSON เพื่อสร้างเอาต์พุตหลายชนิดในการรันเอาต์พุตเดียวกันเช่นข้อมูล XML ของคุณสร้างรูปภาพและตารางและกราฟในเอกสาร PDF ได้หรือไม่? หรือสามารถใช้ข้อมูล XML / JSON เพื่อสร้างตารางหรือกราฟสำหรับเอกสาร PDF ได้เท่านั้น
กิบสัน

นี่คือทั้งหมดที่เกี่ยวกับxkcd.com/927 - ทำไมคุณพยายามบูรณาการล้อ DocBook, Markdown / pandoc เป็นต้นมีอยู่แล้ว ...
Deer Hunter

คำตอบ:


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 และการอ้างอิง IRPresentation กับอินสแตนซ์ของ 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 ควรจัดเตรียมวิธีการในการอ่านข้อมูลที่ต้องการโดยผู้พัฒนา IRPresentation เพื่อสร้างการแสดงข้อมูล 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
ฉันคิดว่ามันเกี่ยวข้องมากกว่านั้นเล็กน้อย ตัวอย่างเช่นโปรโตคอลใดที่จะใช้ในการสื่อสารข้อมูลตามบรรทัดในแผนภาพ สามารถมีการแสดงข้อมูลทั่วไปใน Rendering / layout engine หรือว่า engine นั้นเป็นเพียงโรงงานสำหรับวิธีการที่กำหนดเองอย่างสมบูรณ์หนึ่งอันสำหรับแต่ละบรรทัดในแผนภาพ
Robert Harvey

ไม่แน่ใจถ้าฉันได้รับคะแนนของคุณที่นี่ เพราะหากจำเป็นต้องใช้โปรโตคอลในการสื่อสารบรรทัดในไดอะแกรมฉันคิดว่าฉันใช้ชุดบริการเพื่อสร้างการส่งออก (ในกรณีนี้คุณจะเห็นรูปแบบ SOA / Integration) แม้ว่านี่จะเป็นจริงการแก้ปัญหามีความยืดหยุ่นและแข็งแกร่งพอที่จะใช้โรงงาน บางทีสิ่งที่คุณต้องการทำคือการสร้างอินเตอร์เฟสแปลงที่มีสองวิธี: วิธีหนึ่งที่ได้รับข้อมูล XML และอีกวิธีหนึ่งสำหรับข้อมูล JSON วัตถุส่งคืนสำหรับทั้งสองจะเป็นวัตถุที่ถูกแปลง ด้วยวิธีนี้คุณสามารถรวบรวมสิ่งที่คุณต้องการ
Orposuser

จริงๆแล้วมีสองคำถามในส่วนหัว: about content (gif, pdf, html) และเกี่ยวกับ transport (ไฟล์โลคอล, http-response-item) หากต้องการขยายคำตอบ @Orposuser (+1): ฉันจะสร้างสตรีมโดยใช้โรงงานที่สามารถ unittested และแสดงผลได้ง่ายสำหรับ http-response
k3b

0

คุณสามารถจบลงด้วยสิ่งนี้

โรงงานสองแห่งตั้งอยู่รอบ ๆ :

1 - สำหรับการแปลงประเภทอินพุต (Json / XML) เป็นการใช้งานที่เป็นรูปธรรมของวิธีการแปลงข้อมูลนี้เป็นภาพ / กราฟ

2 - โรงงานแห่งที่สองสำหรับการตัดสินใจว่าจะแสดงผลลัพธ์ไปยังเอกสารคำ / เอกสาร PDF ได้อย่างไร

ความหลากหลายใช้อินเทอร์เฟซทั่วไปสำหรับข้อมูลที่แสดงผลทั้งหมด ดังนั้นสามารถย้ายรูปภาพ / ตารางไปรอบ ๆ เป็นอินเทอร์เฟซที่ใช้งานง่าย

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 อนุญาตให้คุณส่งประเภทเดียวไปยังเฟสถัดไป: หมายเหตุ: คุณอาจไม่ได้คืนช่องว่างที่นี่ มันสามารถ byte [] สำหรับรูปภาพหรือเอกสาร OpenXml สำหรับ WordDocument ปรับตามความจำเป็น

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

polymorphism:

ใช้สำหรับแปลงข้อมูลเป็นชนิดเอาต์พุตที่เกี่ยวข้อง เช่นการแสดงผลเป็น 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=th ที่ นั่นเรามี "ตาราง" และ "กราฟ" ส่วนใหญ่ที่จะส่งออกในรูปแบบต่าง ๆ : pdf, excel, เว็บ แนวคิดของเราคือการระบุแต่ละวัตถุที่จะแสดงผลเป็นคลาส Java ของตัวเองพร้อมอินเทอร์เฟซเพื่อสร้างและอ่านคลาสเหล่านั้น ในกรณีของคุณจะมีการใช้งาน 2 รายการสำหรับแต่ละวัตถุสำหรับการสร้าง (xml, json) และการใช้งาน 4 รายการสำหรับการแสดงผล (อ่าน)

ตัวอย่าง: คุณจะต้องมีบางคลาสสำหรับตาราง: Class Table (จัดการโครงสร้างตารางการตรวจสอบและเนื้อหา) Interface CreateTable (ให้ข้อมูลตารางเซลล์ช่วงเนื้อหาเนื้อหา) Interface ReadTable (getters สำหรับข้อมูลทั้งหมด)

อาจเป็นไปได้ว่าคุณไม่จำเป็นต้องใช้อินเทอร์เฟซ (หรือเพียงอันเดียว) แต่ฉันคิดว่ามันให้การถอดรหัสที่ดีซึ่งมีประโยชน์เป็นพิเศษในการทดสอบ


0

ฉันคิดว่าสิ่งที่คุณกำลังมองหาคือรูปแบบกลยุทธ์ คุณมีคลาสที่หลากหลายเพื่อส่งออกข้อมูลในรูปแบบที่ต้องการและคุณเลือกคลาสที่เหมาะสมในขณะใช้งานจริง การเพิ่มรูปแบบใหม่ควรจะง่ายเหมือนการเพิ่มคลาสอื่นที่ใช้อินเทอร์เฟซที่จำเป็น ฉันทำสิ่งนี้บ่อยครั้งใน Java โดยใช้ Spring เพื่อรักษาแผนที่ของตัวแปลงโดยพิมพ์ตามรูปแบบ

ตามที่คนอื่น ๆ กล่าวถึงสิ่งนี้มักจะสำเร็จได้โดยให้ทุกคลาสใช้อินเตอร์เฟสเดียวกัน (หรือสืบทอดจากคลาสฐานเดียวกัน) และเลือกการนำไปใช้ผ่านโรงงาน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.