Для меня путь - интерфейсы и фабрика. Тот, который возвращает ссылки на интерфейсы, за которыми могут скрываться различные классы. Все классы, выполняющие фактическую работу, должны быть зарегистрированы в 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 могут кодировать значения таблицы и байты изображения / графика, как того требует цель экспорта.