2018年6月21日 星期四

Generic in Delphi

Some History about Delphi 

Delphi has existed for about 23-24 years, in early version (Delphi 1-Delphi 7), Win32 is the only target Delphi shoots for. In year 2000-2003, Microsoft take .Net and C# as next star, Delphi also tried to get ahead, but failed in Delphi 8.

And in the next 10 years, Delphi 2005, 2006, 2007, CodeGear was spinned off from Borland, they tried to shoot for software lifetime management, so we had starteam boundled in these versions.

However, Windows didn’t get real improvement in those years, the only milestones we can see, could be multi-user, 64bit support, and UAC (User Access Control). And Delphi keep silent for years.

Delphi 2009 is a keystone for next couple versions, we have native Unicode support (RTL & components), Generic support, new TTask supports multi-core execution. These new features are great, and makes applications get remarkable performance improvement.

And Delphi XE series help programmers crossing to iOS, MacOSX, Android, and 64bit Linux server in “one code base”, with different compile configures, that’s awesome.

It’s a pity that the wonderful features are seldom used, it might because the sample codes are not enough in quantity or quality. So, let’s have a simple code sample to show you how to use “Generic” with Delphi.

Gernic? Template? What The Hell?

C++ is the very first language which makes object oriented concept well-defined. (C++ was defined earlier then JAVA, so we don’t discuss about the differences between C++ and JAVA in this article)

C++ defines multiple inheritance, Template, and several important concepts, it’s very powerful, and too powerful to implement in common codes.

Delphi takes object pascal as core language, and object pascal evolved for several times in paste decades with Delphi. Object pascal is single root concept, which means all classes in object pascal inherited from the same root class named “TObject”, no exceptions.

If we wish make a class with 2 classes, we need to implement the features with “interface”, and with well-defined interfaces, multiple features of specific classes can be adopted, or we can say to be implemented. Inheritance and interface are another long story, I will write another article to introduce.

What we say “Generic” or “Template”, is to replace specific type name with the keyword “<T>“ in the codes, with this modification, we can reduce the effort to adopt the class for other types.

If you do understand the above sentence, congratulations! You must be an experienced programmer, and you don’t need help from this article.

So, let’s speak English, with some sample codes, you might be understand the concept faster.

When I was a college student, once I was doing my homework from data structure class, or once I was trying to implement a "Stack" with Object Pascal, I will have the following thoughts:

  • Well, Stack, first in last out, or last in first out, so I need an array to store elements.
  • I have to define a push method to put data in.
  • I have to define a pop method to get data out.
 So, the following figure will be helpful to visualize the stack:




As a "stack for storing integer", the basic declaration will look like:

TMyStack = class (TObject)
private
   FElements: array[0..5] of Integer; // Array for storing integer.
public
   function push(element: integer) : integer; // the returned integer will indicate the
                                                                     // position of the pushed element.
   function pop: integer; // return last element, and remove it.

   constructor Create(); ovevrride; reintroduce;
   destructor Destory(); override;
end;

I skip the implementation codes, because there are more students searching the codes for their homework in recent years.

A stack class named "TMyStack" is declared in the above codes, but there are still a lot of problems in the above codes. For example, the above class can store only 6 integers, the number of element is limited, and as I mentioned, TMyStack is a "stack for integer", so the argument of push method must be "integer", and return type of pop method must be integer, too.

Let's resolve the limitation of element count first.

I don't remember which version of Delphi exactly, Delphi provide a feature named "variable length of array", we can change the length of an array with setLength function, the declaration of a variable length of array might like this:

var
   varLengIntArray : array of Integer;

and we can change the length of the array in run-time with the following codes:

setLength(varLengIntArray, 20);

The second argument "20", is the length we wish to make the array to be.


With this implementation, we can set the stack class free from the limitation of count of element:
TMyStack = class (TObject)
private
   FElements: array of Integer; // The array for storing elements.
   FElementCount: integer;
public
   function push(element: integer) : integer; // the returned integer will indicate the
                                                                     // position of the pushed element.
   function pop: integer; // return last element, and remove it.

   property count: integer; read FElementCount;

   constructor Create(); ovevrride; reintroduce;
   destructor Destory(); override;
end;

With the modification, push and pop methods will require some changes, constructor Create will require to initial the count:
==================================================================
constructor TMyStack.Create();
begin
   inherited Create();

  FElementCount := 0; // initialization, set the count of element as 0;
  setLength(FElements, 0); // initialization, set the element array as empty.
