Friday, April 11, 2008

Class helpers vs Subclassing

In a Dutch Delphi forum I came across a posting that had some sample code using 'class helpers'. At first, I didn't know what it was. Later I realized I had seen that before, but somehow I forgot about those. Class helpers extend an existing class, without subclassing.

I tried to put down why I don't use class helpers, but do subclass frequently. I couldn't (and can't) remember. While writing all the pros for subclassing, I couldn't find and argument agains class helpers. Except of course, you can't override members. It took me a while to realize (again) why I don't help a class, but do subclass.

If anyone is reading this, what is your opinion about the subject? Are class helpers of *any* use?

Bye,
Bart

1 comment:

Jolyon Smith said...

They are a useful quick fix /disposable solution, but are simply not viable as part of a long-term solution to any problem.

The reason is quite simply that this is how they were designed to be used!

The biggest problem with them is that you can only have one class helper (for a given class) in scope at any time - if you have more than one then only the nearest (in scope) is actually visible.

This means that simply adding a unit to your uses clause can break your code!

i.e. if the unit you add contains a class helper that is nearer in scope than some other class helper you are already using.


That in turn stems from their other huge problem (from a code maintenance perspective) which is that it is impossible to determine from the use of an instance of a class, whether that usage requires a class helper (not to mention, if it does, which one and where it resides).

Impossible that is, without resorting to spelunking through the code (similar to "class fragments" where the problem is that you cannot know if your fragmented class is "complete" without spelunking for any other fragments that you might not be aware of).


Ironically I had been using a technique *very* similar to "class helpers" before they were formally added to the language.

That is, define some new class that derives from the required ancestor and adds the required functionality. This is simply a slightly more formal implementation of the similar technique used to bring protected members of a class into scope. This latter technique requires the "helper" to be declared in the unit in which it is to be used, but this more formal (and in this respect slightly more limited and therefore perhaps safer) "class extender" method does not.


TEditHelper = class(TEdit)
private
function get_AsInteger: Integer;
procedure set_AsInteger(const aValue: Integer);
public
property AsInteger: Integer read get_AsInteger write set_AsInteger;
end;


Declaratively this is pretty much identical to the way you would declare a "class helper for TEdit".

In usage of course, things are a lot different.

1) You have to cast

i := TEditHelper(Edit1).AsInteger;

Which neatly avoids the obscurity issue attendant with "proper" class helpers. i.e it is immediately apparent that the code is treating the Edit1 instance in a way that is not built-in to the Edit1 class itself.


2) You have to be explicit:

uses
EditHelper,
...;

:

i := TEditHelper(Edit1).AsInteger;

It doesn't matter if someone else comes up with "TEditSuperHelper", their doing so cannot interfere with your use of TEditHelper.



i.e. "class helpers" are merely a labour saving device. They allow you to cut corners when using instances of a class, i.e. removing the need to hard-cast.


Some might say that hard-casting to a type that an instance does not actually derive from is potentially dangerous. Possibly so, but then any direct cast is potentially dangerous. But at least the technique requires you to be up front and open when dancing with the devil in the pale moonlight.

In *my* code, if I see the use of a TXXXHelper", that is a double waved yellow... tread carefully in this area cos here be dragons.

In code that uses class helpers there are no such warning signs.

Just imho.