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

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.

Related

How to affect descendants of a modified parent class in Delphi?

Let's say I have two classes in Unit1: TParent = class(TCustomControl) and TDescendant = class(TParent) and they both have a lot of methods, fields and properties.
In Unit2 I need to modify TParent. Let's say I only need to modify a single method, Method1, in a very simple way that doesn't really affect anything outside itself (say, print a text in red color instead of black). I also need TDescendant to be affected by this modification (so every time it calls Method1 the modified Method1 is executed).
Is there a way to do that in Delphi 7, without modifying Unit1 and without copying the entire TDescendant class into Unit2?
On top of that I really need the class names to remain the same (I'm simply trying to modify a method in a 3rd party control without creating a whole new control to do it).
I made an interceptor class of TParent in Unit2, but I don't know how (if at all possible) to "tell" TDescendant to become a descendant of the interceptor class instead of the original.
As far as I know this is not possible without some serious low level hacks (pointer/memory based). Therefor I would not suggest is at all... (also I don't know the actual answer to that solution).
When you only want to add some extra methods to the TParent class, you can use helper classes (Delphi XE+ - maybe even earlier versions).
Ex.
TParentHelper = class helper for TParent
public
procedure MyMethod(param: string);
end;
Now you can dos stuff like:
uses
MyParentHelperU;
Procedure test(D: TDescendant);
begin
D.MyMethod('test');
end;
Notes:
you need to include the unit in which the helper class in defined, in order to use this functionality.
You can only add methods to the class (and descendants), for which the helper class has been assigned. You cannot add properties or fields.
Protected properties can be accessed by the helperclass, but are not always shown by the IDE

Class Reference as Property

Google is useless for these sorts of searches, because you get hundreds of millions of results absolutely none of which relate to the specific question.
The question is simply this:
Is it possible to have a Class Reference Property in Delphi?
If so, how?
Here's what I've tried...
type
TMyObject = class
// ...
end;
TMyObjectClass = class of TMyObject
TMyObjectA = class(TMyObject)
// specifics here
end;
TMyObjectB =class(TMyObject)
// specifics here
end;
TMyComponent = class(TComponent)
private
FObjectType: TMyObjectClass;
published
property ObjectType: TMyObjectClass read FObjectType write FObjectType;
end;
The above code compiles fine, however the Object Inspector does not show the ObjectType property at all.
My objective here (if you haven't already guessed) is to make it so that I can select a class descendant from a specific base class, to make the same component behave in a different way.
I want to do it this way so that the component doesn't need to know about the sub-classes directly (it needs to be fully modular).
Let me just make this bit clear: I cannot use an Enum to choose between the sub-class types as the component cannot directly link to the sub-class types (It's simply not possible in this particular case)
Anyway... thanks in advance!
You can find all classes that descend from a particular base class: Delphi: At runtime find classes that descend from a given base class? and make this a special property with list of values using TPropertyEditor.
If you were going to do this then you would need to provide a property editor. The IDE does not come with property editors for class type properties. You would also need to handle .dfm persistence. You would write the class type out to the .dfm file as a string and when the .dfm file is read, you would need to fixup the reference. New style RTTI could do that.
However, I don't think any of this is actually viable for the following reason. Your design time code runs in a package inside the IDE and does not have access to the class types in the active project in the IDE. Those class types only exist when that project runs. So the ObjectType property in the code in your question cannot be assigned to anything meaningful in the design time package. Well, you could use it for classes defined in the VCL and any other packages installed in your IDE but I rather imagine you'd want to use it on classes defined in the active project.
I think all this means that you should instead use a simple string property and fixup the class type references only at runtime.

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.

Class Helper for generic class?

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.

Resources