You have seen that Pascal Objects exhibit some characteristics of object-oriented paradigm. They implement encapsulation, data hiding and inheritance, but they also have limitations. For example, Pascal Objects do not take part in polymorphism. So classes are widely used to implement proper object-oriented behavior in a program, especially the GUI-based software.
A Class is defined in almost the same way as an Object, but is a pointer to an Object rather than the Object itself. Technically, this means that the Class is allocated on the Heap of a program, whereas the Object is allocated on the Stack. In other words, when you declare a variable the object type, it will take up as much space on the stack as the size of the object, but when you declare a variable of the class type, it will always take the size of a pointer on the stack. The actual class data will be on the heap.
A class is declared in the same way as an object, using the type declaration. The general form of a class declaration is as follows −
type class-identifier = class private field1 : field-type; field2 : field-type; ... public constructor create(); procedure proc1; function f1(): function-type; end; var classvar : class-identifier;
Its worth to note following important points −
Class definitions should come under the type declaration part of the program only.
A class is defined using the class keyword.
Fields are data items that exist in each instance of the class.
Methods are declared within the definition of a class.
There is a predefined constructor called Create in the Root class. Every abstract class and every concrete class is a descendant of Root, so all classes have at least one constructor.
There is a predefined destructor called Destroy in the Root class. Every abstract class and every concrete class is a descendant of Root, so, all classes have at least one destructor.
Let us define a Rectangle class that has two integer type data members - length and width and some member functions to manipulate these data members and a procedure to draw the rectangle.
type Rectangle = class private length, width: integer; public constructor create(l, w: integer); procedure setlength(l: integer); function getlength(): integer; procedure setwidth(w: integer); function getwidth(): integer; procedure draw; end;
Let us write a complete program that would create an instance of a rectangle class and draw the rectangle. This is the same example we used while discussing Pascal Objects. You will find both programs are almost same, with the following exceptions −
You will need to include the {$mode objfpc} directive for using the classes.
You will need to include the {$m+} directive for using constructors.
Class instantiation is different than object instantiation. Only declaring the variable does not create space for the instance, you will use the constructor create to allocate memory.
Here is the complete example −
{$mode objfpc} // directive to be used for defining classes {$m+} // directive to be used for using constructor program exClass; type Rectangle = class private length, width: integer; public constructor create(l, w: integer); procedure setlength(l: integer); function getlength(): integer; procedure setwidth(w: integer); function getwidth(): integer; procedure draw; end; var r1: Rectangle; constructor Rectangle.create(l, w: integer); begin length := l; width := w; end; procedure Rectangle.setlength(l: integer); begin length := l; end; procedure Rectangle.setwidth(w: integer); begin width :=w; end; function Rectangle.getlength(): integer; begin getlength := length; end; function Rectangle.getwidth(): integer; begin getwidth := width; end; procedure Rectangle.draw; var i, j: integer; begin for i:= 1 to length do begin for j:= 1 to width do write(' * '); writeln; end; end; begin r1:= Rectangle.create(3, 7); writeln(' Draw Rectangle: ', r1.getlength(), ' by ' , r1.getwidth()); r1.draw; r1.setlength(4); r1.setwidth(6); writeln(' Draw Rectangle: ', r1.getlength(), ' by ' , r1.getwidth()); r1.draw; end.
When the above code is compiled and executed, it produces the following result −
Draw Rectangle: 3 by 7 * * * * * * * * * * * * * * * * * * * * * Draw Rectangle: 4 by 6 * * * * * * * * * * * * * * * * * * * * * * * *
Visibility indicates the accessibility of the class members. Pascal class members have five types of visibility −
Sr.No | Visibility & Accessibility |
---|---|
1 |
Public These members are always accessible. |
2 |
Private These members can only be accessed in the module or unit that contains the class definition. They can be accessed from inside the class methods or from outside them. |
3 | Strict Private These members can only be accessed from methods of the class itself. Other classes or descendent classes in the same unit cannot access them. |
4 |
Protected This is same as private, except, these members are accessible to descendent types, even if they are implemented in other modules. |
5 | Published This is same as a Public, but the compiler generates type information that is needed for automatic streaming of these classes if the compiler is in the {$M+} state. Fields defined in a published section must be of class type. |
Constructors are special methods, which are called automatically whenever an object is created. So we take full advantage of this behavior by initializing many things through constructor functions.
Pascal provides a special function called create() to define a constructor. You can pass as many arguments as you like into the constructor function.
Following example will create one constructor for a class named Books and it will initialize price and title for the book at the time of object creation.
program classExample; {$MODE OBJFPC} //directive to be used for creating classes {$M+} //directive that allows class constructors and destructors type Books = Class private title : String; price: real; public constructor Create(t : String; p: real); //default constructor procedure setTitle(t : String); //sets title for a book function getTitle() : String; //retrieves title procedure setPrice(p : real); //sets price for a book function getPrice() : real; //retrieves price procedure Display(); // display details of a book end; var physics, chemistry, maths: Books; //default constructor constructor Books.Create(t : String; p: real); begin title := t; price := p; end; procedure Books.setTitle(t : String); //sets title for a book begin title := t; end; function Books.getTitle() : String; //retrieves title begin getTitle := title; end; procedure Books.setPrice(p : real); //sets price for a book begin price := p; end; function Books.getPrice() : real; //retrieves price begin getPrice:= price; end; procedure Books.Display(); begin writeln('Title: ', title); writeln('Price: ', price:5:2); end; begin physics := Books.Create('Physics for High School', 10); chemistry := Books.Create('Advanced Chemistry', 15); maths := Books.Create('Algebra', 7); physics.Display; chemistry.Display; maths.Display; end.
When the above code is compiled and executed, it produces the following result −
Title: Physics for High School Price: 10 Title: Advanced Chemistry Price: 15 Title: Algebra Price: 7
Like the implicit constructor named create, there is also an implicit destructor method destroy using which you can release all the resources used in the class.
Pascal class definitions can optionally inherit from a parent class definition. The syntax is as follows −
type childClas-identifier = class(baseClass-identifier) < members > end;
Following example provides a novels class, which inherits the Books class and adds more functionality based on the requirement.
program inheritanceExample; {$MODE OBJFPC} //directive to be used for creating classes {$M+} //directive that allows class constructors and destructors type Books = Class protected title : String; price: real; public constructor Create(t : String; p: real); //default constructor procedure setTitle(t : String); //sets title for a book function getTitle() : String; //retrieves title procedure setPrice(p : real); //sets price for a book function getPrice() : real; //retrieves price procedure Display(); virtual; // display details of a book end; (* Creating a derived class *) type Novels = Class(Books) private author: String; public constructor Create(t: String); overload; constructor Create(a: String; t: String; p: real); overload; procedure setAuthor(a: String); // sets author for a book function getAuthor(): String; // retrieves author name procedure Display(); override; end; var n1, n2: Novels; //default constructor constructor Books.Create(t : String; p: real); begin title := t; price := p; end; procedure Books.setTitle(t : String); //sets title for a book begin title := t; end; function Books.getTitle() : String; //retrieves title begin getTitle := title; end; procedure Books.setPrice(p : real); //sets price for a book begin price := p; end; function Books.getPrice() : real; //retrieves price begin getPrice:= price; end; procedure Books.Display(); begin writeln('Title: ', title); writeln('Price: ', price); end; (* Now the derived class methods *) constructor Novels.Create(t: String); begin inherited Create(t, 0.0); author:= ' '; end; constructor Novels.Create(a: String; t: String; p: real); begin inherited Create(t, p); author:= a; end; procedure Novels.setAuthor(a : String); //sets author for a book begin author := a; end; function Novels.getAuthor() : String; //retrieves author begin getAuthor := author; end; procedure Novels.Display(); begin writeln('Title: ', title); writeln('Price: ', price:5:2); writeln('Author: ', author); end; begin n1 := Novels.Create('Gone with the Wind'); n2 := Novels.Create('Ayn Rand','Atlas Shrugged', 467.75); n1.setAuthor('Margaret Mitchell'); n1.setPrice(375.99); n1.Display; n2.Display; end.
When the above code is compiled and executed, it produces the following result −
Title: Gone with the Wind Price: 375.99 Author: Margaret Mitchell Title: Atlas Shrugged Price: 467.75 Author: Ayn Rand
Its worth to note following important points −
The members of the Books class have protected visibility.
The Novels class has two constructors, so the overload operator is used for function overloading.
The Books.Display procedure has been declared virtual, so that the same method from the Novels class can override it.
The Novels.Create constructor calls the base class constructor using the inherited keyword.
Interfaces are defined to provide a common function name to the implementers. Different implementers can implement those interfaces according to their requirements. You can say, interfaces are skeletons, which are implemented by developers. Following is an example of interface −
type Mail = Interface Procedure SendMail; Procedure GetMail; end; Report = Class(TInterfacedObject, Mail) Procedure SendMail; Procedure GetMail; end;
Please note that, when a class implements an interface, it should implement all methods of the interface. If a method of an interface is not implemented, then the compiler will give an error.
An abstract class is one that cannot be instantiated, only inherited. An abstract class is specified by including the word symbol abstract in the class definition, like this −
type Shape = ABSTRACT CLASS (Root) Procedure draw; ABSTRACT; ... end;
When inheriting from an abstract class, all methods marked abstract in the parent's class declaration must be defined by the child; additionally, these methods must be defined with the same visibility.
Declaring class members or methods as static makes them accessible without needing an instantiation of the class. A member declared as static cannot be accessed with an instantiated class object (though a static method can). The following example illustrates the concept −
program StaticExample; {$mode objfpc} {$static on} type myclass=class num : integer;static; end; var n1, n2 : myclass; begin n1:= myclass.create; n2:= myclass.create; n1.num := 12; writeln(n2.num); n2.num := 31; writeln(n1.num); writeln(myclass.num); myclass.num := myclass.num + 20; writeln(n1.num); writeln(n2.num); end.
When the above code is compiled and executed, it produces the following result −
12 31 31 51 51
You must use the directive {$static on} for using the static members.