end;

function TMyStack.push(element: integer) : integer;
begin
    Inc(self.FElementCount); // Increasing the count of element by 1
    setLength(FElements, self.FElementCount); // Increasing the count of array by 1.

   self.FElements[self.FElementCount -1] := element; // saving the element in the
                                                                                    //  added position.
end;

function TMyStack.pop: integer;
begin
    Result := self.FElements[self.FElementCount -1]; // Returning the last element.

    Dec(self.FElementCount); // Decreasing the count by 1
    setLength(FElements, self.FElementCount); // Removing the last element of array.
end;
==================================================================

With the modification, the stack class was set free from limitation of count.

But, will we need a stack for integer only? Will we need a stack for string tomorrow? or will we need a stack for customized record or class?

If every time when we need a stack for particular type, we need to copy & paste the above codes, and then modify the all the "type" field, it will kill me, am I right?

What if customers require some "outstanding" features for particular types, we will need to modify all the stack classes one by one....... That will drive me crazy in second.

So, is there someway, we can make a stack for all type?

Dr. Alfred Lanning: *That*, Detective, is the right question. -- quoted from "I, robot".

"Generic" is the redemption.

For using the features of generic provided by Delphi, we need to use the "System.Generics.Collections" unit, there are so many types, functions ready for use.

First, let's modify the previous declaration, replace "array of integer" with "TArray<T>", hence, we can save any type of data in TMyStack class.

TMyStack<T> = class (TObject)
private
   FElements: TArray<T>; // New array type with element of all types.
   FElementCount: integer;
public
   function push(element: T) : integer; // the returned integer will indicate the
                                                                     // position of the pushed element.

   function pop: T; // return last element, and remove it.

   property count: integer; read FElementCount;

   constructor Create(); ovevrride; reintroduce;
   destructor Destory(); override;
end;


And the implementation should be modified as:
==================================================================
constructor TMyStack<T>.Create();
begin
   inherited Create();

  FElementCount := 0; // initialization, set the count of element as 0;
  setLength(FElements, 0); // initialization, set the element array as empty.
end;

function TMyStack<T>.push(element: T) : integer;
begin
    Inc(self.FElementCount); // Increasing the count of element by 1
    setLength(FElements, self.FElementCount); // Increasing the count of array by 1.

   self.FElements[self.FElementCount -1] := element; // saving the element in the
                                                                                    //  added position.

end;

function TMyStack<T>.pop: T;
begin
    Result := self.FElements[self.FElementCount -1]; // Returning the last element.

    Dec(self.FElementCount); // Decreasing the count by 1
    setLength(FElements, self.FElementCount); // Removing the last element of array.
end;
==================================================================

Oh my Goodness, that's all? Is that simple? And then? How can I use the class?
Just as this way:

var
   integerStack : TMyStack<Integer>;
begin
    integerStack := TMyStack<Integer>.Create;
    try
        integerStack.push(79);
        integerStack.push(7);
        integerStack.push(21);
        integerStack.push(13); 
    finally
         integerStack.Free;
    end;
end; 

In the above code, a stack will be built as the following figure:





And we can create a stack for saving strings:

var
   stringStack : TMyStack<String>;
begin
    stringStack := TMyStack<String>.Create;
    try
        stringStack.push('Woo');
        stringStack.push(Th');
        stringStack.push('at');
        stringStack.push('is'); 
        stringStack.push('sta');  
        stringStack.push('ck');  


        // p.s. I create the figure first, but the space is not long enough, so
        // I split the string into many pieces. 
    finally
         stringStack.Free;
    end;
end;



The built stack looks like the following figure:

With the above codes, the class implementation is not changed. What we do is to declare, create the instance of TMyStack<T>, then the class can be adopted with different data types, it's convenient, right?

There is a class named TList<T> in System.Generics.Collections. We have to create TObjectList to store any object derived from TObject, and we have to add additional codes to mapping different object instance.

With TList<T>, we can save the extended codes, works. In System.Generics.Collections, there are even TStack<T>, TQueue<T>, are you very interested in that unit now?

With a simple conclusion, Generic is to replace the data type with keyword <T> when we try to have a class implementation, and make it clear when we use the class in real execution codes. In this way, we will save so many time, and we, developers, will have extra time for our life. I HOPE SO.....

沒有留言:

張貼留言