Painless vaccination of object thinking
Or as simple as possible about the basic principles of OOP in Lazarus and FreePascal
Part I
You can study OOP (object-oriented programming) in two ways: either read a hundred books that give a bare theory on the structure of classes and principles of inheritance, polymorphism, encapsulation, but don’t learn anything, or stop worrying and try to learn new techniques in practice having processed, for example, ready-made codes, but rather from scratch having made something simple but beautiful.
In all books devoted to Pascal, delphi and lazarus (I found as many as two about the latter), a very similar part is devoted to OOP. From these books, you can learn a lot about how much cooler OOP an outdated structural approach is, but you can’t get enough skills to put this into practice. Of course, any programmer using visual IDEs already uses OOP by default, since all components and structural elements of a visual application are objects, but it can be very difficult to transfer your own structures and abstractions to the OOP paradigm. To understand all the charm and appreciate the emerging prospects, I decided to make a small application, which eventually turned into a simple screensaver. At the same time I remembered the existence of trigonometry.
The application will draw on the screen in random places fifty polar roses with different characteristics: size, color, number of petals. Then rub them over and draw new ones, etc. Using the principles of structural programming, you can, of course, make an ordinary multidimensional array with a volume of 50 and save all unique characteristics in it. However, it is worth remembering that pascal implies strict typing of data, and therefore, an array cannot consist of their elements with different types. You can make an array of records (record), but what a little detail, from recording to class - one step. Here we will do it.
An important principle of OOP is encapsulation. This means that the class must hide within itself all the logic of its work. Our class, let's call it TPetal, has fields with different types of data that define unique characteristics (center coordinates, size, coefficients for the polar rose equation, color), and working methods. All other program elements should only call these methods, without delving into the details of their implementation. While the class should be able to draw themselves and erase. For starters, it’s enough:
{ TPetal }
TPetal = class
private
R, phi: double;
X, Y, CX, CY: integer;
Scale, RColor, PetalI: integer;
public
constructor Create (Xmax, Ymax: integer);
procedure Draw (Canvas: TCanvas; Erase: boolean = FALSE);
end;
The class constructor has two parameters - these are the borders of the canvas on which the drawing will take place. The constructor implementation is as follows:
constructor TPetal.Create(Xmax, Ymax: integer);
begin
inherited Create;
CX:=Random(Xmax);
CY:=Random(Ymax);
RColor:=1+Random($FFFFFF);
Scale:=2+Random(12);
PetalI:=2+Random(6);
end;
Any man-made class in Delphi / Lazarus is a direct or indirect descendant of the TObject class, and when creating objects of its class, you must invoke the constructor of the parent so that the object is correctly created and computer memory is allocated for it. Therefore, at the beginning of our constructor, we call the constructor of the parent. After that we randomly generate unique characteristics of our polar rose: the coordinates of the center, color, scale factor and a coefficient that determines the number of petals.
Next is the drawing method. As you can see, to draw or erase an object, I wrote the only method in which there is a second parameter and by default it has the value FALSE. This is due to the fact that drawing and deleting is the same operation, only the object is drawn in random color, and it is erased in black. When the method is called from the program without using the second parameter, the object is drawn, and when using the Erase parameter, the object is erased:
procedure TPetal.Draw(Canvas: TCanvas; Erase: boolean);
begin
phi:=0;
if Erase then RColor:=clBlack;
with Canvas do
while phi < 2*pi do
begin
R:=10*sin(PetalI*phi);
X:=CX+Trunc(Scale*R*cos(phi));
Y:=CY-Trunc(Scale*R*sin(phi));
Pixels[X,Y]:=RColor;
phi+=pi/1800;
end;
end;
To draw the petals, the polar rose function is used in the polar coordinate system :

