Encapsulation


In structured applications, data is usually stored in records and the application logic is divided between numerous functions and procedures. The application data and the code that uses this data are always separated.

For instance, the following example shows the standard way of working with data in structured applications.

Listing 10-1: Working with records

image from book
program Project1; {$APPTYPE CONSOLE} uses   SysUtils; type   TAnimal = record     Name: string;     Age: Integer;     Hungry: Boolean;   end; procedure ShowInfo(var AAnimal: TAnimal); const   HUNGRY_STRING: array[Boolean] of string = ('No', 'Yes'); begin   with AAnimal do   begin     WriteLn('Name: ', Name);     WriteLn('Age: ', Age);     WriteLn('Hungry: ', HUNGRY_STRING[Hungry]);   end;          // with AAnimal end; var   MyDog: TAnimal; begin   MyDog.Name := 'Apollo';   MyDog.Age := 10;   MyDog.Hungry := False;   ShowInfo(MyDog);   ReadLn; end.
image from book

To access data stored in the MyDog record, you have to pass the record as a variable parameter to a procedure. Every procedure that needs to work with data stored in a TAnimal type record has to have a parameter that accepts a pointer (variable parameters act like pointers) to the actual data.

This produces three undesirable side effects: You always have to pass the record pointer to a procedure or a function, you have to write code that checks whether the data is valid, and you have to check whether the data even exists.

If you only use static records, the data always exists, but if you opt to use dynamic records, you have to make sure that the record isn't nil because trying to access fields in a nil value always results in an error.

In order to properly use a dynamic record, the code in the procedure has to be changed to accept a real pointer to the record and it has to test whether the pointer references a valid memory location. This is illustrated in Listing 10-2.

Listing 10-2: Accessing records through pointers

image from book
program Project1; {$APPTYPE CONSOLE} uses   SysUtils; type   PAnimal = ^TAnimal; { pointer to a TAnimal record }   TAnimal = record     Name: string;     Age: Integer;     Hungry: Boolean;   end; procedure ShowInfo(AAnimal: PAnimal); const   HUNGRY_STRING: array[Boolean] of string = ('No', 'Yes'); begin   if AAnimal = nil then Exit;   with AAnimal^ do   begin     WriteLn('Name: ', Name);     WriteLn('Age: ', Age);     WriteLn('Hungry: ', HUNGRY_STRING[Hungry]);   end;          // with AAnimal end; var   MyDog: TAnimal;   MyDynamicCat: PAnimal; begin   MyDog.Name := 'Apollo';   MyDog.Age := 10;   MyDog.Hungry := False;   ShowInfo(@MyDog);   New(MyDynamicCat);   ShowInfo(MyDynamicCat);   Dispose(MyDynamicCat);   ReadLn; end.
image from book

Creating a New Class

The solution to these three problems lies in object-oriented programming, and specifically in encapsulation. To remove both the need for pointers to the actual data and the need for data validation, you can encapsulate the TAnimal record and the ShowInfo procedure into a TAnimal class.

Before we move on, note the use of the word "class." In object-oriented programming, classes are blueprints for objects, just like the human genome is the blueprint for human beings. An object is an instance of a class, a tangible result of the blueprint. Without the blueprint, nature wouldn't be able to instantiate us and the Delphi compiler wouldn't be able to instantiate objects.

Creating a basic class is as simple as creating a basic record. The only difference is that in order to create a class we have to use the reserved word class.

type    TAnimal = class       Name: string;   Age: Integer;   Hungry: Boolean; end; 

Now that we have the TAnimal class, we can create an object of type TAnimal and use it in the application. The first step in creating an object is to declare an object variable, that is, to declare a variable of a class type:

var   MyDog: TAnimal;

But this is only the declaration of the object. To actually use the MyDog object in the application, we have to create it.

Objects are created with a constructor, which is a special method called Create that is used to create object instances. The syntax for creating an object is:

Object := Class.Create;

To create the MyDog object, we have to call the TAnimal constructor, because the MyDog variable is declared as TAnimal:

MyDog := TAnimal.Create;

The constructor allocates the memory on the heap necessary to store the declared fields and sets all field values to empty. The constructor also returns the reference to the new object. After the call, the MyDog variable points to the new object created by the constructor. Yes, objects are actually pointers, but you don't have to dereference objects (you don't have to write the ^ symbol) to access their fields or methods.

MyDog.Name := 'Apollo';  WriteLn(MyDog.Name);

Because objects are dynamically created on the heap, you have to release the memory occupied by the object once you're finished with it. Releasing the memory occupied by the object is called object destruction.

To destroy the object, you have to call the object's destructor. A destructor is a special method called Destroy that is used to free the object from memory. You should never call the Destroy method directly. Instead, you should call the Free method because the Free method makes sure the object exists before trying to destroy it. An object that doesn't exist references an invalid memory location, and by accessing an invalid memory location you're asking for trouble.

