We continue our trip through the world of Object Oriented Programming (OOP) with Morfik Pascal. After a quick review of what exactly is OOP, this chapter will provide you with a general overview of Morfik Pascal
What is Object Technology?
In a very simple definition, Object Technology (OT) is a set of methodologies for analysis, design and programming whose goal is to model the characteristics and behaviors of real world objects. In this chapter we will focus on the programming aspects of Object Technology, more commonly known as Object Oriented Programming.
Object Oriented Programming
Morfik Pascal is built on the most common dialects of Pascal found in the world today, with some added features. Over the recent years Pascal has had great advances as a language and Morfik Pascal reflects that. Through all of its development, one of the main goals in creating Morfik Pascal was to provide a powerful, yet easy to use language for the development of Web applications. Towards that end, Morfik Pascal was developed into a powerful object oriented language which can hold its own against languages that are much more widely known for their OOP features.
Today, no large scale application development project is undertaken without the use of OOP. All major tools and languages support the concepts and they have been widely accepted into mainstream for several years.
In order to be considered Object Oriented, a language must have built-in support for three concepts:
Encapsulation – Modularization and the ability to hide implementation details.
Inheritance – Defining characteristics and implementation objects based on pre-existing classes so that code can be easily and successfully reused
Polymorphism – the same message, when sent to different objects provokes different responses, depending on the nature of the object that receives the message.
What Are Objects?
Objects are models we create to represent real world entities in software design. In everyday life you are surrounded by object all the time. Look around and you will see: cars, refrigerators, chairs, cats and dogs. All of these are objects.
Software applications also have objects such as buttons in the interface, grids and their cells, menus, string lists, etc. These objects, just as their real world counterparts, have states and behaviors. You can represent these characteristics (and their change over time – ie. states) and behaviors with software structures which are generally called objects.
In its daily comings and goings a car can be modeled as a car. An object has characteristics, which change over time defining its states (speed, aim, fuel consumption, etc) and behaviors (engine off, engine on, accelerate, turn left, break, etc.).
You drive to the office where you work with clients. The way you interact with clients can, also, be modeled through an object. The client also has characteristics (Current Phone, Current Address, etc) and behaviors (Move, Close Deal, etc).
In programming, an object’s characteristics and state are defined by its instance variables (sometimes called member variables, or fields). Instance variables should always be private so as to not be accessible from outside of the object. Public instance variables are accessible by code which does not belong to the object’s class. This should be avoided as we will see when looking into encapsulation.
The behaviors of an object are defined by its methods. Methods handle all interaction with the object and operate on the instance variables, altering the object’s state and even creating new objects.
In figure 1 you can see a simple graphical representation of an object. This picture represents the conceptual structure of an object, which is very similar to that of a cell. As in a cell, an external structure (the membrane) is the only part to have contact with the outside world and protects the nucleus.
|Figure 1: Graphical representation of an Object.|
The object's instance variables are encapsulated within it, surrounded by the object’s methods. Except on very specific cases the object's methods should be the only the only venue available for other objects to access or alter instance variables. In Morfik Pascal it is possible to have instance variables declared as public or published, except where absolutely necessary (like for IDE interaction with a Form's controls) this should be avoided as these variables will be globally available to other objects. Access levels and member visibility will be seen in greater detail in this chapter.
The basic idea behind the concept of Encapsulation is that the less that a user of objects you create knows about how they are implemented, the less he/she will be affected by changes to that implementation.
A person that knows nothing about cars does not conclude anything about certain noises that the engine makes. A person with a bit more experience could be tempted to guess at the cause of a problem based on such noise and be frustrated by a new type of engine.
Please, don’t image that this will help in hiding problems with your code. The goal is to avoid problems by avoiding a situation where developers create code that depends on how your objects are implemented. This allows you to change how the objects are implemented without adversely affecting code created by third parties.
To put it simply, inheritance is the capability of creating new types of objects (called Classes) from previously existing types. With inheritance we re-use a previously existing class’ (ancestor) functionality, add new functionality and, if necessary, alter some of the class’ behavior, thus creating a new Class (descendant) with a huge part of its functionality already tried and tested.
Before the advent of object oriented programming and the widespread use of inheritance, code reuse was mostly done by the creation of function libraries or by the old cut-and-paste method. The cut-and-paste approach to code reuse normally requires that the copied code suffer small modifications and is very prone to introducing bugs in an application.
Polymorphism is the ability to treat objects of different types as if they were all of the same type, provided that they have a common ancestor. An example of this capability is that I can write a function which takes a Form object as a parameter and then pass any of the forms in an XApp to that function. This is possible because all forms in an XApp are descendant classes of the Form class.
Morfik Pascal has full support for object oriented programming as it implements the Object Pascal concepts and constructs as adopted by Delphi and FreePascal which are the most commonly used Pascal compilers..
Classes are essentially the templates from which we create new objects in our applications. A class represents the definition of what instance variables and methods a certain type of object will have. A class is not an object in itself, but the recipe to create new objects. Actual objects are made by creating them from a class definition in a process called instantiation. This is very similar to how you can create several cakes from the same recipe.
In the Morfik Framework, except for a few of the higher level classes such as the Form and visual controls, all class names are prefixed with the letter "T". This is a convention from the Pascal world which was adopted by Morfik its purpose is to make it simpler to differentiate when you are dealing with a class and when you are dealing with an object (an instance of a class).
Class declarations in Morfik Pascal are very similar to their counterparts in Delphi and FreePascal, in keeping with the spirit of making the learning of the syntax as easy as possible for developers coming from a previous Pascal background. You can see a class declaration in Listing 1.
Listing 1 – A class declaration in Morfik Pascal
Unit ClassDecl; Interface Type BaseClass = Class(TObject) Private fSomething : String; Public constructor Create(aSomething : String); Destructor Destroy; override; Function GetSomething: String; Virtual; End; Implementation Constructor BaseClass.Create(aSomething : String); Begin Inherited create; fSomething := aSomething; ShowMessage('creating BaseClass...'); End; Destructor BaseClass.Destroy; Begin ShowMessage('destroying BaseClass...'); Inherited Destroy; End; Function BaseClass.GetSomething : String; Begin Result := fSomething; End; End.
The class presented in this sample is a very simple one.
The first line of code in Listing 1 reads as:
BaseClass = Class(TObject)
This informs the compiler that the class called BaseClass is a descendant of the TObject class
Member elements of a class can be variables, functions and procedures as well as properties. Variables declared inside a class are called instance variables (or fields, or member variables), while functions and procedures are called methods. We will see more about properties further ahead in this chapter.
All members of a class can be declared with one of several scope modifiers. These include: Private, Protected, Public and Published. The following table describes the scope defined by each of these modifiers.
|Private||Restricted to the current class, but can be accessed by other elements declared within the same module.|
|Protected||Restricted to the current class, but can be accessed by other elements declared within the same module and from within descendants of this class.|
|Public||Accessible from all modules in the project, which have imported the current module.|
|Published||The same as Public but used for the declaration of Controls and Event Handlers which are accessed by the visual editors within the Morfik IDE.|
Method and instance variable declarations using these modifiers take the following form: Private Function Test : String; Protected Function Test : String; Public Function Test : String; Published procedure MyEventHandler(Event : TDOMEvent); Message; Public Function Test: String; Function Test2: String; Public Str1 : String; Int2 : Integer; Private Int3 :Integer;
Note that methods which are created to handle events triggered by controls are not only declared as Published but they also get an additional modifier: "message".
|Note:||In Morfik Pascal it is not necessary to inform an access modifier for each element in a class. You can use the modifier by them selves to specify the start of a section of the class declaration which will have that specific access level. The section will end where another access modifier is found, starting a new section.|
Instantiating Objects from a Class
The process of creating objects is called instantiation. We instantiate an object based on a class definition. To better illustrate this, let’s take a simple example. In listing 2 you can see the definition of a simple class, called TPoint. This class defines objects capable of describing a point in a two dimensional space.
Listing 2 – TPoint Class definition.
Unit geometry; TPoint = Class(TObject) Private X : Integer; Y : Integer; Public Procedure Move(XValue, YValue : integer); End; Procedure TPoint.Move(XValue, YValue : integer); Begin X := XValue; Y := YValue; End; Function NewPoint : TPoint; Var Pt : TPoint; Begin Pt := TPoint.Create; Pt.Move(10, 10); Result := P; End; End.
Notice that in Listing 2 there is a function called NewPoint. This function instantiates a new TPoint object through the use of the New operator and assigns it to a local variable called Pt. Once the object has been created its methods can be called as the call to the Move method demonstrates. This call, based on the methods definition, should set the X and Y instance variables of the object to the value of 10.
Message Passing and Methods
If object A wants object B to perform some action on its behalf, in OT jargon, it should send a message to object B. The message is sent by calling one of object B’s methods. It should be noted that calling a method in Morfik Pascal is very similar to calling a sub routine or a function. In some programming languages methods are called member functions of a class. This should give you a general idea of how close the concepts are.
|Figure 2: Message sending/Method invocation between objects|
Constructors and Destructors
Class instance variables normally require some initialization code and, also frequently, some clean up code. This initialization and clean up are done through two special kinds of methods: Constructors and Destructors. Constructors are the methods which are called when a class instance variable is created, while destructors are called whenever a class instance is destroyed.
Listing 3 – A Morfik Pascal class implementing a Constructor and a Destructor.
Type TestClass = Class Public Constructor Create; Destructor Destroy; Override; End; Constructor TestClass.Create; Begin Inherited Create; //call to the ancestor's Constructor //... instance initialization End; Destructor TestClass.Destroy; Begin //... instance clean up code Inherited Destroy; // call to the ancestor's Destructor End;
From within a Morfik Pascal class method you can call a method that is inherited from the ancestor (also called Base) class, through the use of the inherited keyword as shown in Listing 3.
Morfik Pascal supports having more than one method, within the same class, with the same name, as long as they have different parameter lists. The different methods are implicitly identified by the compiler through the complete method signature which is composed of the method name plus its parameter list.
Let us bring together the concepts of a constructor and of method overloading to demonstrate how they can be used. In Listing 4 we extend a bit the definition of our TPoint class to include two constructors, thus using method overload. We also introduce a second function, called NewPoint2, for the creation of TPoint objects. This second function uses the constructor definition that takes two integers as parameters, instead of calling the one that takes no parameters and then calling the Move method.
Listing 4 – TPoint Class definition, revised.
Unit geometry; Interface Ttype TPoint = Class(TObject) Private X : Integer; Y : Integer; Public Procedure Move(XValue, YValue : Integer); Constructor Create; Overload; Constructor Create (XValue, YValue : Integer); Overload; End; Implementation Procedure TPoint.Move(XValue, YValue : Integer); Begin X := XValue; Y := YValue; End; Constructor TPoint.Create; Overload; Begin Inherited Create; X := 1; Y := 1; End; Constructor TPoint.Create (XValue, YValue : Integer); Begin Inherited Create; X := XValue; Y := YValue; End; Function NewPoint: TPoint; Var Pt : TPoint; Begin Pt := TPoint.Create; Pt.Move(10, 10); Result := P; End; Function NewPoint2: TPoint; Var Pt : TPoint; Begin Pt := TPoint.Create(10, 10); Result := P; End; End.
Forward Class Declaration
There are situations when one class in your application needs to reference another class which, in turn references the first one. When working with Morfik you always need to have any data types declared prior to their use, which would make this scenario impossible. In order to work around this problem you can use what is called a forward class declaration as can be seen in Listing 5.
Listing 5 – Usage of Forward Class Declarations
Unit ClassForwardDeclarations; Interface Type Class1 = Class; Class2 = Class Field1 : Class1; End; Class1 = Class Field1 : Class2; End; Implementation End.
The insertion of the line with the forward declaration, as show below, satisfies the compiler’s need to know all referenced type before their usage.
Class1 = class;
At this point the compiler knows that Class1 is a class and that it will be detailed later. If the actual declaration is not found when the application is compiled an error will result.
Properties look very much like instance variables and are used as those, however they are actually implemented through two Subroutines, one for reading and one for setting the value that is associated with them. Normally a property will be directly related with an instance variable which holds its value.
Why use properties and not just plain instance variables? Properties have their values set and read through what we call access methods giving us encapsulation of the underlying instance variable and allowing us to take whatever appropriate action whenever their value is read or changes. In listing 6 you can see how to add properties to a class, with Morfik Pascal.
Listing 6 – TPoint class with added properties.
Unit Geometry; Type TPoint = class(TObject) Private FX : Integer; FY : Integer; Protected Function GetX:Integer; Procedure SetX(Value:Integer); Function GetY:Integer; Procedure SetY(Value:Integer); Public Property X : Integer Read GetX Write SetX; Property Y : Integer Read GetY Write SetY; Procedure Move(XValue, YValue : Integer); Constructor Create; overload; Constructor Create (XValue, YValue : Integer); Overload; End; Implementation Procedure TPoint.Move(XValue, YValue : Integer); Begin X := XValue; Y := YValue; End; Constructor TPoint.Create; Overload; Begin Inherited Create; X := 1; Y := 1; End; Constructor TPoint.Create (XValue, YValue : Integer); Begin Inherited Create; X := XValue; Y := YValue; End; Function NewPoint: TPoint; Var Pt : TPoint; Begin Pt := TPoint.Create; Pt.Move(10, 10); Result := P; End; Function NewPoint2: TPoint; Var Pt : TPoint; Begin Pt := TPoint.Create(10, 10); Result := P; End; Function TPoint.GetX: Integer; Begin Result := FX; End; Procedure TPoint.SetX(value: Integer); Begin FX := Value; End; Function TPoint.GetY: Integer; Begin Result := FY; End; Procedure TPoint.SetY(value: Integer); Begin FY := Value; End; End.
|Note:||In Morfik Pascal you can have properties which are read only or write only. This is done by simply not specifying the underlying function or procedure which would handle that task for a specific property.|
Read Only properties
You can create a read only property in a class by simply not specifying a write method for the property as shown in the following code snippet.
Public Property Name : String read GetName;
Write Only Properties
You can create a write only property in a class by simply not specifying a read method for the property as shown in the following code snippet.
Public Property Name : String write SetName;
The Self Parameter
Every method in a class receives an invisible parameter called Self. This parameter is a reference to the exact object through which the method is being called and can be used to disambiguate variable names. Suppose that in a class with an instance variable called FX, you had a method with a local variable called FX; how do you differentiate between them? You can see a sample of how to use the Self parameter in listing 7.
Listing 7 – Usage of Self in the TPoint class.
Unit Geometry; Type TPoint = class(TObject) Private FX : integer; Public Function GetX: integer; Procedure SetX(value: integer); Property X : integer read GetX write SetX; End; Implementation Function TPoint.GetX: integer; Var FX: integer; Begin FX := 2*15; // assigns value to the local variable // unrelated code Result := Self.FX; End; Procedure TPoint.SetX(value: integer); Begin FX := Value; End; End.
Class References (Metaclasses)
Class references allow us to perform operations on Classes instead of on objects (class instances). This is very useful when you need dynamically choose what kind of class you are going to use to instantiate an object.
In Listing 8 you can see a function which based on a string parameter, the kind of food an animal eats, chooses which class of animal to instantiate.
Listing 8 – Class Reference declaration.
Unit Animals; Interface Type Animal = Class Public Class Function FoodType: String; virtual; End; AnimalClass = class of Animal; mouse = Class(Animal) Public Class Function FoodType: String; Override; End; cat = Class(Animal) Public Class function FoodType: String; Override; End; dog = Class(Animal) Public Class function FoodType: String; Override; End; Function GetAnimalWithFoodType(food : String) : AnimalClass; Procedure TestAnimal; Implementation Class function Animal.FoodType: String; Begin Result := ''; End; Class function Mouse.FoodType: String; Begin Result := 'cheese'; End; Class Function Cat.FoodType: String; Begin Result := 'fish'; End; Class function Dog.FoodType: String; Begin Result := 'slippers'; End; Function GetAnimalWithFoodType(food : String) : AnimalClass; Begin If food = mouse.FoodType Then Result := mouse Else If food = cat.FoodType Then Result := cat Else If food = dog.FoodType Then Result := dog End; Procedure TestAnimal; Var MyPetClass : AnimalClass; MyPet : Animal; Begin MyPetClass := GetAnimalWithFoodType('cheese'); MyPet := MyPetClass.Create; MyPet.Free; End; End.
Inheritance as we have seen in a method through which we can define new and more sophisticated classes of objects, based on pre-existing classes. Let us consider another simple animal example. Most species of felines are generally referred to as cats or big cats. One could conceive that if we had an object representation of a cat it would be a good starting place to create an object representation for, say, a lion. In Morfik Pascal, as we have seen when we previously discussed class declarations you can specify class inheritance when you declare a new class.
In listing 9 you can see a small section of code with the declaration of the Lion class as a descendant from the Cat class.
Listing 9 – Lion and Cat classes
Override Cat = class // Cat member variables and methods End; Lion = class(Cat) // Lion additions to the members of the Cat class End;
In keeping to a simplistic view we can suppose that the Cat class would have all the instance variables and methods to correctly describe the characteristics and behaviors of a generic feline. This being the case all our Lion class would have to redefine is how it describes its visual representation to the outside world.
|Note:||Note that by specifying a class name in parenthesis when we declare a class you define that your class is a descendant of the class referenced.|
To more clearly exemplify this extension by inheritance method of attacking code reuse, we will examine a TPoint3D class which will be a descendant of the TPoint class we have seen previously in this chapter. The TPoint3D class will simply add an extra coordinate Z which is necessary to plot a point in three dimensional space.
Listing 10 – The TPoint3D class definition.
TPoint3D = Class (TPoint) Private FZ : integer; Protected Function GetZ : integer; Procedure SetZ(value: integer); Public Property Z : Integer Read GetZ Write SetZ; Constructor Create(XValue, YValue, ZValue : integer); Overload; Procedure Move(XValue, YValue, ZValue : integer); Overload; End; //... Constructor TPoint3D.Create(XValue, YValue, ZValue : integer); Begin inherited Create(XValue, YValue); FZ := ZValue; End; Procedure TPoint3D.Move(XValue, YValue, ZValue : integer); Begin Inherited Move(XValue, YValue); FZ := ZValue; End; Function TPoint3D.GetZ : integer; Begin Result := FZ; End; Procedure TPoint3D.SetZ(value: integer); Begin FZ := Value; End;
Since the instance variables of our original TPoint class were all declared as private, we can’t access them even within a descendant class. To work around this limitation we can use the preexisting ones, as you can see in listing 10’s implementation of the Move method for the TPoint3D class which calls the Move method of its ancestor class and thus sets the X and Y coordinates before setting its own Z coordinate.
|Note:||If there is a possibility that complex descendants of a class you are writing will be created, you should make the instance variables protected, instead of private.|
A method can be declared as Virtual by adding the Virtual modifier to its declaration in the class. When a Virtual method is called the real type of the object contained in the variable and not the type of the variable itself is used to identify which method is called.
When you assign a Cat object to a variable of type Animal (ancestor of Cat) and you call a method through that variable if the method was not marked as Virtual the version of the method which will get called will be the one defined in the Animal class. If the method was originally marked as Virtual and is redefined in the Cat class, however, it will be the version introduced in the Cat class that will be called.
When creating a descendant class from a class which has methods marked as Virtual you can redefine those methods and be sure that the correct version of the methods will always get called by adding the overrides modifier to the new method declarations.
In Listing 11 you can see a new version of the Animals module with a Virtual Eat method added to the Animals class and reintroduced with the override modifier in the Cat and Dog classes.
Listing 11 – Animals module with virtual and override methods introduced into the Animals, Cat and Dog classes.
Unit Animals; Interface Type Animal = class Protected QtyEaten: integer; Public class function FoodType: String; virtual; procedure Eat; virtual; End; AnimalClass = class of Animal; mouse = class(Animal) Public Class function FoodType: String; override; Procedure Eat; override; End; cat = Class(Animal) Public Class function FoodType: String; override; Procedure Eat; override; End; dog = class(Animal) Public Class function FoodType: String; override; Procedure Eat; override; End; Function GetAnimalWithFoodType(food : String) : AnimalClass; Procedure TestAnimal; Implementation Class function Animal.FoodType: String; Begin Result := ''; End; Procedure Animal.Eat; Begin QtyEaten := QtyEaten + 1; End; Class function Mouse.FoodType: String; Begin Result := 'cheese'; End; Procedure Mouse.Eat; Begin QtyEaten := QtyEaten + 1; End; Class function Cat.FoodType: String; Begin Result := 'fish'; End; Procedure Cat.Eat; Begin QtyEaten := QtyEaten + 1; End; Class function Dog.FoodType: String; Begin Result := 'slippers'; End; Procedure Dog.Eat; Begin QtyEaten := QtyEaten + 1; End; Function GetAnimalWithFoodType(food : String) : AnimalClass; Begin If food = mouse.FoodType Then Result := mouse Else If food = cat.FoodType Then Result := cat Else If food = dog.FoodType Then Result := dog End; Procedure TestAnimal; Var MyPetClass : AnimalClass; MyPet : Animal; Begin MyPetClass := GetAnimalWithFoodType('cheese'); MyPet := MyPetClass.Create; MyPet.Free; End; End.
In Morfik Pascal you can create methods without any implementation; these are generally called abstract methods. These methods are placeholders, usually designed to ensure that all descending classes have such a method. A call to such a method will result in a runtime error. All classes which wish to make use of this method must override the inherited method.
An abstract method should have the modifier Abstract applied to it. The Animals module which is shown in Listings 8 and 11 has an example where such a method could be used in the Animal class: the FoodType shared function. Notice that this method returns an empty string and essentially functions as a placeholder to be overridden in descendant classes. If the Abstract modifier where used the declaration would look like this:
Class Function FoodType : String; Abstract;
|Note:||Abstract methods are especially interesting when you a creating a class for the sole purpose of being the common ancestor of two or more classes.|
It is important to note, however, that in Morfik 07 abstract methods are not supported in browser side code.
Class methods are methods which are declared with the Class modifier and which can be invoked through a class reference, without the need to actually have an instance variable. You can see an example of this in the Animals sample module used to explain class references. See listings 8 or 11 for a full example. Next you can see the header of a class function extracted from that listing.
Class Function FoodType: String;
One use of Class methods can be to group together a set of functions which are related to a certain subject but which would not necessarily constitute an object in itself. In Listing 12 you can see an example of what such a class would look like and a small test subroutine showing how to use it.
Listing 12 – A class with all methods marked as class methods.
TString = class Class Function length(Str : String) : integer; Class Function UpperCase(Str : String) : String; Class Function LowerCase(Str : String) : String; End; //... Class Function length(Str : String) : Integer; Begin Result := Length(Str); End; Class function UpperCase(Str : String) : String; Begin Result := UpperCase(Str); End; Class Function LowerCase(Str : String) : String; Begin Result := LowerCase(Str); End; Procedure TestTSringClass; Var Str : String = 'my test string'; StringLength : Integer; Begin StringLength := TString.Length(Str); Str := TString.UpperCase(Str); End;
|Note:||Initialized variables as seen in procedure TestTStringClass of Listing 12 will not work if you have chosen to use Delphi as your platform backend compiler.|
If a Helper Method is declared with the same name of a method which is declared within the class, the class' internal method will have precedence and hide the Helper Method. Helper Methods cannot then, be used to change established class behavior as that would violate encapsulation of the class. In line with this respect to class encapsulation a Helper Method cannot access properties of a object which are not declared as public in its class, even if it is being attached to that class.
Two main purposes guided the creation of Helper Methods. The simplest of them was to allow for better usage of code completion by developers when working with fundamental types such as strings, integers, etc. The second was to allow seamless integration of new data types and classes to the entire Morfik Framework. An example of the later would be, for example, to create a complex number data type and then have a new method, called "ToComplex" attached to the string type. In this case, all string variables would automatically expose the functionality of converting their values to complex numbers.
|Note:||Helper Methods were introduced in the Morfik 2 release cycle and are only available in Morfik, version 2 or higher.|
Wrapping it up
Morfik Pascal offers a wide range of Object Oriented technology features which allow developers to be very productive. Most features found in today’s most used programming languages are a part of Morfik Pascal and you should feel free to explore new possibilities.