tags:

views:

1001

answers:

8

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?

+3  A: 

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.
Gamecat
Have you read the 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."
mjustin
+4  A: 

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.

Jon Skeet
As a side note, Delphi has had class helpers since 2000, and we hold the patent on the notion. Nick HodgesDelphi Product ManagerEmbarcadero Technologies
Nick Hodges
@Nick: Which version of Delphi are you talking about, that had class helpers since 2000? I only remember reading about them when they were used in Delphi 2007 to make a non-breaking release. That was 6 years after the year you claim that Delphi had them.
dummzeuch
Class helpers were added to Delphi in Delphi 8 (the first version of Delphi for .NET).
Cary Jensen
+11  A: 

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.

gabr
+2  A: 

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.

Robert Walker
+2  A: 

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;
Uwe Raabe
Wow, overriding 'default'! Incredible. And possibly a great source of weird errors :)
gabr
Agreed, that is a little bit off. But I use class helpers mostly in tight scope, so this has not been an issue - up to now...
Uwe Raabe
+3  A: 

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.

Mason Wheeler
A: 

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.

Jamo
A: 

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

mjustin
I have read that comment. But that's no reason not to use them. Just use them carefully like any other tool.
Gamecat