I am use DAO for load object.
But the object is complex and in different places of the program only certain parts of this object are needed.
There is a need for lazy loading, but it is not clear where to place it.
I would like something similar to include entity framework
Options that come to my mind:
in the model object (when accessing a property or a specific method)
but in this case, it is necessary to have a DAO inside the object
this gives a dependence on a specific type of DAO
in DAO (specific method or include().include() chain)
but in this case it is necessary to extend the DAO interface with a strange "include" method
or create many DAO combinations, for example: "docWithTab1AndTab2", "docWithTab2WithOutTab1" and etc
combinatorial explosion...
if the DAO is responsible for interacting with the data source, then it turns out that it should do lazy loading, then where should the code be located?
rough example:
GenericDao<T: class> = interface
function find(const id: string): T;
procedure save(entity: T);
function update(entity: T): T;
procedure delete(entity: T);
function findAll(): TList<T>;
end;
TUser = class
Id: string;
Name: string;
Adress: TList<TAdress>;
Phone: TList<TPhone>;
...
etc TList<> field
end;
TUserFirebirdDao = class(TInterfacedObject, GenericDao<TUser>)
function find(const id: string): TUser;
end;
function TUserFirebirdDao.find(const id: string): TUser;
begin
Result := TUser.Create();
TFDQuery.Open('select * from users where id = :id');
...
further filling properties from the dataset
...
end;
I get filled object, but I need more "Adress" and possibly "Phone".
You don't need to download them right away.
What to do?
TUser.GetPhone(): TList<TPhone>
begin
TPhoneFirebirdDao.find() ???
end;
or
TUserWithPhoneFirebirdDao = class
TUserWithAdressFirebirdDao = class
TUserWithPhoneAndAdressFirebirdDao = class
Or use some other approach, concept?
Please answer as clear as possible.
Thank you.
Related
This question already has answers here:
Copy object data fields into subclass instance
(2 answers)
Closed 8 years ago.
depending on the dataflow (data itself) i'm starting with a very simple data type "AboutMe", later depending on the data itself or the workflow I want to continue working using this data now in a class called "AboutMe_more". This procedure might happen 1..3 times in my program.
AboutMe= class
Name : String
end;
AboutMe_more = class(AboutMe)
gender : String;
Birth : TDate;
Aboutme_complete = class (AboutMe_more)
adresss : String;
salery : Real;
.....
end;
starting with the complete class is not a good idea in my case because there might be a different switch to an other desired class like
Aboutme_complete_option = class (AboutMe_more)
company : string;
city : String;
kids : String;
.....
end;
Q:
a) What is the best way to transfer data from one class to the derived class, not need for transfer data to parent class .
b) Is the way a good programming style or does the need for that datamovement indicate a poor class construction / design ?
It almost looks like you are trying to model a database using objects.
b) Is the way a good programming style or does the need for that datamovement indicate a poor class construction / design ?
The problem here (as mentioned by #jpfollenius) is that you'll end up with a very deep hierarchy of objects.
a) What is the best way to transfer data from one class to the derived class [...]?
I don't know about the best way, but in Delphi transferring data from one object to another is often done using an overloaded Assign.
TMyObject = class(TPersistent)
procedure Assign(Source: TPersistent); overload;
....
implementation
procedure TMyObject.Assign(Source: TPersistent);
begin
inherited Assign(Source);
if (Source is TMyObject) then begin
Self.Field1:= Source.Field1;
Self.Field2:= Source.Field2;
end;
end;
More sensible approach
Inheritance is only one way to address this problem.
A more appropriate way would be to use encapsulation.
Here you have an object and an interface which holds the data and container objects which give you access to that data.
Delphi has a very nice mechanism for that because it allows you to delegate the implementation of an interface to a contained object.
An example using 2 data objects with interfaces and a container object.
type
IData1 = interface
['{3F996D68-1FD0-4490-AE60-8F735A9DFFE8}'] //Use ctrl+alt+g to generate a number
function GetData1: integer;
procedure SetData1(value: integer);
property Data1: integer read GetData1 write SetData1;
end;
IData2 = interface
['{3F996D68-1FD0-4490-AE60-8F735A9DFFE9}']
function GetData2: integer;
procedure SetData2(value: integer);
property Data2: integer read GetData2 write SetData2;
end;
TData1 = class(TInterfacedObject, IData1);
private
FData1: integer;
protected
function GetData1: integer;
procedure SetData1(value: integer);
public
property Data: integer read GetData1 write SetData1;
end;
TData2 = class(TInterfacedObject, IData2);
{see TData1 above}
TContainer = class(TInterfacedObject, IData1, IData2)
private
FData1: TData1;
FData2: TData2;
public
constructor Create();
property Data1: TData1 read FData1 implements IData1; //delegates implementation to object FData.
property Data2: TData2 read FData2 implements IData2;
end;
Copied from this question: https://stackoverflow.com/questions/6063274/hidden-features-in-the-delphi-language (now sadly deleted).
Is it possible to add and implement an interface to an already existing class (which is a descendant of TInterfaced or TInterfacedPersistent) to accomplish separating Model and View into 2 units?
A small explanation why I need something like this:
I am developing a tree-structure, open-type model, which has following structure (VERY simplified and incomplete, just to illustrate the outline of the problem):
Database_Kernel.pas
TVMDNode = class(TInterfacedPersistent);
public
class function ClassGUID: TGUID; virtual; abstract; // constant. used for RTTI
property RawData: TBytes {...};
constructor Create(ARawData: TBytes);
function GetParent: TVMDNode;
function GetChildNodes: TList<TVMDNode>;
end;
Vendor_Specific_Stuff.pas
TImageNode = class(TVMDNode)
public
class function ClassGUID: TGUID; override; // constant. used for RTTI
// Will be interpreted out of the raw binary data of the inherited class
property Image: TImage {...};
end;
TUTF8Node = class(TVMDNode)
public
class function ClassGUID: TGUID; override; // constant. used for RTTI
// Will be interpreted out of the raw binary data of the inherited class
property StringContent: WideString {...};
end;
TContactNode = class(TVMDNode)
public
class function ClassGUID: TGUID; override; // constant. used for RTTI
// Will be interpreted out of the raw binary data of the inherited class
property PreName: WideString {...};
property FamilyName: WideString {...};
property Address: WideString {...};
property Birthday: TDate {...};
end;
Using a GUID-based RTTI (which uses ClassGUID), the function GetChildNodes is able to find the matching class and initialize it with the raw data. (Each dataset contains ClassGUID and RawData beside other data like created/updated timestamps)
It is important to notice that my API (Database_Kernel.pas) is strictly separated from the vendor's node classes (Vendor_Specific_Stuff.pas).
A vendor-specific program's GUI wants to visualize the nodes, e.g. giving them an user-friendly name, an icon etc.
Following idea works:
IGraphicNode = interface(IInterface)
function Visible: boolean;
function Icon: TIcon;
function UserFriendlyName: string;
end;
The vendor's specific descendants of TVMDNode in Vendor_Specific_Stuff.pas will implement the IGraphicNode interface.
But the vendor also needs to change Database_Kernel.pas to implement IGraphicNode to the base node class TVMDNode (which is used for "unknown" nodes, where RTTI was unable to find the matching class of the dataset, so at least the binary raw data can be read using TVMDNode.RawData).
So he will change my class as follows:
TVMDNode = class(TInterfacedPersistent, IGraphicNode);
public
property RawData: TBytes {...};
class function ClassGUID: TGUID; virtual; abstract; // constant. used for RTTI
constructor Create(ARawData: TBytes);
function GetParent: TVMDNode;
function GetChildNodes: TList<TVMDNode>;
// --- IGraphicNode
function Visible: boolean; virtual; // default behavior for unknown nodes: False
function Icon: TIcon; virtual; // default behavior for unknown nodes: "?" icon
function UserfriendlyName: string; virtual; // default behavior for unknown nodes: "Unknown"
end;
The problem is that IGraphicNode is vendor/program-specific and should not be in the API's Database_Kernel.pas, since GUI and Model/API should be strictly divided.
My wish would be that the interace IGraphicNode could be added and implemented to the existing TVMDNode class (which is already a descendant of TInterfacedPersistent to allow interfaces) in a separate unit. As far as I know, Delphi does not support something like this.
Beside the fact that it is not nice to mix Model and View in one single unit/class, there will be following real-world problem: If the vendor has to change my Database_Kernel.pas API to extend TVMDNode with the IGraphicNode interface, he needs to re-do all his changes, as soon as I release a new version of my API Database_Kernel.pas.
What should I do? I thought very long about possible solutions possible with Delphi's OOP. A workaround may be nesting TVMDNode's into a container class, which has a secondary RTTI, so after I have found the TVMDNode class, I could search for a TVMDNodeGUIContainer class. But this sounds very strangle and like a dirty hack.
PS: This API is an OpenSource/GPL project. I am trying to stay compatible with old generations of Delphi (e.g. 6), since I want to maximize the number of possible users. However, if a solution of the problem above is only possible with the new generation of Delphi languages, I might consider dropping Delphi 6 support for this API.
Yes it is possible.
We implemented something similar to gain control of global/singletons for testing purposes. We changed our singletons to be accessible as interfaces on the application (not TApplication, our own equivalent). Then we added the ability to dynamically add/remove interfaces at run-time. Now our test cases are able to plug in suitable mocks as and when needed.
I'll describe the general approach, hopefully you'll be able to apply it to the specifics of your situation.
Add a field to hold a list of dynamically added interface. An TInterfaceList works nicely.
Add methods to add/remove the dynamic interfaces.
Override function QueryInterface(const IID: TGUID; out Obj): HResult; virtual;. Your implementation will first check the interface list, and if not found will defer to the base implementation.
Edit: Sample Code
To answer your question:
I understand that the class now can tell others that it supports interface X now, so the interface was ADDED during runtime. But I also need to IMPLEMENT the interface's methods from outside (another unit). How is this done?
When you add the interface, you're adding an instance of the object that implements the interface. This is very much like the normal property ... implements <interface> technique to delegate implementation of an interface to another object. The key difference being this is dynamic. As such it will have the same kinds of limitations: E.g. no access to the "host" unless explicitly given a reference.
The following DUnit test case demonstrates a simplified version of the technique in action.
unit tdDynamicInterfaces;
interface
uses
SysUtils,
Classes,
TestFramework;
type
TTestDynamicInterfaces = class(TTestCase)
published
procedure TestUseDynamicInterface;
end;
type
ISayHello = interface
['{6F6DDDE3-F9A5-407E-B5A4-CDF91791A05B}']
function SayHello: string;
end;
implementation
{ ImpGlobal }
type
TDynamicInterfaces = class(TInterfacedObject, IInterface)
{ We must explicitly state that we are implementing IInterface so that
our implementation of QueryInterface is used. }
private
FDynamicInterfaces: TInterfaceList;
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
public
constructor Create;
destructor Destroy; override;
procedure AddInterface(AImplementedInterface: IInterface);
end;
type
TImplementor = class (TInterfacedObject, ISayHello)
{ NOTE: This could easily have been implemented in a separate unit. }
protected
{ISayHello}
function SayHello: string;
end;
{ TDynamicInterfaces }
procedure TDynamicInterfaces.AddInterface(AImplementedInterface: IInterface);
begin
{ The simplest, but least flexible approach (see also QueryInterface).
Other options entail tagging specific GUIDs to be associated with given
implementation instance. Then it becomes feasible to check for duplicates
and also dynamically remove specific interfaces. }
FDynamicInterfaces.Add(AImplementedInterface);
end;
constructor TDynamicInterfaces.Create;
begin
inherited Create;
FDynamicInterfaces := TInterfaceList.Create;
end;
destructor TDynamicInterfaces.Destroy;
begin
FDynamicInterfaces.Free;
inherited Destroy;
end;
function TDynamicInterfaces.QueryInterface(const IID: TGUID; out Obj): HResult;
var
LIntf: IInterface;
begin
{ This implementation basically means the first implementor added will be
returned in cases where multiple implementors support the same interface. }
for LIntf in FDynamicInterfaces do
begin
if Supports(LIntf, IID, Obj) then
begin
Result := S_OK;
Exit;
end;
end;
Result := inherited QueryInterface(IID, Obj);
end;
{ TImplementor }
function TImplementor.SayHello: string;
begin
Result := 'Hello. My name is, ' + ClassName;
end;
{ TTestDynamicInterfaces }
procedure TTestDynamicInterfaces.TestUseDynamicInterface;
var
LDynamicInterfaceObject: TDynamicInterfaces;
LInterfaceRef: IUnknown;
LFriend: ISayHello;
LActualResult: string;
begin
LActualResult := '';
{ Use ObjRef for convenience to not declare interface with "AddInterface" }
LDynamicInterfaceObject := TDynamicInterfaces.Create;
{ But lifetime is still managed by the InterfaceRef. }
LInterfaceRef := LDynamicInterfaceObject;
{ Comment out the next line to see what happens when support for
interface is not dynamically added. }
LDynamicInterfaceObject.AddInterface(TImplementor.Create);
if Supports(LInterfaceRef, ISayHello, LFriend) then
begin
LFriend := LInterfaceRef as ISayHello;
LActualResult := LFriend.SayHello;
end;
CheckEqualsString('Hello. My name is, TImplementor', LActualResult);
end;
end.
You can preserve the ability to persist data and implement it through inheritance and still create the correct instances for the ClassGUIDs stored in the tables if you'd apply the factory design pattern.
For each node class there would be one class factory (or just a function pointer) responsible for creation of the correct Delphi class. Class factories may register themselves in the unit initialization section (once per application startup) at the kernel singleton object.
The kernel singleton would then map GUID to correct factory that would in turn call the correct class instance constructor (as shown at http://delphipatterns.blog.com/2011/03/23/abstract-factory)
Packages may be split into separate DLLs and classes implemented in separate units, still inheriting from one base TVMNode class.
The features you now use RTTI for can be supported in descendant classes or in the factory classes easily through some virtual methods.
You might also consider using simpler Data Transfer Objects for saving/loading the TVMNodes and perhaps take some inspiration from an already well perceived Object Relational Mapper or a Object Persistence framework as the problem you are trying to solve seem to me like exactly the problems they are handling (already)
I don't know about good Delphi open source frameworks of this class. But from other languages you can look at Java Hibernate, Microsoft .NET Entity Framework or minimalistic Google Protocol Buffers serializer
I found this definition of an generic class in the web :
TVertex<T> = class
public
Name: String;
OutputAttributes: TVertexOutputAttributes;
Marker: Boolean;
Data: T; // User-defined data attribute
function HasAdditionalAttributes: Boolean;
constructor Create();
procedure Mark;
procedure UnMark;
end;
a single instance of a TVertex class is created with the following line of code :
A := TVertex<Integer>.Create();
A.Name := 'A';
in this example we define T as Integer data Type. My question goes now like this :
If my use case does not need any assigend datatype T it would be much better / logical if I can skip the specification on the datatype. I failed with :
A := TVertex<>.Create();
A.Name := 'A';
Any change to avoid the handover of the datatype at the create process ???
What you are explicitly asking for has one obvious flaw. What will the compiler do with this declaration?
Data: T; // User-defined data attribute
If you don't supply a T, then the compiler doesn't know what to do.
This observation leads us to one possible approach. If you don't want to supply T then presumably you don't want the class to contain this member. How can it contain that member if its type is not supplied? So define a non-generic version:
type
TVertex = class
public
Name: String;
OutputAttributes: TVertexOutputAttributes;
Marker: Boolean;
end;
And then derive your generic version from this:
type
TVertex<T> = class(TVertex)
public
Data: T; // User-defined data attribute
end;
Clearly you would need to determine in which class the methods should be declared and implemented. Clearly any methods that did not rely on Data could be implemented in the non-generic class.
Situation
I'd like to make an RPC interface easier to use. This is a custom interface so there is no readily available wrapper.
I have to write several wrappers around functions which often have many arguments.
Possible solutions
Solution 1 - Using a class for each function:
TDoSomethingFunction = class
public
property Arg1: Integer;
property Arg2: string;
property Arg3: Boolean;
procedure Run;
end;
The caller has to create an object to call the function:
var
DoSomething: TDoSomethingFunction;
begin
DoSomething := TDoSomethingFunction.Create;
try
DoSomething.Arg1 := 0;
...
DoSomething.Run;
finally
free;
end;
Method 2 - Using a wrapper method for each function:
procedure TRPCInterface.DoSomething(AArg1: Integer; AArg2: string; AArg3: Boolean);
The caller can simply call it:
TRPCInterface.DoSomething(0, ...);
Pro and contra
Method 1 - Class for each function
Contra
More code required.
An object must be created which takes up memory.
Pro
Reading the code is easier, you don't have to look at the declaration to see what the arguments are.
Method 2 - Wrapper method
Contra
You can't tell which arguments are used by just looking at the code.
Pro
Much less code to write.
The wrapper is thinner (no object has to be created).
Which method should I use?
There is an intermediate solution that is calling the wrapper methods passing an object argument.
TDoSomethingArgs = class
public
property Arg1: Integer;
property Arg2: string;
property Arg3: Boolean;
end;
procedure TRPCInterface.DoSomething(Args: TDoSomethingArgs);
one advantage of this method is that you still use methods, but still it's more readable.
One advantage of using classes (you can also use records) in arguments is that you can later change the arguments (add more, change behavior) and if you choose it well, it does not break backward compatibility - in summary you can change method signature without breaking code.
You haven't specified Delphi version, but if yours supports generics, I would go with:
type
TArgList = class( TDictionary< String, Variant > );
type
TBaseFunc = class
private
FArgs: TArgList;
public
function Run: Boolean; virtual; abstract;
public
property Args: TVarList read FArgs write FArgs;
end;
type
TSpecialFunc = class( TBaseFunc )
public
function Run: Boolean; override;
end;
implementation
function TSpecialFunc.Run: Boolean;
begin
// here's where you can access args as variants
end;
you can use:
ASpecialFunc.Args.AddOrSetValue('ArgumentName', 2012);
in this way you will have to write more code, but it's much more readable IMHO and easy to be picked up by other developers in the future.
NOTE: that I haven't tested this code, so chances are that it won't compile.
That's my two cents, I'm very curios as to what others come up with (:
Delphi (and probably a lot of other languages) has class helpers. These provide a way to add extra methods to an existing class. Without making a subclass.
So, what are good uses for class helpers?
I'm using them:
To insert enumerators into VCL classes that don't implement them.
To enhance VCL classes.
To add methods to the TStrings class so I can use the same methods in my derived lists and in TStringList.
TGpStringListHelper = class helper for TStringList
public
function Last: string;
function Contains(const s: string): boolean;
function FetchObject(const s: string): TObject;
procedure Sort;
procedure Remove(const s: string);
end; { TGpStringListHelper }
To simplify access to record fields and remove casting.
At first I was kind of sceptic about class helpers. But then I read an interesting blog entry and now I'm convinced that they are indeed useful.
For example, if you want extra functionality for an existing instance class and for some reason you are not able to change the existing source. You can create a class helper to add this functionality.
Example:
type
TStringsHelper = class helper for TStrings
public
function IsEmpty: Boolean;
end;
function TStringsHelper.IsEmpty: Boolean;
begin
Result := Count = 0;
end;
Every time, we now use an instance of (a subclass of) TStrings, and TStringsHelper is within the scope. We have access to the method IsEmpty.
Example:
procedure TForm1.Button1Click(Sender: TObject);
begin
if Memo1.Lines.IsEmpty then
Button1.Caption := 'Empty'
else
Button1.Caption := 'Filled';
end;
Notes:
Class helpers can be stored in a separate unit, so you can add your own nifty class helpers. Be sure to give these units a easy to remember name like ClassesHelpers for helpers for the Classes unit.
There are also record helpers.
If there are multiple class helpers within scope, expect some problems, only one helper can be used.
This sounds very much like extension methods in C#3 (and VB9). The best use I've seen for them is the extensions to IEnumerable<T> (and IQueryable<T>) which lets LINQ work against arbitrary sequences:
var query = someOriginalSequence.Where(person => person.Age > 18)
.OrderBy(person => person.Name)
.Select(person => person.Job);
(or whatever, of course). All of this is doable because extension methods allow you to effectively chain together calls to static methods which take the same type as they return.
They're very useful for plug-ins. For example, let's say your project defines a certain data structure and it's saved to disc in a certain way. But then some other program does something very similar, but the data file's different. But you don't want to bloat your EXE with a bunch of import code for a feature that a lot of your users won't need to use. You can use a plugin framework and put importers into a plugin that would work like this:
type
TCompetitionToMyClass = class helper for TMyClass
public
constructor Convert(base: TCompetition);
end;
And then define the converter. One caveat: a class helper is not a class friend. This technique will only work if it's possible to completely setup a new TMyClass object through its public methods and properties. But if you can, it works really well.
I would not recommend to use them, since I read this comment:
"The biggest problem with class
helpers, from the p.o.v of using them
in your own applications, is the fact
that only ONE class helper for a given
class may be in scope at any time."
... "That is, if you have two helpers
in scope, only ONE will be recognised
by the compiler. You won't get any
warnings or even hints about any other
helpers that may be hidden."
http://davidglassborow.blogspot.com/2006/05/class-helpers-good-or-bad.html
The first time I remember experiencing what you're calling "class helpers" was while learning Objective C. Cocoa (Apple's Objective C framework) uses what are called "Categories."
A category allows you to extend an existing class by adding you own methods without subclassing. In fact Cocoa encourages you to avoid subclassing when possible. Often it makes sense to subclass, but often it can be avoided using categories.
A good example of the use of a category in Cocoa is what's called "Key Value Code (KVC)" and "Key Value Observing (KVO)."
This system is implemented using two categories (NSKeyValueCoding and NSKeyValueObserving). These categories define and implement methods that can be added to any class you want. For example Cocoa adds "conformance" to KVC/KVO by using these categories to add methods to NSArray such as:
- (id)valueForKey:(NSString *)key
NSArray class does not have either a declaration nor an implementation of this method. However, through use of the category. You can call that method on any NSArray class. You are not required to subclass NSArray to gain KVC/KVO conformance.
NSArray *myArray = [NSArray array]; // Make a new empty array
id myValue = [myArray valueForKey:#"name"]; // Call a method defined in the category
Using this technique makes it easy to add KVC/KVO support to your own classes. Java interfaces allow you to add method declarations, but categories allow you to also add the actual implementations to existing classes.
As GameCat shows, TStrings is a good candidate to avoid some typing:
type
TMyObject = class
public
procedure DoSomething;
end;
TMyObjectStringsHelper = class helper for TStrings
private
function GetMyObject(const Name: string): TMyObject;
procedure SetMyObject(const Name: string; const Value: TMyObject);
public
property MyObject[const Name: string]: TMyObject read GetMyObject write SetMyObject; default;
end;
function TMyObjectStringsHelper.GetMyObject(const Name: string): TMyObject;
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
result := nil
else
result := Objects[idx] as TMyObject;
end;
procedure TMyObjectStringsHelper.SetMyObject(const Name: string; const Value:
TMyObject);
var
idx: Integer;
begin
idx := IndexOf(Name);
if idx < 0 then
AddObject(Name, Value)
else
Objects[idx] := Value;
end;
var
lst: TStrings;
begin
...
lst['MyName'] := TMyObject.Create;
...
lst['MyName'].DoSomething;
...
end;
Did you ever need to access multi line strings in the registry?
type
TRegistryHelper = class helper for TRegistry
public
function ReadStrings(const ValueName: string): TStringDynArray;
end;
function TRegistryHelper.ReadStrings(const ValueName: string): TStringDynArray;
var
DataType: DWord;
DataSize: DWord;
Buf: PChar;
P: PChar;
Len: Integer;
I: Integer;
begin
result := nil;
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, #DataType, nil, #DataSize) = ERROR_SUCCESS then begin
if DataType = REG_MULTI_SZ then begin
GetMem(Buf, DataSize + 2);
try
if RegQueryValueEx(CurrentKey, PChar(ValueName), nil, #DataType, PByte(Buf), #DataSize) = ERROR_SUCCESS then begin
for I := 0 to 1 do begin
if Buf[DataSize - 2] <> #0 then begin
Buf[DataSize] := #0;
Inc(DataSize);
end;
end;
Len := 0;
for I := 0 to DataSize - 1 do
if Buf[I] = #0 then
Inc(Len);
Dec(Len);
if Len > 0 then begin
SetLength(result, Len);
P := Buf;
for I := 0 to Len - 1 do begin
result[I] := StrPas(P);
Inc(P, Length(P) + 1);
end;
end;
end;
finally
FreeMem(Buf, DataSize);
end;
end;
end;
end;
I've seen them used for making available class methods consistent across classes: Adding Open/Close and Show/Hide to all classes of a given "type" rather than only Active and Visible properties.
If Dephi supported extension methods, one use i want is:
TGuidHelper = class
public
class function IsEmpty(this Value: TGUID): Boolean;
end;
class function TGuidHelper(this Value: TGUID): Boolean;
begin
Result := (Value = TGuid.Empty);
end;
So i can call if customerGuid.IsEmpty then ....
Another good example is to be able to read values from an XML document (or JSON if you're into that sort of thing) with the IDataRecord paradigm (which i love):
orderGuid := xmlDocument.GetGuid('/Order/OrderID');
Which is much better than:
var
node: IXMLDOMNode;
node := xmlDocument.selectSingleNode('/Order/OrderID');
if Assigned(node) then
orderID := StrToGuid(node.Text) //throw convert error on empty or invalid
else
orderID := TGuid.Empty; // "DBNull" becomes the null guid
Other languages have properly designed class helpers.
Delphi has class helpers that were introduced solely to help the Borland engineers with a compatibility problem between Delphi and Delphi.net.
They were never intended to be used in "user" code and have not been improved since. They can be helpful if developing frameworks (for private use within the framework, as with the original .NET compatibility solution); it is dangerously misguided to equate Delphi class helpers with those in other languages or to draw on examples from those other languages in an effort to identify use cases for those in Delphi.
To this day, the current Delphi documentation has this to say about class and record helpers:
they should not be viewed as a design tool to be used when developing new code
ref: https://docwiki.embarcadero.com/RADStudio/Alexandria/en/Class_and_Record_Helpers_(Delphi)
So the answer to the question "what are the good uses for class helpers" in Delphi specifically is quite simple:
There is only one safe use: For context-specific extensions of utility and interest only in the single codebase that implements and consumes the helper (detailed example here: https://www.deltics.co.nz/blog/posts/683).
The example is a framework for restful API where extensions to a class of interest only to client-side code are provided by "Client Helper" extensions, explicitly imported from client-specific units rather than (over)loading client-concerns into an original class with both server and client context.
Other than that: Do not use them at all (either implementing your own or consuming those provided by others) unless you are prepared to deal with the consequences:
Primarily: Only one helper can be in scope at any time
Secondarily: There is no way to qualify helper referenced
Because of the primary problem:
Adding a unit to (or even just changing the order of) the units in a uses clause may inadvertently "hide" a helper needed in your code (you may not even know where from)
A helper added to a unit already in your uses list could hide some other helper previously "imported" and used from another
And thanks to the secondary problem, if you are unable to re-order the uses list to make a desired helper "visible" or you need 2 unrelated helpers (unaware of each other and so unable to "extent" one another), then there is no way to use it!
Worth emphasising here is that the ability of Delphi class helpers to break other people's code is an almost uniquely bad characteristic. Many language features in many languages can be abused to break your own code; not many enable you to break someone else's!
More details in various posts here: https://www.deltics.co.nz/blog/?s=class+helpers
Particularly this one: https://www.deltics.co.nz/blog/posts/273/