Dla mnie właściwą drogą byłyby interfejsy i fabryka. Jeden, który zwraca odniesienia do interfejsów, za którymi mogą się ukrywać różne klasy. Klasy, które wykonują rzeczywistą pracę z pomrukami, muszą zostać zarejestrowane w fabryce, aby wiedziała, którą klasę utworzyć, biorąc pod uwagę zestaw parametrów.
Uwaga: zamiast interfejsów można również używać abstrakcyjnych klas bazowych, ale wadą jest to, że w przypadku pojedynczych języków dziedziczenia ogranicza cię to do pojedynczej klasy bazowej.
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');
Kod ma składnię Delphi (Pascal), ponieważ jest to język, z którym jestem najbardziej zaznajomiony.
Po zarejestrowaniu wszystkich klas implementujących w fabryce powinieneś móc poprosić o odwołanie do interfejsu dla instancji takiej klasy. Na przykład:
Factory.GetReader('SomeFileName.xml');
Factory.GetWriter('SomeExportFileName.ppt');
Factory.GetRepresentation(rtTable, 'html');
powinien zwrócić odwołanie IReader do instancji TXMLReader; odwołanie IWriter do wystąpienia TPowerPointWriter i odwołanie IRepresentation do wystąpienia THTMLTable.
Teraz wszystko, co musi zrobić silnik renderowania, to powiązanie wszystkiego razem:
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;
Interfejs IReadera powinien zapewniać metody odczytu danych potrzebnych implementatorom IRepresentation do konstruowania reprezentacji danych. Podobnie IReprezentacja powinna zapewnić metody, których implementatorzy IWriter potrzebują do eksportowania reprezentacji danych do żądanego formatu pliku eksportu.
Zakładając, że dane w twoich plikach mają charakter tabelaryczny, IReader i obsługiwane przez niego interfejsy mogą wyglądać następująco:
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;
Iteracja nad stołem byłaby wtedy kwestią
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;
Ponieważ reprezentacje mogą mieć obrazy, wykresy i charakter tekstowy, IRpresentation prawdopodobnie miałby podobne metody do IReadera do przechodzenia przez skonstruowaną tabelę i miałby metody uzyskiwania obrazów i wykresów, na przykład jako strumień bajtów. Do implementatorów IWriter należałoby zakodowanie wartości tabeli i bajtów obrazu / wykresu zgodnie z wymaganiami celu eksportu.