I didn't understand all the constructor and destructor code but I guess that's an evolving process.
I will try to present it using some simplifications. This is a very short explanation related to classes.
1. A class is a bit like a record. However, it differs from the record in that:
- you can put functions and procedures in it, which are called methods,
- has the ability to hide some fields (as in records, but not accessible from the outside)
- it is not enough to declare a variable of a given class (ie: it is not created statically).
In Object Pascal, class names (but not only) are preceded by the letter
T (type). This is not enforced by the compiler. It's a convention that makes life easier. Most languages do not use such prefixes. For example, it is rarely used in the C++ world, but the creators of the
Qt library considered it a good practice - their class names are preceded by the letter
Q.
In Object Pascal, class fields are usually preceded by the letter
F (field). As before, this is not enforced by the compiler and is just a convention to make life easier.
Hiding fields and methods inside a class is similar to what constructors do with various devices. For example, integrated circuits, engines (electric, internal combustion), watches, etc. have a housing that does not allow you to rummage inside them. Thanks to this, it's harder to break something (it's still possible, but you have to try harder
We can only use what the creator intended to use. On the other hand, thanks to this we do not have to go into details. At least that's how it is at first. After that, most people want to know how the object works. Either way, real-world engineering practice shows that this is a good approach
*. This is called hermetization or encapsulation.
So classes are a schema of some object. It can be a representation of a real object (e.g. a button) or it can be completely abstract (e.g. a mathematical one like a matrix or a polynomial).
2. Because the creator of a class can put any fields inside it, some of them can be created dynamically at run time, such as dynamic arrays or objects (i.e. objects can consist of other objects). This is the case in the TPolygon class, which stores the coordinates of the polygon's vertices in an internal array. Depending on the number of vertices, the number of elements in this array changes, so a dynamic array is used. Also, sometimes it is the case that in classes some fields should be nulled or initialized with non-zero values. It all depends on what the developer is creating. Sometimes, certain values should not appear inside variables, because they can cause the program to malfunction or crash completely.
Therefore, special methods were created for classes: constructor and destructor. For example, a constructor can be used to:
- allocating memory for dynamic fields (e.g. dynamic arrays) or setting them as empty (nil),
- creating objects, if the class contains such objects,
- initializing fields inside the class,
- initiate network connections or database connections, - opening a file stream.
Constructors in Object Pascal have a name:
Create, which is preceded by the keyword
constructor. Of course, you can use a different name for the constructor, because it is preceded by a keyword anyway. But the name
Create is easy to understand. You can create any number of constructors that have the same name (i.e. Create), but must differ in the number and/or type of arguments. This is in the
TPolygon class which has 2 constructors:
constructor Create; overload;
constructor Create(const AAngle, ARadius: Single; const AEdgeCount: Integer;
const ABackColor, AFillColor, ALineColor: TColor;
const ALineWidth: Integer; ABitmap: TBGRABitmap;
const ARadialLines, ATrBack: Boolean); overload;
In general, in Object Pascal you can create multiple functions/procedures/methods with the same name. This is called overloading. They just need to differ in the number and type of arguments. For this to happen, you need to tell the compiler that you're overloading, that it's not an accidental error in your code. Overloaded methods are marked with the
overload keyword after the method header.
The destructor does the opposite, i.e. it cleans up after an object that is no longer needed. For a class, a destructor is created when (by example):
- you need to free the memory allocated for the class field in the constructor or some other method of this class,
- termination of network connections or database connections,
- closing the file stream,
- sending a signal to other objects that the object is being removed.
There can only be one destructor in a class. Its purpose is only to clean up the remains of the facility. If there were several destructors, it would not be known in what situations each of them should be called. When cleaning, the situation must be unambiguous. This is in the TPolygon class which has only 1 destructor:
destructor Destroy; override;
This destructor replaces the destructor version of TPolygon's ancestor: TObject. Hence, after its declaration, the word "override" is entered. In TObject, the destructor is declared as:
destructor Destroy; virtual;
The
virtual keyword can be used with any class method, not just the destructor. It informs that:
- such a method appears for the first time in a given ancestor class,
- classes of descendants of such a class can replace its content with other instructions.
Therefore, in descendant classes, inherited methods (with the names of the ancestor) must be marked with the word
override.
3. To create an object that is of a specific class (e.g. TButton, TPolygon, etc.), you need to:
- declare a variable of this class,
- you need to call the constructor of the class.
That is: a class is a scheme according to which an object is created, i.e. an instance of a class.
When we want to create the source code for a class, we don't have to design it from scratch. You can build on the existing classes, adding what you need. This is called inheritance. In Object Pascal, all classes must share a common ancestor,
TObject. This class deals with low-level details. This saves you from having to reinvent the wheel. This is why
TPolygon is a descendant of
TObject. This can be seen in the declaration of the TPolygon class, which is a descendant of the TObject class:
TPolygonData = class(TObject)…
4. There are 3 levels of access to internal elements in class:
- private - such fields and methods are used only by other methods of a given class, there is no access to them from outside the class, descendants of such a class cannot use them either,
- protected: such fields and methods are used by methods of a given class and descendants of this class, there is no access to them from outside the class,
- public - such fields and methods can be freely used outside the class.
A little digression about classes is in order here. In other languages, such as C++, classes can inherit from multiple ancestors (and then chimeras are created
This has some advantages but also disadvantages.
In Object Pascal language, there is only inheritance from one class: TObject (this is also the case in C# and Java, for example). In addition, in the Object Pascal language, the following may occur in classes:
5. Properties are usually implemented in the public part, but also in the protected part (in classes that are only used as ancestors that implement some common features for child classes). It doesn't make sense to put a property in a private part. For example, in the TPolygon class:
property Angle: Single read FAngle write SetAngle;
On the other hand, fields in the public part are practically not used, at least in Object Pascal. Properties are used instead. Properties can be:
- read-only (keyword: read),
- write-only (keyword: write),
- read and write (keywords: read and write).
In practice, write-only properties are not used. Properties can read and write fields directly or using so-called access methods:
- reading, usually has the prefix Get,
- writing, usually prefixed with Set.
To make life easier, method names are formed using a prefix and property name. This is in the TPolygon class:
procedure SetAngle(Value: Single);
For example, in the TPolygon class, reading is done directly from the
FAngle field. On the other hand, the recording is done through the access (saving) method:
SetAngle. Write accessors are typically used to control what is inserted into a class field to avoid class malfunctions.
There may also be properties in the checkout that do not have corresponding fields in the private or protected part. Usually these are readable properties. For example, for a TPolygon class, you could (hypothetically) create an
Area property like this:
TPolygonData = class(TObject)
private
…
function GetArea: Single; // calculates area of polygon
…
public
…
property Area: Single read GetArea;
…
end;
…
function TPolygonData.GetArea: Single;
begin
// instructions to calculate the area of the polygon
end;
Of course, the TPolygon class doesn't need such a property. It was just an excuse to show the property without the corresponding field in the class.
6. Events are a way for other objects to be notified that something has changed (or happened, e.g. a click) in the current object. Events generally begin with the prefix
On. They are declared similarly to properties, i.e. with the word:
property. Events are actually pointers to methods in other classes.
Suppose (hypothetically) that we want to add an event to the TPolygon class informing that the coordinates of the polygon vertices have already been calculated. It could be done like this:
TPolygonUpdateEvent = procedure(Sender: TObject) of object; // a procedural type definition is a pointer to a method in an object
TPolygonData = class(TObject)
private
…
FOnUpdated: TPolygonUpdateEvent; // an event generated when the coordinates of polygon vertices have been recalculated
…
public
…
property OnUpdated: TPolygonUpdateEvent read FOnUpdated write FOnUpdated; // a property that provides the class field that handles the event
…
end;
…
procedure TPolygonData.Prepare;
var
…
begin
…
// calculates the coordinates of the polygon's vertices
…
if FOnUpdated <> nil
then FOnUpdated(Self);
end;
In order for a class to react to events taking place in it, a field is defined inside this class, which must be of procedural type. Thanks to this, it can store a pointer to a procedure in some object (that is, it stores the address of the procedure located in some object). It is important that the procedural type refers to the method in the object. This is indicated by additional keywords:
of object. It cannot be a pointer to an ordinary procedure or method (there may be other cases, but let's leave that for later).
In order to use such an event, you need to assign a method address from some object to the event. For the event defined above, it could be like this:
procedure TForm1.FormCreate(Sender: TObject);
begin
… // previous instructions related to the program window
FPolygon.OnUpdate := @DoPolygonUpdate; // remembering the procedure address from another object
end;
procedure TForm1.DoPolygonUpdate(Sender: TObject);
begin
LabelArea.Caption := FPolygon.Area.ToString;
end;
In the code initializing the main window of the program, we substitute the
DoPolygonUpdate procedure pointer to the
OnUpdate event (
@ is the operator that takes the address of the procedure). This procedure is implemented in the window class (
TForm1). What you need to pay attention to are the arguments of the procedural type that is used to define the event and the arguments of the procedure whose address we substitute to the event. They must be identical in terms of the types of arguments and how they are passed. If in the procedural type
TPolygonUpdatedEvent a keyword was used in them, e.g.
const, and in the definition of the procedure
DoPolygonUpdate it would not be used (or there would be a different word), then the compiler will report an error.
The given example declares its own type
TPolygonUpdateEvent, which has only 1 argument of type
TObject. In fact, you can use the library type
TNotifyEvent (unit:
Classes):
TNotifyEvent = procedure(Sender: TObject) of object;
In fact, the TPolygon class doesn't need this event. The example provided was to illustrate how to add an event to a class. A good introduction to the Object Pascal language may be the manual "Modern Object Pascal Introduction for Programmers" (
HTML,
PDF), written by Michalis Kamburelis.
*) There are people who think this is the wrong approach. According to them, we should go back to programming based on procedures and a huge set of loose variables. For some time there have been a lot of blogs in which various "specialists" (usually young) explain why, according to them, object-oriented programming is pure evil. However, the content of these descriptions indicates that these people do not understand why the object-oriented paradigm appeared, how to use it and what is gained from it. The most common examples are C++ or Java. The real problem, however, is related to the quirks of C++ (also an inheritance from C), or the limitations and verbosity of Java.
In fact, procedural programming is viable for small programs or when the computer is low on memory. In particular, this applies to programs created for microcontrollers (AVR, PIC, simpler ARM, etc.).