I offloaded all ADO hood in a separate Data Module, so a single module can be referred by several applications. All my applications basically need only two worker methonds to access data:
AdoQuery delivers a result set in a form of TADODataSet.
AdoExecute performs simple update/delete queries without fetching any results.
Here is the class structure:
type
TMyDataModule = class(TDataModule)
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
private
procedure pvtAdoConnect;
procedure pvtAdoExecute(const sql: string);
function pvtAdoQuery(const sql: string): TADODataSet;
public
AdoConnection: TADOConnection;
end;
Then I added two publicly exposed wrappers to class methods. I used that to avoid long class references in the calls:
function AdoQuery(const sql: string): TADODataSet;
procedure AdoExecute(const sql: string);
implementation
function AdoQuery(const sql: string): TADODataSet;
begin
Result := MyDataModule.pvtAdoQuery(sql);
end;
Above are the worker function which I call from within all my forms.
AdoConnect runs only once on DataModuleCreate event. TDatModule derived from TPersistent, which allows to persist the single instance of connection throughout a runtime.
The only thing that annoys me so far - is a useless .DFM which I don't need at all.Is there any option to get rid of it?
I would handle this type of thing in one of two ways, with interfaces or with inheritance. I prefer not to expose classes to the outside world in these cases. The second one could almost be called an interface without interfaces :)
Interfaces
This version returns an interface that includes the required methods. The outside world only needs to use the interface. We keep the implementation details private. Our TMyDBClass implements the interface that we have exposed to the outside world and our global function GetDBInterface returns the single instance.
interface
uses
ADODB;
type
IMyDBInterface = interface
['{2D61FC80-B89E-4265-BB3D-93356BD613FA}']
function AdoQuery(const sql: string): TADODataSet;
procedure AdoExecute(const sql: string);
end;
function GetDBInterface: IMyDBInterface;
implementation
type
TMyDBClass = class(TInterfacedObject, IMyDBInterface)
strict private
FConnection: TADOConnection;
protected
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
public
function AdoQuery(const sql: string): TADODataSet;
procedure AdoExecute(const sql: string);
end;
var
FMyDBInterface: IMyDBInterface;
procedure TMyDBClass.AdoExecute(const sql: string);
begin
// ...
end;
function TMyDBClass.AdoQuery(const sql: string): TADODataSet;
begin
// ...
end;
procedure TMyDBClass.AfterConstruction;
begin
inherited;
FConnection := TADOConnection.Create(nil);
end;
procedure TMyDBClass.BeforeDestruction;
begin
FConnection.Free;
inherited;
end;
// Our global function
function GetDBInterface: IMyDBInterface;
begin
if not Assigned(FMyDBInterface) then
FMyDBInterface := TMyDBClass.Create;
Result := FMyDBInterface;
end;
initialization
finalization
FMyDBInterface := nil;
end.
Inheritance
This version uses a base class that has the required methods. This is a bit easier for people to deal with because it excludes the interface which can be complex to people starting out. Again we hide the implementation details from the user and only expose a shell of a class that includes the two methods we want people to access. The implementation of these methods is performed by a class in the implementation that inherits from the exposed class. We also have a global function that returns the instance of this class. The big advantage that the interface approach has over this approach is that the user of this object can't free the object by accident.
interface
uses
ADODB;
type
TMyDBClass = class(TObject)
public
function AdoQuery(const sql: string): TADODataSet; virtual; abstract;
procedure AdoExecute(const sql: string); virtual; abstract;
end;
function GetDBClass: TMyDBClass;
implementation
type
TMyDBClassImplementation = class(TMyDBClass)
strict private
FConnection: TADOConnection;
protected
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
public
function AdoQuery(const sql: string): TADODataSet; override;
procedure AdoExecute(const sql: string); override;
end;
var
FMyDBClass: TMyDBClassImplementation;
procedure TMyDBClassImplementation.AdoExecute(const sql: string);
begin
inherited;
// ...
end;
function TMyDBClassImplementation.AdoQuery(const sql: string): TADODataSet;
begin
inherited;
// ...
end;
procedure TMyDBClassImplementation.AfterConstruction;
begin
inherited;
FConnection := TADOConnection.Create(nil);
end;
procedure TMyDBClassImplementation.BeforeDestruction;
begin
FConnection.Free;
inherited;
end;
// Our global function
function GetDBClass: TMyDBClass;
begin
if not Assigned(FMyDBClass) then
FMyDBClass := TMyDBClassImplementation.Create;
Result := FMyDBClass;
end;
initialization
FMyDBClass := nil;
finalization
FMyDBClass.Free;
end.
Usage
Usage of these are really easy.
implementation
uses
MyDBAccess; // The name of the unit including the code
procedure TMyMainForm.DoSomething;
var
myDataSet: TADODataSet;
begin
myDataSet := GetDBInterface.AdoQuery('SELECT * FROM MyTable');
...
// Or, for the class version
myDataSet := GetDBClass.AdoQuery('SELECT * FROM MyTable');
...
end;
If you don't have any design-time non-visual components dropped onto your data module, and don't plan to ever do so, then you shouldn't need a data module at all. The whole purpose is for design-time components, and other implementations such as a Web Module or even a Windows Service Application. But not for wrapping pure code without design-time components.
Also, as mentioned in the comments, don't be confused about the meaning of TPersistent. This class is used entirely differently, and can be integrated into the IDE Object Inspector (as sub-properties within a component).
So the ideal thing for you to do is encapsulate everything in just a single class. For your purpose, a database connection...
type
TMyData = class(TObject)
private
FConnection: TADOConnection;
public
constructor Create;
destructor Destroy; override;
procedure pvtAdoConnect;
procedure pvtAdoExecute(const sql: string);
function pvtAdoQuery(const sql: string): TADODataSet;
...
end;
implementation
{ TMyData }
constructor TMyData.Create;
begin
FConnection:= TADOConnection.Create(nil);
end;
destructor TMyData.Destroy;
begin
FConnection.Connected:= False;
FConnection.Free;
inherited;
end;
As for the interpretation of being "persistent", you can create/destroy an instance of it in many ways. For example, you could use a unit's initialization and finalization sections (requiring CoInitialize) or you can have your main form initialize a global instance upon creation.
One common way of doing so is to add...
interface
function MyData: TMyData;
implementation
var
_MyData: TMyData;
function MyData: TMyData;
begin
if not Assigned(_MyData) then
_MyData:= TMyData.Create;
Result:= _MyData;
end;
initialization
_MyData:= nil;
finalization
_MyData.Free;
end.
The first time you call MyData from anywhere, a new global instance will be instantiated. Then, every further time it re-uses the same instance. This also solves the need of ActiveX and CoInitialize etc. because at this point, COM is expected to already be instantiated (which is required for ADO).
Usage of this unit would be extremely simple - use include it in the uses anywhere, and access its instance through the MyData function.
Notes
You should get out of the habit of global variables. This is asking for trouble down the road when trying to do later work. The example above shows how to accommodate for a global object instance. All other variables should be self-contained within that object, or in general one of scope / relevance. The whole control of your TADOConnection should be within here, including connecting/disconnecting, exception handling, assigning the connection string.
In case you might be interested in an alternative without DataModules alltogether, have a look at this:
https://github.com/stijnsanders/xxm/blob/master/Delphi/demo2/03%20Data%20ADO/xxmData.pas
Queries are stored in a single .sql file, which is handy to edit it in specific SQL editors or workbenches. Queries are separated with a line with --"QueryName", and loaded in a query-store on start-up. Assuming you query for smaller recordsets most of the time, the best lock and open style is read-only and static, which offers the best possible performance and least load on the database server. Getting field values uses the Collect call which also offers a little performance gain.
Related
This question is OOP-patterns related (but i'm working with delphi IDE).
I'm developing a library that needs a sort of memory management on their objects (without garbage collection nor reference counting).
I'm thinking on making initialization/finalization methods public to the users knowing that construction/destruction methods will be called automatically (allowing users to clean the object memory without destroying the object for instance). Is there a similar pattern or best practices recommended to do this?
type
TAbstractInitializable = abstract class(TAbstractBase)
constructor Create();
destructor Destroy(); override;
private
FInitialized: Boolean;
protected
procedure CheckError();
procedure DoInitialize(); virtual; abstract;
procedure DoFinalize(); virtual; abstract;
public
procedure Initialize();
procedure Finalize();
end;
//...
procedure TAbstractInitializable.CheckError();
begin
if not FInitialized then
raise Exception.Create('Error: Trying to use a non initialized object.');
end;
procedure TAbstractInitializable.Create();
begin
inherited;
DoInitialize();
end;
procedure TAbstractInitializable.Destroy();
begin
DoFinalize();
inherited;
end;
procedure TAbstractInitializable.Initialize();
begin
if not FInitialized then begin
DoInitialize();
FInitialized:=True;
end;
end;
procedure TAbstractInitializable.Finalize();
begin
if FInitialized then begin
DoFinalize();
FInitialized:=False;
end;
end;
So this could be a library non abstract class:
type
TDataModel = class(TAbstractInitializable)
private
FDataList: TList;
protected
procedure DoInitialize(); override;
procedure DoFinalize(); override;
public
procedure DoSomethingWithData();
end;
//...
procedure TDataModel.DoInitialize();
begin
FDataList:=TList.Create();
LoadData(FDataList); //for non emptyness in this example
end;
procedure TDataModel.DoFinalize();
begin
FreeData(FDataList); //for no memory leaks in this example
FDataList.Destroy();
end;
procedure TDataModel.DoSomethingWithData();
begin
CheckError();
//Do something here
end;
I don't know Delphi, but I think having public methods for initializing and deinitializing objects is the right way to go. For example in any sort of file stream object you'll usually see an open() and close() method. Although they're not always automatically called, it gives the client the ability to manage those behaviors explicitly instead of relying on the class to implicitly initialize and cleanup after itself. In the case of a file handle, you don't want to couple releasing a file handle to garbage collection because garbage collection is often difficult to predict.
I would not make your initialize() and finalize() methods public. If the language was designed for these methods to be used by clients, they would already be public, and in all the libraries I've used, I've never seen something like that. I would write two differently named methods that are descriptive of the action your performing (e.g. create() & destroy()). Your initialize() and finalize() may invoke these methods respectively.
I have to develop plugin system for non-gui app that I'm developing. I envisioned this as core app that has basic set of features, and which is extendable with plugins.
Now, I figured out that probably best way to do this is to make plugins as DLLs, and to load them within host app. Plugins should be able to modify some parts of app core (access to certain methods/variables), and this is tricky part.
What I've thought is to make THost class, which implements IHost and IHostExposed interfaces. When host loads plugin, it will pass IHostExposed to plugin, and plugin could call methods/access to variables in that interface. Something like this:
Interface declarations:
unit uHostInterfaces;
interface
type
IHost = interface
['{BAFA98BC-271A-4847-80CE-969377C03966}']
procedure Start;
procedure Stop;
end;
// this intf will get exposed to plugin
IHostExposed = interface
['{1C59B1A9-EC7A-4D33-A574-96DF8F5A7857}']
function GetVar1: Integer;
function GetVar2: String;
procedure SetVar1(const AValue: Integer);
procedure SetVar2(const AValue: String);
property Var1: Integer read GetVar1 write SetVar1;
property Var2: String read GetVar2 write SetVar2;
end;
implementation
end.
Host class declaration:
unit uHost;
interface
uses
Winapi.Windows, Winapi.Messages,
uHostInterfaces, uInstanceController, uSettings;
type
THost = class(TInterfacedObject, IHost, IHostExposed)
private
FVar1 : Integer;
FVar2 : String;
FWindowHandle : HWND;
FInstanceController: TInstanceController;
FSettings : TSettings;
procedure WndProc(var AMessage: TMessage);
public
constructor Create;
destructor Destroy; override;
// methods from IHost
procedure Start;
procedure Stop;
// methods from IHostExposed, which get set Var1/Var2
function GetVar1: Integer;
function GetVar2: string;
procedure SetVar1(const AValue: Integer);
procedure SetVar2(const AValue: string);
end;
implementation
...
...and how i'd use it:
type
TRegisterPlugin = procedure(const AHostExposed: IHostExposed);
var
hdll : THandle;
RegisterPlugin: TRegisterPlugin;
host : IHost;
begin
host := THost.Create;
hdll := LoadLibrary('plugin.dll');
if hdll <> 0 then
begin
#RegisterPlugin := GetProcAddress(hdll, 'RegisterPlugin');
if Assigned(RegisterPlugin) then
begin
// call the plugin function and pass IHostExposed interface to it
// from there on, plugin can use this interface to interact with core app
RegisterPlugin(host as IHostExposed);
...
What I would like to hear is any suggestions about this approach, and if there are better solutions for what I'm trying to achieve?
Apparently you've worked with interfaces before, but you don't know the component registration features of COM? Use the new project wizard to start an ActiveX library with Automation Objects, have a look around the Type Library editor, and see what happens when the library runs and registers itself (it's all there in System.Win.ComServ.pas)
In the snippet below I have two classes that have the exact same procedure names. The code within these procedures is substantially different. Is it possible to consolidate these classes down to allow for easier maintenance and future enhancements or am I going about this in an acceptable manner?
type
TMFRCore = class
private
FGridMain: TNextGrid;
FGridSummary: TNextGrid;
public
constructor Create(Grid: TNextGrid; SummaryGrid: TNextGrid);
destructor Destroy; override;
procedure SetRowColour(Grid: TNextGrid; Row: Integer; Color: TColor; Start: Integer = 0);
procedure OnSummaryDblClick(Sender: TObject);
function GetSectionId(Section: String): Integer;
property GridMain: TNextGrid read FGridMain write FGridMain;
property GridSummary: TNextGrid read FGridSummary write FGridSummary;
end;
type
TMFRDates = class(TMFRCore)
public
procedure UpdateSummary;
procedure UpdateMain(const sAircraft: string);
end;
type
TMFRHours = class(TMFRCore)
public
procedure UpdateSummary;
procedure UpdateMain(const sAircraft: string);
end;
I initialise these classes as follows;
procedure TfrmMain.FormCreate(Sender: TObject);
begin
MFRHours := TMFRHours.Create(gridHours, gridHoursSummary);
MFRDates := TMFRDates.Create(gridDates, gridDatesSummary);
end;
You can declare the methods as virtual (and probably abstract) in the base class so you can take advantage of dynamic dispatch and polymorphism, but otherwise, there isn't really anything else to consolidate. Everything common to those classes is already in the one base class. Adding those declarations will actually mean you have more code because you'd still need to keep the existing method declarations and implementations.
I have a parent class that can have 2 possible child classes:
TEmailBaseAccount = class
Connected: boolean;
setting: TEmailAccountSettings;
folders: TEmailAccountFolders;
procedure Connect; virtual; abstract;
end;
TEmailIMAPAccount = class(TEmailBaseAccount)
IdIMAP4: TIdIMAP4;
OpenSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
procedure Connect; override;
end;
TlEmailPOP3Account = class(TEmailBaseAccount)
IdPOP3: TIdIPOP3;
OpenSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
procedure Connect; override;
end;
I'm maintaining a list of the email accounts using a generic TList:
TEmailAccountList = class(TList<TEmailBaseAccount>)
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
constructor Create(AOwner: TObject);
destructor Destroy;
end;
and adding the email accounts to the list using the following code:
procedure TEmailAccountList.LoadFromStream(Stream: TStream);
var
a, c: Integer;
e: TEmailBaseAccount;
begin
c := ReadStreamInt(Stream);
for a := 0 to c - 1 do
begin
e := TEmailBaseAccount.Create(FOwnerEmailEngine);
e.LoadFromStream(Stream);
Add(e);
end;
end;
procedure TEmailAccountList.SaveToStream(Stream: TStream);
var
a, c: Integer;
e: TEmailBaseAccount;
begin
c := Count;
WriteStreamInt(Stream, c);
for a := 0 to Count - 1 do
Items[a].SaveToStream(Stream);
end;
At runtime I need to differentiate between the 2 types of child classes using something like:
if account is TEmailIMAPAccount then
...
else if account is TEmailPOP3Account then
...
I am sure that my original class declarations and the TList declaration is not suited to this requirement. What changes are needed in this scenario?
TIA.
Your type declarations are absolutely fine. Your problem is presumably that when you read an item from the stream, you don't know what type it is. You cannot use is since you don't have an instance yet.
Solve that problem by writing a type code to the stream for each instance. When you read from the stream, read the type code and use that to determine which type to instantiate.
This sort of persistence streaming is so much easier using a persistence framework that emits XML, JSON, YAML etc.
Instead of serializing the complete, highly implementation-specific objects, I would only write the account properties (mail account type, user credentials, server / port / security settings) to a file.
This allows to modify the implemenation without breaking existing setting file compatibility.
Also I would not even think about a if <object> is <class> ... else if <object> is <otherclass> ... solution. Instead, define a simple enumeration type TAccountType = (atPOP3, atIMAP) and then branch in a case structure depending on a account type property of the Account, or use the Strategy pattern.
It may seem a little newbie, but I really have got problem with it. I have a form (not the main one)for getting many different data from the user and I want to pass it to a manager class for creating an object with these. The problem is that I can't have this class to use the other unit (getting circle uses) and also it doesn't have access to the manager class instance (which is in main form).
So, what shall I do? I've already considered using public variable but I have a bad feeling about it (regarding OOD patterns).
My suggestion is to decouple data from the GUI because this is causing your problem.
If you have a form which gathers data from the user then you should distinguish the data from the form(TForm).
For example, let's assume that you have some instance of TForm and a form, which is built from three fields: username, age and location. You want the user to enter those three things, but when the user closes the form, you should pass this inserted data onto some object. Form closes, it is freed, but the object persist. Then you pass this object to your manager object.
Simple example:
This is your record which will hold the data
type
TGatheredData = record
Name: String[40];
Age: Byte;
Location: String[40];
end;
Your TForm1 might have an aditional constructor:
constructor TForm1.Create(AOwner: TComponent; var GatheredData: TGatheredData );
begin
inherited Create(AOwner);
FGatheredData := GatheredData;
//you may want to deserialize GatheredData here and show the data in your form controls
end;
You call it, pass GatheredData and then your are showing your form.
Next, when closing form, you pick upd the data from the form controls.
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
if Self.ModalResult = mrOk then
begin
//serialize your object
FGatheredData.Name := '';//name taken from control f.e. TEdit
FGatheredData.Age := '';//name taken from control f.e. TSpinButton
FGatheredData.Location := '';//name taken from control f.e. TEdit
end;
end;
Having this record of data, you may now pass it in the same manner to your Manager object.
You decoupled data from GUI in this way, and you may easly plugin in your record to a number of different forms.
Just remember to declare your record type in external unit and use that unit in your manager unit and forms unit.
Hope this helps a little.
The "manager class" shouldn't be in either form's unit, but in its own. By separating GUI code from bussiness logic you avoid problems such like this.
[Edit: I originally put this answer in a comment, but decided to move it out into full answer. TDatamodules are too important and too common in Delphi not to emphasize them and they provide built-in easy-to-use means of seperating gui from logic and data.]
Other people have given good comments about decoupling gui from the logic and data. Surprisingly, I don't think anybody has mentioned that in Delphi TDatamodules are one main means of doing this. You put your data and logic on the Datamodule, then have both forms "use" the Datamodule to get access to its data and methods. Here is brief intro: http://delphi.about.com/od/database/l/aa101601a.htm
Both of your forms (and other forms) can access datasets or other data/datastructures that are located on/in a Datamodule unit. There should be an easy to find sample project out there illustrating the setup, since it is (or at least was) the standard way to construct Delphi apps.
If I understand your question properly then you want the manager to manage the forms (not the forms to access the manger). Right? You can't close the Main Form as if you do you close the application but you CAN Hide it. (unless you create a console app). But it poses a nice little problem :)
So... Splash form (Main Form) is:
.
.
.
uses
ManagerU;
type
TFormSplash = class(TForm)
procedure FormPaint(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
Manager: TManager;
end;
var
FormSplash: TFormSplash;
implementation
{$R *.dfm}
procedure TFormSplash.FormCreate(Sender: TObject);
begin
Manager := TManager.Create;
end;
procedure TFormSplash.FormDestroy(Sender: TObject);
begin
Manager.Free;
end;
procedure TFormSplash.FormPaint(Sender: TObject);
begin
if Visible then
begin
Manager.GetData(Self);
Hide;
Manager.DoDataStuff;
Close;
end;
end;
end.
DaaObject is:
unit DataObjectU;
interface
uses classes;
type TDataObject = class(TObject)
Data: string;
end;
implementation
end.
Manager is:
interface
uses DataObjectU;
type
TManager = Class(Tobject)
MyData: TDataObject;
constructor Create; virtual;
destructor Destroy; override;
procedure GetData(OwnerForm: TForm);
procedure DoDataStuff;
end;
implementation
uses DataFormU;
{ TManager }
constructor TManager.Create;
begin
inherited Create;
MyData := TDataObject.Create;
end;
destructor TManager.Destroy;
begin
MyData.Free;
inherited;
end;
procedure TManager.DoDataStuff;
begin
// do stuff with data here
end;
procedure TManager.GetData(OwnerForm: TForm);
var
MyDataForm: TDataForm;
begin
MyDataForm := TDataForm.Create(OwnerForm);
try
if MyDataForm.ShowModal = mrOK then
begin
MyData.Data := MyDataForm.Data;
end;
finally
MyDataForm.Free;
end;
end;
end.
The Dataform is:
type
TDataForm = class(TForm)
btnOK: TButton;
procedure btnOKClick(Sender: TObject);
private
function GetData: String;
{ Private declarations }
public
{ Public declarations }
MyData: TDataObject;
property Data: String read GetData;
end;
var
DataForm: TDataForm;
implementation
{$R *.dfm}
procedure TDataForm.btnOKClick(Sender: TObject);
begin
MyData := TDataObject.Create;
ModalResult := mrOk;
end;
function TDataForm.GetData: String;
begin
Result := MyData.Data;
end;
You will need to fill in the rest of the unit code and free some stuff but essentially this:
Start Program (Creates Splash)
Splash Creates the manager calls it to get data from the dataform then hides itself
calls manager to manage the data
when manager is done it then closes.
There is no other way to shut it down now except through task manager!)
Tim
To solve circular refrence error, you use that unit in implementation section.
implementation
{$R *.DFM}
Uses <Your Unit>;
end.
Having this 3 units:
FormMain
FormEdit
UnitMyClass
You create your object in FormMain and pass it to the FormEdit in a function like:
class function FormEdit.EditMyObject(AObject: TMyClass): boolean;
this function will showModal the form. The form will do the changes to the object, and finally return True if user pressed OK.
As you can see in UnitMyClass there is no reference to FormMain or FormEdit
FWIW, I did a whole presentation on this topic in a CodeRage 9 video. It can be seen here:
https://youtu.be/qqKx8fQTTfI