When calling either Destroy or Free, you have to reference the object, not the class:

MyDog.Destroy; MyDog.Free;

The following listing shows how to properly use an object.

Listing 10-3: Using an object

image from book
program Project1; {$APPTYPE CONSOLE} uses   SysUtils; type   TAnimal = class     Name: string;     Age: Integer;     Hungry: Boolean;   end; var   MyDog: TAnimal; begin   { create the object }   MyDog := TAnimal.Create;   { use it }   MyDog.Name := 'Apollo';   WriteLn(MyDog.Name);   { destroy it }   MyDog.Free;   ReadLn; end.
image from book

Adding Methods to the Class

To completely finish the encapsulation process, we have to encapsulate the ShowInfo procedure. Encapsulating the ShowInfo procedure means to declare it as a part of the TAnimal class:

type   TAnimal = class     Name: string;     Age: Integer;     Hungry: Boolean;     procedure ShowInfo;   end;

Note that this is only the declaration of the ShowInfo procedure. We still have to write the method implementation. In Delphi, the method implementation has to be written outside of the class block (unlike C++, which allows both inline and external method implementation).

When writing the method implementation (procedures and functions that belong to a class are called methods), you have to specify to which class the method belongs. This requires writing the class name before the method name and separating class and method names with a dot.

procedure Class.Method(ParameterList);  function Class.Method(ParameterList);

Thus, the implementation of the ShowInfo method looks like this:

procedure TAnimal.ShowInfo;  begin end;

The ShowInfo method has no parameter list because the data needed by the ShowInfo method is encapsulated in the class. Here is the full implementation of the ShowInfo method:

procedure TAnimal.ShowInfo; const   HUNGRY_STRING: array[Boolean] of string = ('No', 'Yes'); begin   WriteLn('Name: ', Name);   WriteLn('Age: ', Age);   WriteLn('Hungry: ', HUNGRY_STRING[Hungry]); end;

The Self Pointer

Since both fields and the ShowInfo method belong to the same class, the method can directly access the fields. Actually, the method can access the fields through an invisible pointer called Self. The Delphi compiler implicitly creates the Self pointer for every object. The Self pointer references the object in which the method is called, which means that by using the Self pointer, the object can reference itself (hence the name). If you want, you can use the Self pointer in the ShowInfo method to reference the fields of the TAnimal class explicitly:

procedure TAnimal.ShowInfo; const   HUNGRY_STRING: array[Boolean] of string = ('No', 'Yes'); begin   WriteLn('Name: ', Self.Name);   WriteLn('Age: ', Self.Age);   WriteLn('Hungry: ', HUNGRY_STRING[Self.Hungry]); end;

Using the Class

Object-oriented programming is so popular today because using objects in your code results in cleaner and more straightforward code. The call to the ShowInfo method is now simpler and easier to understand than the call to the ShowInfo procedure, because we don't have to worry about superfluous parameter passing.

Listing 10-4: The completed class

image from book
program Project1; {$APPTYPE CONSOLE} uses   SysUtils; type   TAnimal = class     Name: string;     Age: Integer;     Hungry: Boolean;     procedure ShowInfo;   end; procedure TAnimal.ShowInfo; const   HUNGRY_STRING: array[Boolean] of string = ('No', 'Yes'); begin   WriteLn('Name: ', Name);   WriteLn('Age: ', Age);   WriteLn('Hungry: ', HUNGRY_STRING[Hungry]); end; var   MyDog: TAnimal; begin   { create the object }   MyDog := TAnimal.Create;   { use it }   MyDog.Name := 'Apollo';   MyDog.Age := 10;   MyDog.Hungry := True;   MyDog.ShowInfo;   { destroy it }   MyDog.Free;   ReadLn; end.
image from book

Moving the Class to a Unit

The encapsulation of the TAnimal record and the ShowInfo procedure is now complete, but we have done a VBT (very bad thing). We have created a class in the main project file, but classes should always reside in separate units.

To move the class to a new unit, we have to do the following:

  • Create a new unit (called Animal).

  • Move the class declaration to the interface section of the unit.

  • Move the method implementation(s) to the implementation section of the unit.

The resulting image from book Animal.pas unit should look like the one in Listing 10-5.

Listing 10-5: The TAnimal class unit

image from book
unit Animal; interface type   TAnimal = class     Name: string;     Age: Integer;     Hungry: Boolean;     procedure ShowInfo;   end; implementation procedure TAnimal.ShowInfo; const   HUNGRY_STRING: array[Boolean] of string = ('No', 'Yes'); begin   WriteLn('Name: ', Name);   WriteLn('Age: ', Age);   WriteLn('Hungry: ', HUNGRY_STRING[Hungry]); end; end.
image from book



Inside Delphi 2006
Inside Delphi 2006 (Wordware Delphi Developers Library)
ISBN: 1598220039
EAN: 2147483647
Year: 2004
Pages: 212
Authors: Ivan Hladni

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net