where ρ determines the radial coordinate and φ the angular coordinate. α is a coefficient that determines the length of the petals. In our formula, the coefficient is immediately equal to 10 so that the roses do not turn out too small. The angular coordinate runs from 0 to 2π in order to capture all 360 degrees (while loop). And after obtaining the radial coordinate, we calculate the Cartesian: x and y to draw this point on the canvas (marvel once again how fast modern computers perform calculations: inside the method there is a long cycle in which trigonometric calculations; remember how “quickly” painted like that Zx-spectrum). The coefficient k in the formula (in the program - PetalI) determines the number of petals. So far, for simplicity, we use only integers, so all roses are hypotrochoid with non-overlapping petals.
So, our class is implemented and it has all the necessary skills. It's time to use. In the main module of the application, first of all, we need to declare an array of 50 objects of the TPetal class, then we throw Image and Timer on the form, stretch the image to the entire form (Client), and set the response time to 100 milliseconds on the timer. The timer method will be like this:
var
Form1: TForm1;
Marg: boolean;
Petals: array [0..49] of TPetal;
CurPetal: smallint;
//-----------------------------------------------------------------------------------
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if not Marg then
begin
Petals[CurPetal]:=TPetal.Create(img.Width,img.Height);
Petals[CurPetal].Draw(img.Canvas);
CurPetal+=1;
if CurPetal=50 then Marg:=TRUE;
end else begin
Petals[50-CurPetal].Draw(img.Canvas,TRUE);
Petals[50-CurPetal].Free;
CurPetal-=1;
if CurPetal=0 then Marg:=FALSE;
end;
img.Canvas.TextOut(10,10,IntToStr(CurPetal)+' ');
end;
As you can see, I used a couple of global variables: CurPetal - object counter, takes values from 0 to 50 and vice versa; Marg is a counter boundary signaling device, but everything should be very clear from the logic of the method.
If we used the paradigm of structural programming, then inside the timer handler we would have to independently initialize all the characteristics of a unique rose, draw it, and then erase it. The method would have grown and would not have been obvious. But now we have a class that does everything on its own - the class constructor immediately initializes all the characteristics, and the DrawPetal class method encapsulates all the computation and rendering logic, for which it just gets a pointer to the necessary object that has the Canvas property (and this is any form , and almost any component). The result is such a nice screensaver:
Exploring the next principle of OOP - inheritance, you can later create a descendant from the TPetal class, for example TOverlappingPetal, in which the polar rose will have overlapping petals. To do this (for universalization) in the ancestor class, you need to change the type of the PetalI field to a real number, and reload the constructor of the descendant so that this field can be initialized with a random fractional number according to the relevant rules.
I saved the project files in my bitbucket repository , and for each stage I created a separate branch. An example above can be found in the lesson1 branch.
Part II
Now I propose to do what we stopped at. So, we have the TPetal class, which can draw a polar rose with the number of petals from 3 to 16. However, all the objects we get with non-overlapping petals. Meanwhile, if you look at the plate below, we will see that there are more varieties of them. The shape is determined by a coefficient equal to n / d:

Spawn a descendant from the TPetal class:
{ TPetal }
TPetal = class
protected
R, phi, PetalI: double;
X, Y, CX, CY: integer;
Scale, RColor: integer;
public
constructor Create (Xmax, Ymax: integer);
procedure Draw (Canvas: TCanvas; Erase: boolean = FALSE); overload;
end;
{ TOverlappedPetal }
TOverlappedPetal = class (TPetal)
public
constructor Create (Xmax, Ymax: integer);
procedure Draw (Canvas: TCanvas; Erase: boolean = FALSE); overload;
end;
In the TOverlappedPetal class, we add our own constructor, which will act together with the ancestor's constructor, as well as the overloaded DrawPetal method (in fact, in the end we will do without it at all, but at the moment this is a good way to demonstrate method overloading in the class’s descendants). Here is the implementation:
{ TOverlappedPetal }
constructor TOverlappedPetal.Create(Xmax, Ymax: integer);
begin
inherited Create (Xmax,Ymax);
while PetalI=Round(PetalI) do
PetalI:=(1+Random(6))/(1+Random(6));
end;
procedure TOverlappedPetal.Draw(Canvas: TCanvas; Erase: boolean);
begin
phi:=0;
if Erase then RColor:=clBlack;
with Canvas do
while phi < 12*pi do
begin
R:=10*sin(PetalI*phi);
X:=CX+Trunc(Scale*R*cos(phi));
Y:=CY-Trunc(Scale*R*sin(phi));
Pixels[X,Y]:=RColor;
phi+=pi/1800;
end;
end;
You can see that the constructor of the TOverlappedPetal class uses the inherited method, but then changes the value of the PetalI field, which sets the coefficient that affects the shape of the rose. When calculating the field, we exclude integers so as not to duplicate the forms that the TPetal ancestor already had.
The files for this example can be found in the lesson2 branch in the repository.
Now, if you take a closer look, it will become clear that even though we are programmers with OO thinking, we are still short of labor programmers, since the implementation of the DrawPetal methods in the ancestor and descendant is almost identical, and this is the first by degree of called butthurt to any refactoring guru - a repeating

