Class Helper for generic class? - delphi

I'm using Delphi 2009. Is it possible to write a class helper for a generic class, i.e. for TQueue . The obvious
TQueueHelper <T> = class helper of TQueue <T>
...
end;
does not work, nor does
TQueueHelper = class helper of TQueue
...
end;

As documented in the Delphi help, class helpers are not designed for general purpose use and they are incorrectly perceived as having a number of limitations or even bugs as a result.
nevertheless there is a perception - incorrect and dangerous in my view - that these are a legitimate tool in the general purpose "toolkit". I have blogged about why this is wrong and subsequently about how you can go some way to mitigate the dangers by following a socially responsible coding pattern (although even this isn't bullet proof).
You can achieve much the effect of a class helper without any of these bugs or limitations or (most importantly) risks by using a hard cast to a "pseudo" class derived from the class you are trying to extend. i.e instead of:
TFooHelper = class helper for TFoo
procedure MyHelperMethod;
end;
use
TFooHelper = class(TFoo)
procedure MyHelperMethod;
end;
Just like with a "formal" helper, you never instantiate this TFooHelper class, you use it solely to mutate the TFoo class, except in this case you have to be explicit. In your code when you need to use some instance of a TFoo using your "helper" methods you then have to hard cast:
TFooHelper(someFoo).MyHelperMethod;
Downsides:
you have to stick to the same rules that apply to helpers - no member data etc (not really a downside at all, except that the compiler won't "remind you").
you have to explicitly cast to use your helper
If using a helper to expose protected members you have to declare the helper in the same unit that you use it (unless you expose a public method which exposes the required protected members)
Advantages:
Absolutely NO risk that your helper will break if you start using some other code that "helps" the same base class
The explicit typecasting makes it clear in your "consumer" code that you are working with the class in a way that is not directly supported by the class itself, rather than fudging and hiding that fact behind some syntactic sugar.
It's not as "clean" as a class helper, but in this case the "cleaner" approach is actually just sweeping the mess under the rug and if someone disturbs the rug you end up with a bigger mess than you started with.

I currently still use Delphi 2009 so I thought I'd add a few other ways to extend a generic class. These should work equally well in newer versions of Delphi. Let's see what it would look like to add a ToArray method to a List class.
Interceptor Classes
Interceptor classes are classes that are given the same name as the class they inherit from:
TList<T> = class(Generics.Collections.TList<T>)
public
type
TDynArray = array of T;
function ToArray: TDynArray;
end;
function TList<T>.ToArray: TDynArray;
var
I: Integer;
begin
SetLength(Result, self.Count);
for I := 0 to Self.Count - 1 do
begin
Result[I] := Self[I];
end;
end;
Notice you need to use the fully qualified name, Generics.Collections.TList<T> as the ancestor. Otherwise you'll get E2086 Type '%s' is not completely defined.
The advantage of this technique is that your extensions are mostly transparent. You can use instances of the new TList anywhere the original was used.
There are two disadvantages to this technique:
It can cause confusion for other developers if they aren't aware that you've redefined a familiar class.
It can't be used on a sealed class.
The confusion can be mitigated by careful unit naming and avoiding use of the "original" class in the same place as your interceptor class. Sealed classes aren't much of a problem in the rtl/vcl classes supplied by Embarcadero. I only found two sealed classed in the entire source tree: TGCHandleList(only used in the now defunct Delphi.NET) and TCharacter. You may run into issues with third party libraries though.
The Decorator Pattern
The decorator pattern lets you extend a class dynamically by wrapping it with another class that inherits its public interface:
TArrayDecorator<T> = class abstract(TList<T>)
public
type
TDynArray = array of T;
function ToArray: TDynArray; virtual; abstract;
end;
TArrayList<T> = class(TArrayDecorator<T>)
private
FList: TList<T>;
public
constructor Create(List: TList<T>);
function ToArray: TListDecorator<T>.TDynArray; override;
end;
function TMyList<T>.ToArray: TListDecorator<T>.TDynArray;
var
I: Integer;
begin
SetLength(Result, self.Count);
for I := 0 to Self.Count - 1 do
begin
Result[I] := FList[I];
end;
end;
Once again there are advantages and disadvantages.
Advantages
You can defer introducing the new functionally until its actually needed. Need to dump a list to an array? Construct a new TArrayList passing any TList or a descendant as a parameter in the constructor. When you're done just discard the TArrayList.
You can create additional decorators that add more functionality and combine decorators in different ways. You can even use it to simulate multiple inheritance, though interfaces are still easier.
Disadvantages
It's a little more complex to understand.
Applying multiple decorators to an object can result in verbose constructor chains.
As with interceptors you can't extend a sealed class.
Side Note
So it seems that if you want to make a class nearly impossible to extend make it a sealed generic class. Then class helpers can't touch it and it can't be inherited from. About the only option left is wrapping it.

As near as I can tell, there's no way to put a class helper on a generic class and have it compile. You ought to report that to QC as a bug.

Related

SW-Design: Adapters for Class Hierarchy in Delphi (Generics vs. Downcast)

I would like to have some suggestions for the following problem:
Let's say you want to write adapters for the VCL controls. All Adapters should have the same base class, but differ in wrapping special controls (for example getting a value from a TEdit is different from getting a value from TSpinEdit).
So the first idea is to create a class hierarchy like
TAdapter = class
end;
TEditAdapter = class (TAdapter)
end;
TSpinEditAdapter = class (TAdapter)
end;
Now I want to introduce a field to hold a reference to the vcl control. In my special adapaters I want - of course - work with the concrete subclass. But the Base class should also contain a reference (for example if I want to use the adapter to make a control visible).
Possibility 1 (Downcast in Property Accessor):
TAdapter = class
protected
FCtrl : TControl;
end;
TEditAdapter = class (TAdapter)
public
property Control : TEdit read GetControl write Setcontrol;
end;
{...}
function TEditAdapter.GetControl : TEdit;
begin
Result := FCtrl as TEdit;
end;
So if I implement a specific method I work with the property Control, if I do something in my base class I use the protected field.
Possibility 2 (Use a generic base class):
TAdapter = class
end;
TAdapter <T : TControl> = class (TAdapter)
protected
FCtrl : T;
end;
TEditAdapter = class (TAdapter <TEdit>)
end;
Which solution would you prefer? Or is there a third solution, which is even better?
Kind regards,
Christian
You can't use generics to solve this problem, because you'll be in one of two situations:
The property or method you want to "Adapt" (the Text property for example) is defined in an ancestor class. In that case you don't need generics because you can use the one adapter for the ancestor and solve the problem for all descendants.
The property or method is introduced by the class you want to adapt. In that case you can't use generics because in order to access the property or method you'd need a generic type constraint of that given type. Example. Let's say you want an adapter for the Text property of TMyClass. Let's assume TMyClass is the one introducing the Text property. In order to access it, you'd need to declare the generic type as TGeneric<T:TMyClass> and that's not actually generic.
In my opinion the best bet is to write specific adapters for each class, as in your first option. You might be able to use RTTI tricks to make your first option easier to implement, but I'm not sure it'd be worth it.
The generics version could allow to avoid some duplicated code, at least in the TAdapter class. By using the T type, it will allow a lot of shared code.
On the other hand, due to the VCL hierarchy, most used properties and methods will already be in TControl. So I'm not sure there will be so many duplicated code in non-generic implementation.
I suspect the non-generic version will produce less code and RTTI, since the current generics implementation tends not to duplicate the source, but increase the exe size.
IMHO the generic-based design will add more abstraction to the implementation, but the non-generic would perhaps be more close to the VCL hierarchy it will adapt on.
So for your particular case (mapping the VCL), since your attempt is to map non generic classes, I'd rather investigate into the non-generic solution.
For another (non VCL-based) adapter architecture, I would probably have advised a pure generic implementation, from the bottom up.

Class constructor not called when class registration is done in that class constructor

I am writing a simple dependency injection / inversion of control system based on a TDictionary holding abstract class references with their respective implementor classes.
My goals are:
Avoid direct instantiation by type (obviously).
Inclusion of a class' unit in the dpr should be enough to have it registered and be available for selection and instantiation through the di/ioc system.
Declare concrete implementing classes in implementation section only.
Use class constructors instead of initialization sections.
Btw, I am aware that using class constructors to take advantage of smart linking and wanting the inclusion of a unit to be enough to make a class available are defeating each other. I want to use class constructors instead of initialization sections for other reasons as well. And I would like to keep all class initialization/registration code together instead of having to split it between the class constructor and initialization section.
Problem
I want the registration of the class into the factory to be in the class constructor. Unfortunately, the compiler doesn't think the class is "touched" by just using its type in its own class constructor.
When I put the registration function in the initialization section, then the compiler does think the class is touched and calls the class constructor. But that defeats the object of my exercise of keeping all class initialization code in the class constructor.
Two questions
Should the compiler consider the use of the class in its own class constructor "touching the class" or is that too much to expect the compiler to do?
Does anybody have any clever ideas on how I can still achieve my goals without using the initialization section?
Example
The abstract classes used in the application:
TSite = class abstract (TObject)
function GetURL: string; virtual; abstract;
property URL: string read GetURL;
end;
TSites = class (TList<TSite>);
TThisApplication = class abstract (TObject)
function Sites: TSites; virtual; abstract;
end;
The concrete implementing class (declared in the implementation section!) for TThisApplication
TThisApplicationConcrete = class(TThisApplication)
class constructor ClassCreate;
strict private
FSites: TSites;
function Sites: TSites; override;
end;
class constructor TThisApplicationConcrete.ClassCreate;
begin
RegisterImplementorClass(TThisApplication, TThisApplicationConcrete);
end;
function TThisApplicationConcrete.Sites: TSites;
var
SiteList: TSites;
begin
if not Assigned(FSites) then begin
SiteList := TSites.Create; // Change to use factory
//RetrieveSites(SiteList);
FSites := SiteList;
end;
Result := FSites;
end;
The function to get an instance of TThisApplication:
function ThisApplication: TThisApplication;
var
ImplementorClass: TClass;
begin
ImplementorClass := GetImplementorClass(TThisApplication);
if Assigned(ImplementorClass) then begin
Result := ImplementorClass.Create as TThisApplication;
end else begin
Result := nil;
end;
end;
This is currently coded in a separate function, but it w/could be moved to the factory.
Full example code
If anybody would like to experiment, I have the full code of my test projects available at : http://www.bjsoftware.com/delphistuff/stackoverdlow/classconstructors.zip
Zip contents:
4 projects all using the same source files, differing only in conditional defines (that's why the dproj's are also included)
4 source files
groupproj and its dsk with all 4 projects
RunTestApps.cmd to run all 4 projects
Results.txt with the output of my run of the RunTestApps.cmd
WriteUp.txt with the text of this question
Please bear in mind that at all times you need to do a "Build All Projecs" because all dcu's and exe's are going to the source dir and otherwise you are going to face a lot of errors and/or confusion because the exe isn't doing what its name indicates.
This is as expected. As Uwe pointed out, a self-referential class constructor isn't enough to trigger inclusion. Placing the reference in the initialization section will do the trick since that is outside the class itself. Trying to self-reference a class for inclusion is akin to trying pull yourself out of a deep hole by pulling on your own suspenders.
As long as you don't use the class anywhere outside the class itself, the compiler takes this as the class not used at all and hence won't call the class constructor. So I guess you have to use the intialization section instead of tweaking the code to fool the compiler. Take the pragmatic approach instead of the dogmatic one. It will be more readable anyway.
I think you expect the class constructor to work differently than it has been designed to. Class constructor don't get called "just before" the first create. (Not in WIN32 delphi at least). If a class is referenced, its class constructor will run before the unit's initialization code.
If the only reference that you have to your class happens to be inside said class, then no code actually linked into your application refer to said class. Therefore, its class constructor will never get called.
I believe register functions belongs to the initialization section. Having the register function there will force the class constructor to also be executed. I don't see why you would want/require the register code to be placed into the class constructor.
If you want more information about the way class constructor/destructor works, you can read this pretty good article by Allen Bauer:
http://blogs.embarcadero.com/abauer/2009/09/04/38899

Correct way to duplicate Delphi object

What are pros and cons of duplication an object instance with constructor or instance function?
Example A:
type
TMyObject = class
strict private
FField: integer;
public
constructor Create(srcObj: TMyObject); overload;
//alternatively:
//constructor CreateFrom(srcObj: TMyObject);
property Field: integer read FField;
end;
constructor TMyObject.Create(srcObj: TMyObject);
begin
inherited Create;
FField := srcObj.Field;
end;
Example B:
type
TMyObject = class
strict private
FField: integer;
public
function Clone: TMyObject;
property Field: integer read FField;
end;
function TMyObject.Clone: TMyObject;
begin
Result := TMyObject.Create;
Result.FField := FField;
end;
One major difference immediately springs to mind - in the latter case the Create constructor would have to be virtual so that a class hierarchy supporting Clone could be built basing on the TMyObject.
Assume that this is not a problem - that TMyObject and everything based on it is entirely under my control. What is your preferred way of doing copy constructor in Delphi? Which version do you find more readable? When would you use former or latter approach? Discuss. :)
EDIT:
My main concern with the first example is that the usage is very heavy compared to the second approach, i.e.
newObj := TMyObject.Create(oldObj)
vs.
newObj := oldObj.Clone;
EDIT2 or "Why I want single-line operation"
I agree that Assign is a reasonable approach in most cases. It's even reasonable to implement 'copy constructor' internally by simply using assign.
I'm usually creating such copies when multithreading and passing objects through the message queue. If object creation is fast, I usually pass a copy of the original object because that really simplifies the issues of object ownership.
IOW, I prefer to write
Send(TMyObject.Create(obj));
or
Send(obj.Clone);
to
newObj := TMyObject.Create;
newObj.Assign(obj);
Send(newObj);
The first adds information about which object to want to create, the second not. This can be used to instantiate e.g. a descendant or an ancestor of a class
The Delphi way (TPersistent) separates creation and cloning:
dest := TSomeClass.Create;
dest.Assign(source);
and has this same property that you explicitly choose the class to instantiate. But you don't need two constructors, one for normal use, and one where you want to clone.
edit due to oneline requirement
You can mix it of course using Delphi metaclasses (untested)
type
TBaseSomeObject = class;
TBaseObjectClass = class of TBaseSomeObject;
TBaseSomeObject = class(TPersistent)
function Clone(t: TBaseObjectClass = nil): TBaseSomeObject; virtual;
end;
...
function TBaseSomeObject.Clone(t: TBaseObjectClass = nil): TBaseSomeObject;
begin
if Assigned(t) then
Result := t.Create
else
Result := TBaseObjectClass(Self.ClassType).Create;
Result.Assign(Self);
end;
SendObject(obj.Clone); // full clone.
SendObject(obj.Clone(TDescandantObject)); // Cloned into Descendant object
For the rest, just implement your assign() operators, and you can mix multiple ways.
edit2
I replaced the code above with code tested in D2009. There are some dependencies of the types that might have confused you, hope it is clearer this way. Of course you'll have to study the assign mechanism. I also tested the metaclass=nil default parameter and it works, so I added it.
I don't think there is a correct way it just depend on personal style. (And as Marco pointed out, there are more ways.)
The constructor way is short but it violates the principle that the constructor must only construct the object. Which is possibly not a problem.
The clone way is short although you need to provide a call for each class.
The assign way is more Delphi like. It separates creation and initialization which is good because we like the one method one function concept that makes code better to maintain.
And if you implement Assign using streams, you have only one place to worry about which fields need to be available.
I like the clone style - but only in Java (or any other GC language). I used it some times in Delphi, but mostly I stay with Create and Assign, because it is much clearer who is responsible for the destruction of the object.
I use the second method, the one with the Clone function, and it works like a charm, even with complex classes. I find it more readable and error proof.

Can a Delphi generic class descend from its class argument?

I've been trying to define a generic, inheritable TSingleton class. Here's what I had in progress:
TSingleton<RealClass, InheritsFrom : class> = class(InheritsFrom)
strict private
class var FInstance : RealClass;
protected
procedure InstanceInitialization;virtual;
public
destructor Destroy; override;
class procedure Create; reintroduce;
class function Instance : RealClass;
class procedure InstanceFree;
end;
The goal was to be able to "insert" the singleton pattern in an inheritance tree. so instead of declaring something like this :
TMySingletonComponent = class(TComponent)
end;
And need to implement the singleton pattern there, I would declare something like this :
TMyGenericSingletonComponent = class(TSingleton<TMyGenericSingletonComponent,TComponent>)
end;
Sadly, this won't work. I'm getting the following error(In D2010):
TSingleton<RealClass, InheritsFrom : class> = class(InheritsFrom) ///E2021 Class type required
Now I was wondering, would this work in Delphi XE? Is there some "clean hack" I could use to make this work in D2010? Is there some fundamental reasons why this can't work?
By design, you can't create a generic class which derives from one of its type arguments.
No, that won't work. You're trying to define a class in terms of itself. Whatever you put inside the parameters has to be fully defined already.
What do you want to obtain?
IMHO, singletons are evil. They were introduced because of bad OOP design of C++ (for access to input/output streams in console applications, as far as I remember). And they tend to be like hell to maintain.
You can always live without them. It's definitively not a "Delphi classical" way of programing, because Delphi doesn't suffer the C++ problems I mentioned.
Some Java project (ab)uses of singleton. Google for it, and you'll find out what I mean.
Use a property of a common class with a getter, initializing an instance if the corresponding field is still nil, or directly returning the field pointer to the instance if it was already created. You'll have the singleton feature, with good performance, nice code, good OOP practice (no "global" class), and the ability to run the class without any singleton feature, if you don't need this feature later (for testing purpose, for instance).

Delphi class references... aka metaclasses... when to use them

I've read the official documentation and I understand what class references are but I fail to see when and why they are best solution compared to alternatives.
The example cited in the documentation is TCollection which can be instantiated with any descendant of TCollectionItem. The justification for using a class reference is that it allows you to invoke class methods whose type is unknown at compile time (I assume this is the compile time of TCollection). I'm just not seeing how using TCollectionItemClass as an argument is superior to using TCollectionItem. TCollection would still be able to hold any descendant of TCollectionItem and still be able to invoke any method declared in TCollectionItem. Wouldn't it?
Compare this with a generic collection. TObjectList appears to offer much the same functionality as TCollection with the added benefit of type safety. You are freed from the requirement to inherit from TCollectionItem in order to store your object type and you can make a collection as type specific as you want. And if you need to access item's members from within the collection you can use generic constraints. Other than the fact that class references are available to programmers prior to Delphi 2009 is there any other compelling reason to use them over generic containers?
The other example given in the documentation is passing a class reference to a function that acts as an object factory. In this case a factory for creating objects of type TControl. Its not really apparent but I'm assuming the TControl factory is invoking the constructor of the descendant type passed to it rather than the constructor of TControl. If this is the case then I'm starting to see at least some reason for using class references.
So I guess what I'm really trying to understand is when and where class references are most appropriate and what do they buy a developer?
MetaClasses and "class procedures"
MetaClasses are all about "class procedures". Starting with a basic class:
type
TAlgorithm = class
public
class procedure DoSomething;virtual;
end;
Because DoSomething is a class procedure we can call it without an instance of TAlgorithm (it behaves like any other global procedure). We can do this:
TAlgorithm.DoSomething; // this is perfectly valid and doesn't require an instance of TAlgorithm
Once we've got this setup we might create some alternative algorithms, all sharing some bits and pieces of the base algorithm. Like this:
type
TAlgorithm = class
protected
class procedure DoSomethingThatAllDescendentsNeedToDo;
public
class procedure DoSomething;virtual;
end;
TAlgorithmA = class(TAlgorithm)
public
class procedure DoSomething;override;
end;
TAlgorithmB = class(TAlgorithm)
public
class procedure DoSomething;override;
end;
We've now got one base class and two descendent classes. The following code is perfectly valid because we declared the methods as "class" methods:
TAlgorithm.DoSomething;
TAlgorithmA.DoSomething;
TAlgorithmB.DoSomething;
Let's introduce the class of type:
type
TAlgorithmClass = class of TAlgorithm;
procedure Test;
var X:TAlgorithmClass; // This holds a reference to the CLASS, not a instance of the CLASS!
begin
X := TAlgorithm; // Valid because TAlgorithmClass is supposed to be a "class of TAlgorithm"
X := TAlgorithmA; // Also valid because TAlgorithmA is an TAlgorithm!
X := TAlgorithmB;
end;
TAlgorithmClass is a data type that can be used like any other data type, it can be stored in a variable, passed as a parameter to a function. In other words we can do this:
procedure CrunchSomeData(Algo:TAlgorithmClass);
begin
Algo.DoSomething;
end;
CrunchSomeData(TAlgorithmA);
In this example the procedure CrunchSomeData can use any variation of the algorithm as long as it's an descendant of TAlgorithm.
Here's an example of how this behavior may be used in a real-world application: Imagine a payroll-type application, where some numbers need to be calculated according to an algorithm that's defined by Law. It's conceivable this algorithm will change in time, because the Law is some times changed. Our application needs to calculate salaries for both the current year (using the up-to-date calculator) and for other years, using older versions of the algorithm. Here's how things might look like:
// Algorithm definition
TTaxDeductionCalculator = class
public
class function ComputeTaxDeduction(Something, SomeOtherThing, ThisOtherThing):Currency;virtual;
end;
// Algorithm "factory"
function GetTaxDeductionCalculator(Year:Integer):TTaxDeductionCalculator;
begin
case Year of
2001: Result := TTaxDeductionCalculator_2001;
2006: Result := TTaxDeductionCalculator_2006;
else Result := TTaxDeductionCalculator_2010;
end;
end;
// And we'd use it from some other complex algorithm
procedure Compute;
begin
Taxes := (NetSalary - GetTaxDeductionCalculator(Year).ComputeTaxDeduction(...)) * 0.16;
end;
Virtual Constructors
A Delphi Constructor works just like a "class function"; If we have a metaclass, and the metaclass knows about an virtual constructor, we're able to create instances of descendant types. This is used by TCollection's IDE Editor to create new items when you hit the "Add" button. All TCollection needs to get this working is a MetaClass for a TCollectionItem.
Yes a Collection would still be able to hold any descendant of TCollectionItem and to invoke methods on it. BUT, it wouldn't be able to instantiate a new instance of any descendant of a TCollectionItem. Calling TCollectionItem.Create constructs an instance of TCollectionItem, whereas
private
FItemClass: TCollectionItemClass;
...
function AddItem: TCollectionItem;
begin
Result := FItemClass.Create;
end;
would construct an instance of whatever class of TCollectionItem descendant is held in FItemClass.
I haven't done much with generic containers, but I think that given a choice, I would opt for the generic container. But in either case I'd still have to use a metaclass if I wanted to have the list instantiate and do whatever else needs to be done when an item is added in the container and I do not know the exact class beforehand.
For example an observable TObjectList descendant (or generic container) could have something like:
function AddItem(aClass: TItemClass): TItem;
begin
Result := Add(aClass.Create);
FObservers.Notify(Result, cnNew);
...
end;
I guess in short the advantage/benefit of metaclasses is any method/class having only knowledge of
type
TMyThing = class(TObject)
end;
TMyThingClass = class of TMyThing;
is able to construct instances of any descendant of TMyThing whereever they may have been declared.
Generics are very useful, and I agree that TObjectList<T> is (usually) more useful than TCollection. But class references are more useful for different scenarios. They're really part of a different paradigm. For example, class references can be useful when you have a virtual method that needs to be overridden. Virtual method overrides have to have the same signature as the original, so the Generics paradigm doesn't apply here.
One place where class references are used heavily is in form streaming. View a DFM as text sometime, and you'll see that every object is referred to by name and class. (And the name is optional, actually.) When the form reader reads the first line of an object definition, it gets the name of the class. It looks it up in a lookup table and retrieves a class reference, and uses that class reference to call that class's override of TComponent.Create(AOwner: TComponent) so it can instantiate the right kind of object, and then it starts applying the properties described in the DFM. This is the sort of thing that class references buy you, and it can't be done with generics.
I also would use a metaclass whenever I need to be able to make a factory that can construct not only one hard-coded class, but any class that inherits from my base class.
Metaclasses are not the term I am familiar with in Delphi circles however. I believe we call them class references, which has a less "magic" sounding name, so it's great that you put both common monikers in your question.
A concrete example of a place I have seen this used well is in the JVCL JvDocking components where a "docking style" component provides metaclass information to the base docking component set, so that when a user drags their mouse and docks a client form to the docking host form, the "tab host" and "conjoin host" forms that show grabber bars (similar in appearance to the title bar of a regular undocked window) can be of a user-defined plug-in class, that provides a customized appearance and customized runtime functionality, on a plug-in basis.
In some of my applications I have a mechanism that connects classes to forms capable of editing instances of one or more of that classes. I have a central list where those pairs are stored: a class refernce and a form class reference. Thus when I have an instance of a class I can lookup the corresponding form class, create a form from it and let it edit the instance.
Of course this could also be implemented by having a class method returning the appropriate form class, but that would require the form class to be known by the class. My approach makes a more modular system. The form must be aware of the class, but not the other way round. This can be a key point when you cannot change the classes.

Resources