Object Pascal: Redefining Classes

Bookmarks: 

Tuesday, April 03, 2018

Photo by Mpumelelo Macu on Unsplash

The redefinition of classes is a practical method to minimize the collision of names between classes, even using short identifiers.

Introduction

The 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.

Visibility

Many 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.

Inheritance

There 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.

Extending

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 Nomenclature

This 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.

Conclusion

The 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


Bookmarks: 

This blog post has received 3 comments.


1. Friday, April 06, 2018 at 3:50:24 PM

Great article, thanks!

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


2. Friday, April 20, 2018 at 1:54:17 PM

Thanks Christian!

Marcos Douglas B. Santos


3. Sunday, April 22, 2018 at 5:39:22 PM

I have found type aliasing to be especially useful when working with Live Bindings, binding objects and lists. Livebinding naming and notation is incredibly cumbersome. For example, in the CreateAdapter event, you must create something like TListBindSourceAdapter<TEmployee> and pass it back to the Adapter Bind Source. This becomes easier if you use an alias like TEmployeeLBS = TListBindSourceAdapter<TEmployee>. Besides eliminating the cumbersome generic notation you can improve on the horrifically bad naming used by Live Bindings.

MILAN VYDARENY




Add a new comment:
Author:
Email:
  You will receive a confirmation mail with a link to validate your comment, so please use a valid email address.
Comment:
 
Change Image
Fill in the characters from the image above:
 

All fields are required.
 




Previous  |  Next  |  Index