How to create dynamically generic reports in Smart Pascal. Something like:
var Report: BaseReport; begin Report:= CreateGenericReport( <T> ); end;
Does SmartMS supports generics? | |||||
---|---|---|---|---|---|
Smart Pascal does not support generics. The original syntax derives from Delphi Web Script, was compatible with Delphi 7. Although the DWScript codebase and language has evolved considerably over the years, generics, represents a monumental change that might require a complete re-write of the entire parser and AST ("abstract symbolic tree") in compiler. |
I'd try creating a single factory class in pure smart way, that does not uses generics to allow you to change the key (the key, in this case is a string representing the report name) used to request an report instance.
You simply pass the key value and the static method that will be invoked to do the creation. Lastly, I want to retrieve a particular instance of my report by passing the key value, for instance.
Remember however, the type of the key value and the base type of the returned objects should be configurable.
LIVE PREVIEW
Let's say you have a reporting application. You may have a base report and a bunch of descendant reports, and you want to enforce some consistency over property values for the different reports that are set at the time of creation.
This is the base class TBaseReport:
unit uBaseReport; interface type TBaseReport = class(TObject) published function BuildReport: string; virtual; end; implementation uses uReportA, uReportB, uReportC; function TBaseReport.BuildReport: string; begin Result := 'master report'; end; end.
and three descendant classes:
unit uReportA; interface uses uBaseReport; type TReportA = class(TBaseReport) published function BuildReport: string; override; end; implementation function TReportA.BuildReport: string; begin // TODO end; end.
unit uReporB; interface uses uBaseReport; type TReportB = class(TBaseReport) published function BuildReport: string; override; end; implementation function TReportB.BuildReport: string; begin // TODO end; end.
unit uReporC; interface uses uBaseReport; type TReportC = class(TBaseReport) published function BuildReport: string; override; end; implementation function TReportC.BuildReport: string; begin // TODO end; end.
The idea is try to centralise the creation logic for classes.
Having these spread around every different place a report could be created may be error-prone.
A Report Factory can give you one place to make sure all reports get created in the certain way, and also let the client code simply request a report by name and have the correct concrete report class created for them.
So, let's re-implement the uBaseReport unit
nit uBaseReport; interface uses W3C.Console; type TBaseReport = class(TObject) published function BuildReport: string; virtual; end; TModelClass = class of TBaseReport; TModelFactory = class public class function CreateModelFromID(const AID: string): TBaseReport; class function FindModelClassForId(const AID: string): TModelClass; class function GetModelClassID(AModelClass: TModelClass): string; class procedure RegisterModelClass(const AID: string; AModelClass: TModelClass); end; implementation uses uReportA, uReportB, uReportC; { TModelFactory } type TModelClassRegistration = record ID: string; ModelClass: TModelClass; end; var RegisteredModelClasses: array of TModelClassRegistration; class function TModelFactory.CreateModelFromID(const AID: string): TBaseReport; var ModelClass: TModelClass; begin ModelClass := FindModelClassForId(AID); if ModelClass <> nil then Result := ModelClass.Create else Result := nil; end; class function TModelFactory.FindModelClassForId( const AID: string): TModelClass; var i, Len: integer; begin Result := nil; Len := Length(RegisteredModelClasses); for i := 0 to Len - 1 do if RegisteredModelClasses[i].ID = AID then begin Result := RegisteredModelClasses[i].ModelClass; break; end; end; class function TModelFactory.GetModelClassID(AModelClass: TModelClass): string; var i, Len: integer; begin Result := ''; Len := Length(RegisteredModelClasses); for i := 0 to Len - 1 do if RegisteredModelClasses[i].ModelClass = AModelClass then begin Result := RegisteredModelClasses[i].ID; break; end; end; class procedure TModelFactory.RegisterModelClass(const AID: string; AModelClass: TModelClass); var i, Len: integer; begin Assert(AModelClass <> nil); Len := Length(RegisteredModelClasses); for i := 0 to Len - 1 do if (RegisteredModelClasses[i].ID = AID) and (RegisteredModelClasses[i].ModelClass = AModelClass) then begin Assert(FALSE); exit; end; RegisteredModelClasses.SetLength(Len+1); RegisteredModelClasses[Len].ID := AID; RegisteredModelClasses[Len].ModelClass := AModelClass; end; function TBaseReport.BuildReport: string; begin Result := 'master report'; end; end.
Remember however, the type of the key value and the base type of the returned objects should be configurable.
In the initialization session let's fill the configurable data:
initialization var rec :TModelClassRegistration; rec.ID := 'devil_report'; rec.ModelClass := TReportA; RegisteredModelClasses.Add(rec); rec.ID:= 'flowers_report'; rec.ModelClass := TReportB; RegisteredModelClasses.Add(rec); rec.ID := 'cards_report'; rec.ModelClass := TReportC; RegisteredModelClasses.Add(rec);
With that, we've got a very reusable Factory class,
Now, create a form, add the uBaseReport, SmartCL.Controls.Combobox, SmartCL.Controls.Memo units, and create config the ComboBox and Memo components:
unit Form1; interface uses uBaseReport, SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL. Forms, SmartCL.Fonts, SmartCL.Borders, SmartCL.Application, SmartCL.Controls.Combobox, SmartCL.Controls.Memo; type TForm1 = class(TW3Form) procedure ComboBox1Changed(Sender: TObject); private {$I 'Form1:intf'} div1: TW3Memo; ComboBox1: TW3ComboBox; protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; end; implementation { TForm1 } procedure TForm1.ComboBox1Changed(Sender: TObject); begin if ComboBox1.SelectedIndex >= 1 then begin var rpt:= TModelFactory.CreateModelFromID( ComboBox1.Items[ComboBox1.SelectedIndex] ); div1.InnerHTML := rpt.BuildReport; end; end; procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} div1 := TW3Memo.Create(Self); div1.Width := 264; div1.Top := 128; div1.StyleClass := 'memo'; div1.Left := 104; div1.Height := 208; div1.Name := 'div1'; ComboBox1 := TW3ComboBox.Create(Self); ComboBox1.Width := 264; ComboBox1.Top := 88; ComboBox1.Left := 104; ComboBox1.Height := 32; ComboBox1.Name := 'ComboBox1'; ComboBox1.OnChanged := ComboBox1Changed; ComboBox1.BeginUpdate; ComboBox1.Clear; ComboBox1.Add('Select a report'); ComboBox1.Add('devil_report'); ComboBox1.Add('flowers_report'); ComboBox1.Add('cards_report'); ComboBox1.EndUpdate; end; procedure TForm1.Resize; begin inherited; end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end.
I should have no excuse for avoiding them in the future. Anyway, hopefully this little experiment has sparked some thoughts in you.
Another idea, would be create stores, it would be nice you have for instance a generic data stores, you can switch from Delphi REST mORMot data store to in-memory data store, but this is another story.
just curious to see the javascript output:
var TObject = { $ClassName : "TObject", $Parent : null, ClassName : function (s) { return s.$ClassName }, ClassType : function (s) { return s }, ClassParent : function (s) { return s.$Parent }, $Init : function () {}, Create : function (s) { return s }, Destroy : function (s) { for (var prop in s) if (s.hasOwnProperty(prop)) delete s.prop }, Destroy$ : function (s) { return s.ClassType.Destroy(s) }, Free : function (s) { if (s !== null) s.ClassType.Destroy(s) } } var Exception = { $ClassName : "Exception", $Parent : TObject, $Init : function () { FMessage = "" }, Create : function (s, Msg) { s.FMessage = Msg; return s } } function $W(e) { return e.ClassType ? e : Exception.Create($New(Exception), e.constructor.name + ", " + e.message) } function $NewDyn(c, z) { if (c == null) throw Exception.Create($New(Exception), "ClassType is nil" + z); var i = { ClassType : c }; c.$Init(i); return i } function $New(c) { var i = { ClassType : c }; c.$Init(i); return i } function $Is(o, c) { if (o === null) return false; return $Inh(o.ClassType, c); }; function $Inh(s, c) { if (s === null) return false; while ((s) && (s !== c)) s = s.$Parent; return (s) ? true : false; } var TApplication = { $ClassName : "TApplication", $Parent : TObject, $Init : function ($) { TObject.$Init($); }, RunApp : function (Self) { var m = null, p = "", n = null; m = TModelFactory.FindModelClassForId(TModelFactory, "devil_report"); console.log(TObject.ClassName(m)); p = TModelFactory.GetModelClassID(TModelFactory, m); console.log(p); n = TModelFactory.CreateModelFromID(TModelFactory, "flowers_report"); console.log(n); TBaseReport.showmsg(n, "this is the smart report"); }, Destroy : TObject.Destroy }; var TModelFactory = { $ClassName : "TModelFactory", $Parent : TObject, $Init : function ($) { TObject.$Init($); }, CreateModelFromID : function (Self, AID) { var Result = null; var ModelClass$1 = null; ModelClass$1 = TModelFactory.FindModelClassForId(Self, AID); if (ModelClass$1) { Result = TObject.Create($NewDyn(ModelClass$1, "")); } else { Result = null; } return Result }, FindModelClassForId : function (Self, AID$1) { var Result = null; var i = 0; var Len = 0; Result = null; Len = RegisteredModelClasses.length; var $temp1; for (i = 0, $temp1 = Len; i < $temp1; i++) { if (RegisteredModelClasses[i].ID$2 == AID$1) { Result = RegisteredModelClasses[i].ModelClass; break; } } return Result }, GetModelClassID : function (Self, AModelClass) { var Result = ""; var i$1 = 0; var Len$1 = 0; Result = ""; Len$1 = RegisteredModelClasses.length; var $temp2; for (i$1 = 0, $temp2 = Len$1; i$1 < $temp2; i$1++) { if (RegisteredModelClasses[i$1].ModelClass == AModelClass) { Result = RegisteredModelClasses[i$1].ID$2; break; } } return Result }, Destroy : TObject.Destroy }; var TBaseReport = { $ClassName : "TBaseReport", $Parent : TObject, $Init : function ($) { TObject.$Init($); }, showmsg : function (Self, str) { console.log(str); }, Destroy : TObject.Destroy }; function Copy$TModelClassRegistration(s, d) { d.ID$2 = s.ID$2; d.ModelClass = s.ModelClass; return d; } function Clone$TModelClassRegistration($) { return { ID$2 : $.ID$2, ModelClass : $.ModelClass } } var TReportA = { $ClassName : "TReportA", $Parent : TBaseReport, $Init : function ($) { TBaseReport.$Init($); }, Destroy : TObject.Destroy }; var TReportB = { $ClassName : "TReportB", $Parent : TBaseReport, $Init : function ($) { TBaseReport.$Init($); }, Destroy : TObject.Destroy }; var TReportC = { $ClassName : "TReportC", $Parent : TBaseReport, $Init : function ($) { TBaseReport.$Init($); }, Destroy : TObject.Destroy }; var Application = null; var RegisteredModelClasses = [], rec = { ID$2 : "", ModelClass : null }; rec.ID$2 = "devil_report"; rec.ModelClass = TReportA; RegisteredModelClasses.push(Clone$TModelClassRegistration(rec)); ; rec.ID$2 = "cards_report"; rec.ModelClass = TReportB; RegisteredModelClasses.push(Clone$TModelClassRegistration(rec)); ; rec.ID$2 = "flowers_report"; rec.ModelClass = TReportC; RegisteredModelClasses.push(Clone$TModelClassRegistration(rec)); ; var m = null, p = "", n = null; m = TModelFactory.FindModelClassForId(TModelFactory, "devil_report"); console.log(TObject.ClassName(m)); p = TModelFactory.GetModelClassID(TModelFactory, m); console.log(p); n = TModelFactory.CreateModelFromID(TModelFactory, "flowers_report"); console.log(n); TBaseReport.showmsg(n, "this is the smart report");
Nenhum comentário:
Postar um comentário