Blog
All Blog Posts | Next Post | Previous PostObject Pascal: Redefining Classes
Tuesday, April 3, 2018
The redefinition of classes is a practical method to minimize the collision of names between classes, even using short identifiers.
IntroductionThe Object Pascal language has a very useful feature which, I believe, is not widely used by developers.
I do not know if there is a specific name for it, but I named it "redefining classes".
In fact this feature can be used to redeclarate classes, constants, and even functions.
However, in object-orientation, we basically only use classes. So, forget the rest and let's focus on them.
Let's take a look at some techniques that can be applied using such a concept.
Renaming
Imagine that you want to use a class of some Lib but, this class has the same name example TSmartMemory
of one of your classes, which you already use throughout your code. What to do?
The first option is to never use both classes on the same unit. But maybe you're not that lucky.
The second option is to prefix one of the Classes with the unit name very common to see this in Java projects for example:
uses LibMemory, MyMemory; begin M := TSmartMemory.New; // your class L := LibMemory.TSmartMemory.Create; end
The third option, which I use most of the time, is "rename" the class of Lib to a nomenclature that does not collide with the nomenclature already used in my project. Let's take a look at an example:
unit MyMemory; uses LibMemory; type // TSmartMemory from LibMemory TLibStartMemory = TSmartMemory; // my new class TSmartMemory = class // ... end;
In the example above, both classes are declared in the same unit in the project the MyMemory
and the project can use both classes without nomenclature collision.
begin M := TSmartMemory.New; L := TLibSmartMemory.Create; end;
Using this technique we avoid name conflict, which is very useful.
Instead of using the real class name, we can give it an alias. The code stay cleaner, simple and using short identifiers, because we don't need to use the unit name as a prefix.
The C# language has something very similar which makes me wonder where the main architect of the C# language took out this idea.
VisibilityMany times we need to use a composition of different classes to solve a problem.
But, if each one of these classes has been declared into a different unit, we will need to declare all those units to get access for each one of those classes.
For example. We need of TClass1
, TClass2
, and TClass3
. Each one of them into different units, Unit1
, Unit2
e Unit3
, respectively.
unit MyUnit; uses Unit1, Unit2, Unit3; begin TClass3.New( TClass2.New( TClass1.New ) ) end;
If we need to use that composition in many places of the code, we will always have to remember which units these classes are in.
Another option is to make classes visible, redeclaring all of them in a single unit, for example Unit123
, so that we can use them in a simpler way but still maintaining each implementation in different units.
unit Unit123; uses Unit1, Unit2, Unit3; type TClass1 = Unit1.TClass1; TClass2 = Unit2.TClass2; TClass3 = Unit3.TClass3;
Now, just use Unit123
in the code to have access to all 3 classes that previously could only be accessed in different units.
unit UnitTest; uses Unit123; begin TClass3.New( TClass2.New( TClass1.New ) ) end;
This technique is very useful for simplifying an API, giving developers only a few classes for use in very specific contexts.
InheritanceThere are times when we want to use class inheritance even though Object Composition is the best choice however, we would like to use the same name of the ancestral class.
Let's imagine that we are developing a software and, into one of its units, has a class that represents a PDF file. We name this class as TPDFFile
.
However, we know there may be tens or hundreds of Libs already working with PDF. So, let's named one of them of PDFLib
, for example.
In PDFLib we have a class called TPDFFile
which is exactly the same name that we have already decided that will be used in our software, but the project architect says that our class should inherit from PDFLib.TPDFFile
.
I guess you already know the answer:
unit MyPDFUnit; uses PDFLib; type TPDFFile = class(PDFLib.TPDFFile) // more methods end;
By prefixing the class of Lib, we can identify it differently from our own class declared in the same unit.
The TPDFFile
class is now an extension of PDFLib.TPDFFile
which belongs to an external Lib. But, for all the rest of the code in the project, there will be only the MyPDFUnit.TPDFFile
class to represent a PDF.
The technique that I will show you now was already used even before to have a syntax for class helpers.
For example, imagine that you want to include new properties or methods in TEdit
class. The first thought is to use inheritance to create a new component. However, you do not want to replace each TEdit
in all TForm
that already exist in the project. So, what to do?
The answer is still to use inheritance. But there is a trick or hack that I will show in the technique below. But I would like to remind you that this should be used very sparingly. I only used this technique very seldom and only for classes that represent widgets, that is, classes of components that are used in Forms.
To extend a TEdit
without creating a new class and without having to change the Forms, just use the same technique above:
unit MyStdCtrls; type TEdit = class(StdCtrls.TEdit) // or Vcl.StdCtrls.TEdit // more methods and properties end;
You do not need to change widgets in Forms. The design rendering of the Form will work using the resources (* .lfm * .dfm) correctly.
There is one trick you can not forget: In each of these Forms you will need to declare your unit after of the actual StdCtrls
unit.
unit MyForm1; uses StdCtrls, MyStdCtrls;
This hack is required for the compiler to find the declaration of your unit before it is found in the default StdCtrls
.
That is why it is so important to have an order in the declaration of the units.
Optional NomenclatureThis technique can be considered the opposite of the first one shown in this article, ie., instead of you "renaming" a class belonging to another Lib, you will give to the developer some naming options for the use of their classes.
Imagine that you've coded a package with some classes. In this package you like to use simple names such as TStream
, TMemoryStream
, TString
, TInteger
, etc.
See that we already have some possible "problems" here. Let's see:
1. The TStream
and TMemoryStream
classes already exist in FPC or Delphi and, because that, there may be a collision of names if developers use your package, as it is very likely that they are already using such default classes names in their projects;
2. The TString
and TInteger
classes are classes with very simple or generic names and, again, it is likely either some other Lib or even the projects of these developers already use such names.
The problem here is the name clean and short. While these names are perfect, they have the great potential of generating some name collision problems.
But when you are creating your package, you have to abstract the external world. The context of the package that should influence the nomenclature of classes and not the external world that might use it!
I "fought" against this problem for many years. Before I "discovered" this technique, I prefixed my Classes with 1, 2 or 3 letters this seems to be the standard used by all component developers in the market however, you can find out over time that your "prefix" chosen for your classes have already been used in another Lib from third parties.
Imagine you have to use verbose names like TXyzMemoryStream
throughout your code to then find out that Xyz
is already a prefix used by a large "component maker" on the market.
Then, I discovered that the Object Pascal language already had an answer and I could have the best of both worlds. I could use simple, compact and clean names inside my context (package, project, lib) but giving to potential developers a more verbose, but with less possibility of collision of names, if so desired.
Let's look at an example: Imagine that you have a package with a class named TDataStream
. This name already avoids the collision with TStream
which is basically what this class represents, but the Data
prefix was not used to minimize the nomenclature collision, but rather because of its semantics.
But, let's say either Lazarus or Delphi have now implemented a general purpose class called... TDataStream
.
Should I change the name of this class in all my projects that already use this nomenclature? Of course not!
I would only give the option for (new) developers to use another nomenclature for the same class, like this:
type TDataStream = class sealed(TInterfacedObject, IDataStream) // some methods end; TSuperDataStream = TDataStream;
The code now has 2 naming possibilities for the same class!
You can either continue to use TDataStream
throughout your code, as long as you hold the declaration order of the units, or you can use the new TSuperDataStream
option.
And, even if there is still a collision of nomenclature, it will be up to the developer to use the first technique of this article to solve it.
This way, both sides (package developers and package users) are free to use whatever names they want in their classes.
The only exception, as far as I know, are the components that are installed in the IDE. Both IDEs (Lazarus and Delphi) do not allow install components with the same name, even if they are in different packages.
ConclusionThe redeclaration or renaming of classes (and constants, functions, etc) is a technique that exists almost from the beginning of the language, but which the vast majority of developers do not use, either due to lack of knowledge, inability or lack of interest.
It is an old concept but it provides some techniques and possibilities for better encoding today, as demonstrated in this article.
See you.
Marcos Douglas B. Santos
This blog post has received 3 comments.
Marcos Douglas B. Santos
MILAN VYDARENY
All Blog Posts | Next Post | Previous Post
It''s good to be reminded of... Oh, there were a lot of things here: How good the OP/D language has always been; how useful clever "hacks" like this can be; how one tends to forget — or maybe never even realise — these in hindsight blindingly obvious techniques; the importance of "housekeeping", like the order of units in "uses" statements... Lots of great things.
Christian R. Conrad