Delphi is based on OOP concepts, and in particular on the definition of new class types. The use of OOP is partially enforced by the visual development environment, because for every new form defined at design time, Delphi automatically defines a new class. In addition, every component visually placed on a form is an object of a class type available in or added to the system library.
Note The terms class and object are commonly used and often misused, so let's be sure we agree on their definitions. A class is a user-defined data type, which has a state (its representation or internal data) and some operations (its behavior or its methods). An object is an instance of a class, or a variable of the data type defined by the class.
Objects are actual entities. When the program runs, objects take up some memory for their internal
representation. The relationship between object and class is the same as the one between variable and type.
As in most other modern OOP languages (including Java and C#), in Delphi a class-type variable doesn't provide the storage for the object, but is only a pointer or reference to the object in memory. Before you use the object, you must allocate memory for it by creating a new instance or by assigning an existing instance to the variable:
var
Obj1, Obj2: TMyClass;
begin
// assign a newly created object Obj1 := TMyClass.Create;
// assign to an existing object Obj2 := ExistingObject;
The call to Create invokes a default constructor available for every class, unless the class redefines it (as described later). To declare a new class data type in Delphi, with some local data fields and some methods, use the following syntax:
Note The convention in Delphi is to use the letter T as a prefix for the name of every class you write and every other type (T stands for Type). This is just a convention—to the compiler, T is just a letter like any other—but it is so common that following it will make your code easier for other developers to understand.
A method is defined with the function or procedure keyword, depending on whether it has a return value. Inside the class definition, methods can only be declared; they must be then defined in the implementation portion of the same unit. In this case, you prefix each method name with the name of the class it belongs to, using dot notation:
procedure TDate.SetValue (m, d, y: Integer);
begin
// call IsLeapYear in SysUtils.pas Result := IsLeapYear (Year);
end;
Tip If you press Ctrl+Shift+C while the cursor is within the class definition, the Class Completion feature of the Delphi editor will generate the skeleton of the definition of the methods declared in a class.
This is how you can use an object of the previously defined class:
Notice that ADay.LeapYear is an expression similar to ADay.Year, although the first is a function call and the second a direct data access. You can optionally add parentheses after the call of a function with no parameters. You can find the previous code snippets in the source code of the Dates1 example; the only difference is that the program creates a date based on the year provided in an edit box.
Note The code snippet above uses a try/finally block to ensure the destruction of the object even in the case of exceptions in the code. You can find an introduction to the topic of exceptions at the end of this chapter.
More on Methods
There is a lot more to say about methods. Here are some short notes about the features available in Delphi:
Delphi supports method overloading. This means you can have two methods with the same name, provided that you mark the methods with the overload keyword and that the parameter lists of the two methods are sufficiently different. By checking the parameters, the compiler can determine which version you want to call.
Methods can have one or more parameters with default values. If these parameters are omitted in the method call, they will be assigned the default value.
Within a method, you can use the Self keyword to access the current object. When you refer to local data of the object, the reference to self is implicit. For example, in the SetValue method of the TDate class listed earlier, you use Month to refer to a field of the current object, and the compiler translates Month into Self.Month.
You can define class methods, marked by the class keyword. A class method doesn't have an object instance to act upon, because it can be applied to an object of the class or to the class as a whole.
Delphi doesn't (currently) have a way to define class data, but you can mimic this functionality by adding global data in the implementation portion of the unit defining the class.
By default, methods use the register calling convention: (simple) parameters and return values are passed from the calling code to the function and back using CPU registers, instead of the stack. This process makes method calls much faster.
Creating Components Dynamically
To emphasize the fact that Delphi components aren't much different from other objects (and also to demonstrate the use of the Self keyword), I've written the CreateComps example. This program has a form with no components and a handler for its OnMouseDown event, which I've chosen because it receives as a parameter the position of the mouse click (unlike the OnClick event). I need this information to create a button component in that position. Here is the method's code:
procedure TForm1.FormMouseDown (Sender: TObject;
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
Btn: TButton;
begin
Btn := TButton.Create (Self);
Btn.Parent := Self;
Btn.Left := X;
Btn.Top := Y;
Btn.Width := Btn.Width + 50;
Btn.Caption := Format ('Button at %d, %d', [X, Y]);
end;
The effect of this code is to create buttons at mouse-click positions, as you can see in Figure 2.1. In the code, notice in particular the use of the Self keyword as both the parameter of the Create method (to specify the component's owner) and the value of the Parent property. I'll discuss these two elements (ownership and the Parent property) in Chapter 4,
"Core Library Classes."
Figure 2.1: The output of the CreateComps example, which creates Button components at run time
When writing code like this, you might be tempted to use the Form1 variable instead of Self. In this specific example, that change wouldn't make any practical difference; but if there are multiple instances of a form, using Form1 would be an error. In fact, if the Form1 variable refers to the first form of that type being created, then by clicking in another form of the same type, the new button will always be displayed in the first form. The button's Owner and Parent will be Form1, not the form the user has clicked. In general, referring to a particular instance of a class when the current object is required is bad OOP practice.
Encapsulation
A class can have any amount of data and any number of methods. However, for a good object-oriented approach, data should be hidden, or encapsulated, inside the class using it. When you access a date, for example, it makes no sense to change the value of the day by itself. In fact, changing the value of the day might result in an invalid date, such as February 30. Using methods to access the internal representation of an object limits the risk of generating erroneous situations, because the methods can check whether the date is valid and refuse to modify the new value if it is not. Encapsulation is important because it allows the class writer to modify the internal representation in a future version.
The concept of encapsulation is often indicated by the idea of a "black box." You don't know about the internals: You only know how to interface with the black box or use it regardless of its internal structure. The "how to use" portion, called the class interface, allows other parts of a program to access and use the objects of that class. However, when you use the objects, most of their code is hidden. You seldom know what internal data the object has, and you usually have no way to access the data directly. Of course, you are supposed to use methods to access the data, which is shielded from unauthorized access. This is the object-oriented approach to a classical programming concept known as information hiding. However, in Delphi there is the extra level of hiding, through properties, as we'll see later in this chapter.
Delphi implements this class-based encapsulation, but it still supports the classic module-based encapsulation using the structure of units. Every identifier that you declare in the interface portion of a unit becomes visible to other units of the program, provided there is a uses statement referring back to the unit that defines the identifier. On the other hand, identifiers declared in the implementation portion of the unit are local to that unit.
Private, Protected, and Public
For class-based encapsulation, the Delphi language has three access specifiers: private, protected, and public. A fourth, published, controls run-time type information (RTTI) and design-time information (as discussed in more detail in Chapter 4), but it gives the same programmatic accessibility as public. Here are the three classic access specifiers:
The private directive denotes fields and methods of a class that are not accessible outside the unit that declares the class.
The protected directive is used to indicate methods and fields with limited visibility. Only the current class and its inherited classes can access protected elements. More precisely, only the class, subclasses, and any code in the same unit as the class can access protected members, which opens up to a trick discussed in the sidebar "Accessing Protected Data of Other Classes (or, the "Protected Hack")" later in this chapter. We'll discuss this keyword again in the section "Protected Fields and Encapsulation."
The public directive denotes fields and methods that are freely accessible from any other portion of a program as well as in the unit in which they are defined.
Generally, the fields of a class should be private and the methods public. However, this is not always the case.
Methods can be private or protected if they are needed only internally to perform some partial computation or to implement properties. Fields might be declared as protected so that you can manipulate them in inherited classes, although this isn't considered a good OOP practice.
Warning Access specifiers only restrict code outside your unit from accessing certain members of classes declared in the interface section of your unit. This means that if two classes are in the same unit, there is no protection for their private fields.
As an example, consider this new version of the TDate class:
type
TDate = class private
Month, Day, Year: Integer;
This document was created by an unregistered ChmMagic, please go to http://www.bisenter.com to register it. Thanks.
public
procedure SetValue (y, m, d: Integer); overload;
procedure SetValue (NewDate: TDateTime); overload;
function LeapYear: Boolean;
function GetText: string;
procedure Increase;
end;
You might think of adding other functions, such as GetDay, GetMonth, and GetYear, which return the corresponding private data, but similar direct data-access functions are not always needed. Providing access functions for each and every field might reduce the encapsulation and make it harder to modify the internal implementation of a class. Access functions should be provided only if they are part of the logical interface of the class you are implementing.
Another new method is the Increase procedure, which increases the date by one day. This calculation is far from simple, because you need to consider the different lengths of the various months as well as leap and non–leap years.
To make it easier to write the code, I'll change the internal implementation of the class to Delphi's TDateTime type for the internal implementation. The class definition will change to the following (the complete code is in the DateProp example):
procedure SetValue (NewDate: TDateTime); overload;
function LeapYear: Boolean;
function GetText: string;
procedure Increase;
end;
Notice that because the only change is in the private portion of the class, you won't have to modify any of your existing programs that use it. This is the advantage of encapsulation!
Note The TDateTime type is a floating-point number. The integral portion of the number indicates the date since 12/30/1899, the same base date used by OLE Automation and Microsoft Win32 applications. (Use negative values to express previous years.) The decimal portion indicates the time as a fraction. For example, a value of 3.75 stands for the second of January 1900, at 6:00 A.M. (three-quarters of a day). To add or subtract dates, you can add or subtract the number of days, which is much simpler than adding days with a day/month/year representation.
Encapsulating with Properties
Properties are a very sound OOP mechanism, or a well-thought-out application of the idea of encapsulation.
Essentially, you have a name that completely hides its implementation details. This allows you to modify the class extensively without affecting the code using it. A good definition of properties is that of virtual fields. From the perspective of the user of the class that defines them, properties look exactly like fields, because you can generally read or write their value. For example, you can read the value of the Caption property of a button and assign it to the Text property of an edit box with the following code:
Edit1.Text := Button1.Caption;
It looks like you are reading and writing fields. However, properties can be directly mapped to data, as well as to access methods, for reading and writing the value. When properties are mapped to methods, the data they access can be part of the object or outside of it, and they can produce side effects, such as repainting a control after you change one of its values. Technically, a property is an identifier that is mapped to data or methods using a read and a write clause. For example, here is the definition of a Month property for a date class:
property Month: Integer read FMonth write SetMonth;
To access the value of the Month property, the program reads the value of the private field FMonth; to change the
property value, it calls the method SetMonth (which must be defined inside the class, of course).
Different combinations are possible (for example, you could also use a method to read the value or directly change a field in the write directive), but the use of a method to change the value of a property is common. Here are two alternative definitions for the property, mapped to two access methods or mapped directly to data in both directions:
property Month: Integer read GetMonth write SetMonth;
property Month: Integer read FMonth write FMonth;
Often, the actual data and access methods are private (or protected), whereas the property is public. For this reason, you must use the property to have access to those methods or data, a technique that provides both an extended and a simplified version of encapsulation. It is an extended encapsulation because not only can you change the
representation of the data and its access functions, but you can also add or remove access functions without changing the calling code. A user only needs to recompile the program using the property.
Tip When you're defining properties, take advantage of the extended class completion feature of Delphi's editor, which you activate with the Ctrl+Shift+C key combination. After you write the property name, type, and semicolon, press Ctrl+Shift+C, and Delphi will provide you with a complete definition and the skeleton of the setter method. Write Get in front of the name of the identifier after the read keyword, and you'll also have a getter method with almost no typing.