The difference in implementations is only in the coefficient multiplied by the number π (2 or 12). We take this coefficient into a separate field of the ancestor TPetal (field K), remove the unnecessary overload of the DrawPetal method now and get the following structure of our classes:
{ TPetal }
TPetal = class
protected
R, phi, PetalI: double;
X, Y, K, CX, CY: integer;
Scale, RColor: integer;
public
constructor Create (Xmax, Ymax: integer);
procedure Draw (Canvas: TCanvas; Erase: boolean = FALSE);
end;
{ TOverlappedPetal }
TOverlappedPetal = class (TPetal)
public
constructor Create (Xmax, Ymax: integer);
end;
Although the descendant of TOverlappedPetal now differs from the TPetal ancestor only in its constructor, we clearly and fully demonstrated all the principles of object-oriented programming:
- encapsulation (all the work of the class is inside itself, external communication with the class is only a couple of methods and passing the required minimum parameters);
- inheritance (we gave rise to a class of roses with overlapping petals from a class of roses with non-overlapping petals);
- minimally, polymorphism (we demonstrated an overload of methods - one name, but a different implementation, but I plan to demonstrate the real power of polymorphism later).
Here is the implementation of the classes as a result:
constructor TOverlappedPetal.Create(Xmax, Ymax: integer);
begin
inherited Create (Xmax,Ymax);
K:=12;
while PetalI=Round(PetalI) do
PetalI:=(1+Random(6))/(1+Random(6));
end;
{ TPetal }
constructor TPetal.Create(Xmax, Ymax: integer);
begin
inherited Create;
CX:=Random(Xmax);
CY:=Random(Ymax);
K:=2;
RColor:=1+Random($FFFFF0);
Scale:=2+Random(12);
PetalI:=2+Random(6);
end;
procedure TPetal.Draw(Canvas: TCanvas; Erase: boolean);
begin
phi:=0;
if Erase then RColor:=clBlack;
with Canvas do
while phi < K*pi do
begin
R:=10*sin(PetalI*phi);
X:=CX+Trunc(Scale*R*cos(phi));
Y:=CY-Trunc(Scale*R*sin(phi));
Pixels[X,Y]:=RColor;
phi+=pi/1800;
end;
end;
I used the new constructions as follows: I supplemented the program with a second array of 50 objects of the TOverlappedPatel class, threw a second timer with a triggering period of 166 milliseconds, wrote about the same code in its handler as the first timer. Due to the delay between the timers, visually screensaver even began to work a little nicer:
How can I improve the program? Just with the help of the third OOP whale - polymorphism. Now our program does a double job, and the processor is swept off later, continuously making trigonometric calculations (well, someone like that, probably). Is it possible to create a single array of objects, but of different classes? This will be the next part, and the code from the example above is in the lesson2-1 branch .
Part III
Typically, books on pascal, delphi, and lazarus describe polymorphism for a couple of pages (at best), not counting code listings (not counting, because understanding of these listings doesn't come from a couple of pages of text). And since pascal books are traditionally written for students, all examples wander from one publication to another and are associated with the description of the abstract class Man and his two heirs, Student and Teacher. Since I am neither one nor the other, I have never been able to draw knowledge of polymorphism from these books. Where I could put into practice the classes of Students and Teachers in order to understand the essence of polymorphism, I never came up with, so I had to comprehend everything again by typing.
I would call a very significant drawback of all these books that in the chapters on polymorphism the main question is “how”, although the primary question is “why”, because the answer to it absorbs 99% of the whole essence of polymorphism. I found such an answer in a wonderful article by Vsevolod LeonovEmbarcadero Blogs (current delphi owner name). And in general terms, polymorphism is presented, for example, as follows: there is a basic abstract Feline class, from which numerous heirs are generated - Kotik, Leopardik, Tigra, Lyova, etc. All of them have similar properties, but the methods of their existence are different. The meow method, for example, will be different for a cat and tigers. The “play” base class method for a cat is overlapped by the method of “rubbing one’s feet” implementation, but for the left, it is blocked by the “gobble up” implementation. However, all specific cats will be objects of the Feline class, and an inexperienced child will persistently call the "play" method for all cats, without realizing the difference.
Back to our practice of drawing weird circlespolar roses. We stopped at the fact that we complicated the work of the program by simultaneously creating two arrays of 50 objects of different classes and two timers with different response periods, which have almost identical handlers:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if not Marg then
begin
Petals[CurPetal]:=TPetal.Create(img.Width,img.Height);
Petals[CurPetal].Draw(img.Canvas);
CurPetal+=1;
if CurPetal=50 then Marg:=TRUE;
end else begin
Petals[50-CurPetal].Draw(img.Canvas,TRUE);
Petals[50-CurPetal].Free;
CurPetal-=1;
if CurPetal=0 then Marg:=FALSE;
end;
img.Canvas.TextOut(10,10,IntToStr(CurPetal)+' ');
end;
procedure TForm1.Timer2Timer(Sender: TObject);
begin
if not MargO then
begin
OPetals[CurOPetal]:=TOverlappedPetal.Create(img.Width,img.Height);
OPetals[CurOPetal].Draw(img.Canvas);
CurOPetal+=1;
if CurOPetal=50 then MargO:=TRUE;
end else begin
OPetals[50-CurOPetal].Draw(img.Canvas,TRUE);
OPetals[50-CurOPetal].Free;
CurOPetal-=1;
if CurOPetal=0 then MargO:=FALSE;
end;
img.Canvas.TextOut(50,10,IntToStr(CurOPetal)+' ');
end;
You must admit that the labor-encoder would gobble up the hand that wrote such a code “with a reserve”. Since we decided to evolve into real programmers, we will solve the problem of getting rid of repetitive code and facilitate the work of the program.
Now we have two classes: TPetal and its descendant TOverlappedPetal. However, this is a little wrong and now we will correct the situation. A polar rose with overlapping petals and a polar rose with non-overlapping petals should be classes of the same level, since from the point of view of class theory they are equivalent. Climbing to a higher level of abstraction, we understand that we should introduce the base class of the polar rose, but from it already generate the two above. All the similarities that were in the two previous classes, we transfer to the base, thus, the two descendants will differ only in the most necessary:
{ TCustomPetal }
TCustomPetal = class
protected
R, phi, PetalI: double;
X, Y, K, CX, CY: integer;
Scale, RColor: integer;
public
constructor Create (Xmax, Ymax: integer); virtual;
procedure Draw (Canvas: TCanvas; Erase: boolean = FALSE);
end;
{ TPetal }
TPetal = class (TCustomPetal)
public
constructor Create (Xmax, Ymax: integer); override;
end;
{ TOverlappedPetal }
TOverlappedPetal = class (TCustomPetal)
public
constructor Create (Xmax, Ymax: integer); override;
end;
Polymorphism is expressed in the fact that the constructor of the base class overlaps in the implementation of descendants (there is a legend around the world that in ancient versions of object pascal it was impossible to overlap class constructors, but I do not believe that). This technique is implemented using reserved virtual and override directives . By declaring the create method of the base class virtual, we thus make it clear that the constructor implementation may (but not necessarily) overlap in the descendants. If we added (which in our case is quite possible, but this will complicate the code) the abstract directive to the virtual directive, this would mean that there will be no constructor implementation in the base class, but descendants must have such an implementation. We will not make the constructor of the base class abstract, since its implementation has common features for descendants:
constructor TCustomPetal.Create(Xmax, Ymax: integer);
begin
inherited Create;
CX:=Random(Xmax);
CY:=Random(Ymax);
RColor:=1+Random($FFFFF0);
Scale:=2+Random(12);
end;
constructor TOverlappedPetal.Create(Xmax, Ymax: integer);
begin
inherited Create (Xmax,Ymax);
K:=12;
while PetalI=Round(PetalI) do
PetalI:=(1+Random(6))/(1+Random(6));
end;
constructor TPetal.Create(Xmax, Ymax: integer);
begin
inherited Create (Xmax,Ymax);
K:=2;
PetalI:=1+Random(8);
end;
So, the constructor of the base class initializes the fields that are common for descendants - the center coordinates, color, and scale. But the descendant designers first call the base class constructor, and then they carry out different initialization of the remaining fields - the coefficient for the angular coordinate and the coefficient that determines the shape of the polar rose.
Now look how the main program code is simplified. Instead of two arrays with fifty objects of different classes, we declare one array with objects of the TCustomPetal class, and rewrite the event handler in the timer as follows:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if not Marg then
begin
if Random(2)=1 then Petals[CurPetal]:=TPetal.Create(img.Width,img.Height)
else Petals[CurPetal]:=TOverlappedPetal.Create(img.Width,img.Height);
Petals[CurPetal].Draw(img.Canvas);
CurPetal+=1;
if CurPetal=50 then Marg:=TRUE;
end else begin
Petals[50-CurPetal].Draw(img.Canvas,TRUE);
Petals[50-CurPetal].Free;
CurPetal-=1;
if CurPetal=0 then Marg:=FALSE;
end;
img.Canvas.TextOut(10,10,IntToStr(CurPetal)+' ');
end;
Логика работы: берется случайное число от 0 до 3 и если оно равно 1, то для очередного объекта из массива CustomPetal вызывается конструктор класса-потомка TPetal, в противном случае вызывается конструктор класса-потомка TOverlappedPetal. В этом и проявляется полиморфизм: не смотря на то, что массив объектов у нас одного и того же типа TCustomPetal, по факту объекты создаются с типом потомка. Поскольку у них одни и те же поля, одни и те же методы — работа с ними ничем не отличается для программы. Мы вызываем одинаковый метод DrawPetal, но ведет он себя по-разному в зависимости от типа объекта. Согласитесь, код программы заметно упростился и стал более наглядным (для тех, кто все-таки вкурил парадигму ООП).
Свежий пример с изменениями — в ветке lersson3.
Как еще можно усовершенствовать работу? На мой вкус более симпатичным является вариант, где 50 роз не последовательно рисуются и затираются, а когда это происходит непрерывно. Для этого следует немного изменить обработчик таймера, это уже не связано с ООП, но заставляет пошевелить мозгами:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
if Assigned (Petals[CurPetal]) then
begin
Petals[CurPetal].Draw(img.Canvas,TRUE);
Petals[CurPetal].Free;
end;
if Random(2)=1 then Petals[CurPetal]:=TPetal.Create(img.Width,img.Height)
else Petals[CurPetal]:=TOverlappedPetal.Create(img.Width,img.Height);
Petals[CurPetal].Draw(img.Canvas);
CurPetal+=1;
if CurPetal=PetalC then CurPetal:=0;
img.Canvas.TextOut(10,10,IntToStr(CurPetal)+' ');
end;
В целях еще большего совершенствования программы, я сделал массив роз динамическим (Petals: array of TCustomPetal), а в обработчике события при создании формы ему устанавливается размер — увеличил до 70, поскольку 50 роз на экране выглядят слишком жиденько. Логика работы обработчика таймера изменилась и вместе с тем упростилась: CurPetal — это наш указатель на текущий номер розы в массиве, и он пробегает бесконечно от 0 до 70 (т.к. динамические массивы всегда начинают нумерацию с нуля). Сначала проверяется, создан ли ранее элемент массива роз с номером CurPetal, и если да, то он затирается и уничтожается. Затем случайным способом создается тот же элемент с тем же номером. Указатель на номер массива инкременируется, и если он становится больше границы массива, то обнуляется (в нашем случае, последним существующим элементом массива будет элемент с номером 69, т.к. нумерация, напомню, идет с нуля). Окончательный вид скринсейвера (вверху для наглядности — счетчик):
The final project is in the lesson3-1 branch .
In the most recent version, the amount of computer memory used during operation was reduced to 7 with a small MB, while at the beginning the application had a lot of fun with 30 MB. Using OOP, code refactoring, and knowledge of mathematics allow you to create beautiful and efficient code, always remember this.
PS Update
In connection with practical comments, I corrected inaccuracies and glitches, and also transferred the work of the draftsman to the TPetals class (TQPEtals in another embodiment), which “steers” the whole process using a list of objects (TObjectList) or a queue of objects (TObjectQueue). Now the timer method looks concise:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
P.DrawNext;
end;
Also, in the Draw method, a check is turned on when the roses are erased - whether other roses will be "spoiled" by black dots. The latest project code is in the lesson4-1 (list) and lesson 4-2 (queue) branches .