Details
- You can name a helper anything but it is customary to use the class name and add "Helper". For example, you might name a TObject helper class TObjectHelper.
- Only one helper (or none) applies to a class at any given time. If you choose to implement more than one class helper per project (per compile), the nearest class helper is used (based on your uses clause).
Note: Although you can define more than one helper, I recommend you limit yourself to either none or one per class per project. To help make maintenance and enhancements easier, you might consider having one unit perhaps called helpers.pas per project. I suggest you think of class helpers on a per project basis and avoid creating reusable class helpers that span multiple projects. If you desire to have class helpers for multiple projects, you might consider having a library of class helpers available for use.
The use of class helpers already adds ambiguity so I actually hope the Delphi compiler team restricts class helpers to none or one per class per compile in a future version of Delphi. Thus enforcing one helper per class per project. If that's not possible, I'd like them to remove the ambiguity of multiple class helpers and simply bring in all class helpers and surface errors for any conflicts. If that never happens, or until then, I suggest adopting a one class helper per class per project standard.
- You do not need the class source code (the .DCU will work just fine).
- Helpers have access only to public members of the class it is helping. Helpers do not have access to private nor protected members of the class they are helping.
- You can introduce new member visibility such as private and protected (strict private and strict protected too). If you don't specify, all new members are public.
- You cannot create an object instance directly from a class helper.
- self refers to the class being helped.
- The routines added by a class helper can be used by the class and descendant classes.
- You can add a procedure, function, or property but you cannot add a member field. This means helper properties must make use of existing member fields.
Uses for Class Helpers
Like any language feature, class helpers are powerful in the right hands but you should take care not to overuse them. When you add a class helper, encapsulation is now a bit more ambiguous. A class helper breaks, or at least cracks, OO approach to coding but is sometimes the best way to accomplish a task.
Many developers disagree with the following statement in Delphi's help, "[class helpers]...should not be viewed as a design tool to be used when developing new code. They should be used solely for their intended purpose, which is language and platform RTL binding". Perhaps the driving source of that statement is from a pure OO approach to development where a class implements all functionality discovered during requirements and specified during design. The problem is especially clear with code libraries such as the VCL where it is difficult to predict and implement all future uses. When designing a rich code library, you have to decide when to surface what functionality, which classes a user can inherit from, when to seal a class, etc. Sometimes, allowing a user to use class helpers to add their routines to your code library is the best approach. Whether you use them within you're own code design for an application or your company's code library is a bit more questionable but ultimately if the final code is more maintainable and easier to extend, then you've done your job.
The Good:
- Do use a class helper when you do not own the class you wish to extend and there isn't a more appropriate class to inherit from. For example, many developers like to use a class helper when they wish to add a few routines to an existing VCL class.
- Many developers like to use class helpers for those little routines that don't really belong to a class but are associated with the class. Some developers say those should be global functions and procedures or class members of a utility class while others say they should be part of a class helper.
The Bad and the Ugly:
- Do not use a class helper when inheritance is a better choice.
- Do not use a class helper when you wish to extend a class and you own the source code. Just extend the class directly.
Class Helpers versus Partial Classes
Like partial classes, class helpers are primarily a source code control feature. A class helper tells the compiler to increase the compile scope when resolving identifiers. In very general terms, class helpers allow you to easily extend a class when inheritance isn't possible or practical. Contrast with partial classes which are used for source code management with code generators and large classes to split ownership of a class. Class helpers allow you to cheat a bit by enhancing an existing class without altering it while partial classes allow you to divide your source code and conquer.
Note: Delphi doesn't currently support partial classes. Delphi Prism and most other .Net languages do such as C# and VB.Net (C++/CLI does not). Although both class helpers and partial classes are controversial, I hope a future version of Delphi will support both. I think there is a place for both class helpers and partial classes in OO languages. In the right hands, I think both features lend themselves to more elegant code that is easier to maintain and enhance.
Delphi 2009 Working Example
The following example demonstrates adding a procedure using a helper class to our own TCyborg class. The name of the helper class is TCyborgHelper.
Create a Windows form application and place two buttons on it. Alter the code as follows:
//Add this class to the interface section:
TCyborg = class(TObject)
public
FCyborgName: String;
end;
//Add this class helper to the interface section too:
TCyborgHelper = class helper for TCyborg
procedure ShowClassName;
end;
//Add the procedure's code to the implementation section:
procedure TCyborgHelper.ShowClassName;
begin
ShowMessage(Self.ClassName);
end;
You can exercise our new class and class helper with the following code. For example, on the click events of two buttons.
//Normal usage using a class member field.
var
MyRobot: TCyborg;
begin
MyRobot := TCyborg.Create;
MyRobot.FCyborgName := 'Cameron';
ShowMessage('Hi, my name is ' + MyRobot.FCyborgName);
FreeAndNil(MyRobot);
end;
//Use helper procedure. Notice self refers to the
//TCyborg class and not to the TCyborgHelper class.
var
MyRobot: TCyborg;
begin
MyRobot := TCyborg.Create;
MyRobot.ShowClassName;
FreeAndNil(MyRobot);
end;
As you can see by the above simple demo, adding a routine to an existing class using a class helper is very easy and you use the original class as you always have but now have access to the new routines you've added.
A Simple Delphi 2009 VCL Example
The following code adds a simple procedure to the VCL TEdit class. With class helpers, you do not have to create and use a new class descending from TCustomEdit (perhaps called TMyEdit) but instead you enhance TEdit directly and continue using it as you always have.
unit EditHelper;
interface
uses
StdCtrls, Dialogs;
type
TEditHelper = class helper for TEdit
procedure ShowClassName;
end;
implementation
procedure TEditHelper.ShowClassName;
begin
//Self refers to the helped class.
//Self here refers to TEdit.
ShowMessage(Self.ClassName);
end;
end.
Now, in a form with a TEdit control named Edit1 and a button, you can make use of our newly added TEdit functionality. For example, alter the click event of a button as follows:
procedure TForm1.Button3Click(Sender: TObject);
begin
Edit1.ShowClassName;
end;
Now that's powerful and, when done right, it's easy to implement, maintain, and extend.
Add Properties too!
You can add properties to an existing class using a class helper too. You cannot add a new member field so your new property must make use of existing member fields.
For my last example, we will enhance our Cyborg class. Currently the Cyborg class uses a public member field named FCyborgName to store the cyborg's name. This really should be a private member field accessed through a property. Our first choice is to rewrite the class and correct the less than elegant code. However, let's assume we do not own the code so a class helper is our only approach.
The following code adds a public property called CyborgName to our existing class:
//Alter class helper as follows:
CyborgHelper = class helper for TCyborg
strict private
function GetCyborgName: String;
procedure SetCyborgName(const pValue: String);
public
property CyborgName: String read GetCyborgName write SetCyborgName;
procedure ShowClassName;
end;
//Implement new getter and setter in interface section:
function TCyborgHelper.GetCyborgName: String;
begin
Result := FCyborgName;
end;
procedure TCyborgHelper.SetCyborgName(const pValue: String);
begin
FCyborgName := pValue;
end;
Now you can use either the public FCyborgName member field or our new CyborgName property. Not as elegant as rewriting the original class and correcting the problem, but this code does demonstrate adding a new property to an existing class.
procedure TForm1.Button5Click(Sender: TObject);
var
MyRobot: TCyborg;
begin
MyRobot := TCyborg.Create;
MyRobot.CyborgName := 'Cameron';
ShowMessage('Hi, my name is ' + MyRobot.CyborgName);
FreeAndNil(MyRobot);
end;