We will start our trip through the world of Object Oriented Programming (OOP) with Morfik Basic, right at the beginning, with a quick review of what exactly is OOP. This article will provide you with a general overview of Morfik Basic.
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 Basic is built on the most common dialects of Basic found in the world today, with some added features. Over the recent years Basic has had great advances as a language and Morfik Basic reflects that. Through all of its development, one of the main goals in creating Morfik Basic was to provide a powerful, yet easy to use language for the development of Web applications. Towards that end, Morfik Basic was developed into a powerful object oriented language which can hold its own against languages which much more widely know for their OOP features.
Today, no large scale application development project is undertaken without the use of OOP. All major tools and language 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 comes 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 the 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 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 Basic 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 know 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 that 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 functionalities 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 introduce 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 Basic has full support for object oriented programming. When adapting Basic to work in the Morfik universe, a large number of features where added to the language. Taking into consideration that Microsoft’s implementation of Basic is the most used one, where a specific OOP feature had been added by Microsoft in doing the move from Visual Basic (VB) 6 to Visual Basic .net, we followed their lead. Where the feature did not exist in VB.net Morfik introduced it trying to be as true to the original form of the language as possible.
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. Real objects are created 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 Basic are very similar to their counterparts in Visual Basic, in keeping with the spirit of making the learning of the syntax as easy as possible for developers coming from a Basic background. You can see a class declaration in Listing 1.
Listing 1 – A class declaration in Morfik Basic
Public Class BaseClass : Inherits TObject Private fSomething As String Public Sub New(aSomething As String) MyBase.New fSomething = aSomething ShowMessage("creating BaseClass...") End Sub Public Sub Dispose ShowMessage("destroying BaseClass...") MyBase.Dispose End Sub Public Overridable Function GetSomething As String Return fSomething End Function End Class
The class presented in this sample is a very simple one.
The first line of code in Listing 1 reads as:
Public Class BaseClass : Inherits TObject
This informs the compiler that the class called BaseClass is a descendant of the TObject class. This could also be written in the following manner, to the same effect:
Public Class BaseClass Inherits TObject
Note that when the Inherits clause is not in the same line as the class name, you do not need to use the colon.
|Note:||The colon is a Basic language feature that allows you to place more than one command in a single line of code, so this behavior is not exclusively for class declarations. It is, however, quite frequently used in class declarations.|
Member elements of a class can be variables, functions and subs as well as properties. Variables declared inside a class are called instance variables (or fields, or member variables), while functions and subs are called methods. We will see more about properties further ahead in this chapter.
All members of a class can be declared with on of several scope modifiers. These include: Private, Protected, Public and Published. The following table describes de 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 declarations using these modifiers take the following form:
Private Function Test As String protected Function Test As String Public Function Text As String published message Sub MyEventHandler(Event As TDOMEvent)
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".
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 lets 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.
Namespace geometry Public Class TPoint : inherits TObject Private X As Integer Private Y As Integer Public Sub Move(XValue, YValue As Integer) X = XValue Y = YValue End Sub End class Public Function NewPoint() As TPoint Dim Pt As TPoint Pt = New TPoint Pt.Move(10, 10) Return P End Function End Namespace
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 Basic 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 Basic class implementing a Constructor and a Destructor.
Public Class TestClass Public Sub new() MyBase.New ' call to the ancestor's Constructor '... instance initialization End Sub Public Sub dispose() '... instance clean up code MyBase.Dispose ' call to the ancestor's Destructor End Sub End class
From within a Morfik Basic class method you can call a method that is inherited from the ancestor (also called Base) class, through the use of the MyBase identifier as shown in Listing 3.
Morfik Basic supports having more than one method, within the same class (method overloading), 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 out 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.
Namespace geometry Public Class TPoint : Inherits TObject Private X As Integer Private Y As Integer Public Sub Move(XValue, YValue As Integer) X = XValue Y = YValue End Sub Public Overloads Sub New () MyBase.New() X = 1 Y = 1 End Sub Public overloads Sub New (XValue, YValue As Integer) MyBase.New() X = XValue Y = YValue End Sub End Class Public Function NewPoint() As TPoint Dim Pt As TPoint Pt = New TPoint Pt.Move(10, 10) Return P End Function Public Function NewPoint2() As TPoint Dim Pt As TPoint Pt = New TPoint(10, 10) Return P End Function End Namespace
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.
Namespace ClassForwardDeclarations Public forward Class Class1 Public Class Class2 Public Field1 As Class1 End Class Public Class Class1 Public Field1 As Class2 End class End Namespace
The insertion of the line with the forward declaration, as shown below, satisfies the compiler’s need to know all referenced type before their usage.
Public Forward Class Class1
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 sub routines, 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 Basic.
Listing 6 – TPoint class with added properties.
Namespace Geometry Public Class TPoint : Inherits TObject Private FX As Integer Private FY As Integer Public Property X As Integer Get Return FX End Get Set FX = X End Set End Property Public Property Y As Integer Get Return FY End Get Set FY = Y End Set End Property Public Sub Move(XValue, YValue As Integer) FX = XValue FY = YValue End Sub Public Overloads Sub New () MyBase.New() FX = 1 FY = 1 End Sub Public Overloads Sub New (XValue, YValue As Integer) MyBase.New() FX = XValue FY = YValue End Sub End Class Public Function NewPoint() As TPoint Dim P As TPoint P = New TPoint P.Move(10, 10) Return P End Function Public Function NewPoint2() As TPoint Dim P As TPoint P = New TPoint(10, 10) Return P End Function End Namespace
|Note:||In Morfik Basic you can have properties which are read only or write only. This is done by simply not creating the code which would otherwise be responsible for handling the operation which will not be performed and specifying either the ReadOnly or WriteOnly modifiers.|
Read Only properties
You can create a read only property in a class by use of the ReadOnly modifier as shown in the following code snippet.
Public ReadOnly property Name As String Get Return fName End Get End Property
Write Only Properties
You can create a write only property in a class by use of the WriteOnly modifier as shown in the following code snippet.
Public WriteOnly property Phone As String Set FPhone = Value End Set End Property
The Me Parameter
Every method in a class receives an invisible parameter called Me. 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 Me parameter in listing 7.
Listing 7 – TPoint class with added properties.
Namespace Geometry Public Class TPoint : Inherits TObject Private FX As Integer Private FY As Integer Public Property X As Integer Get Dim FX As Integer FX = 2*15 'assigns value to the local variable ' unrelated code Return Me.FX 'returns the instance variable End Get Set FX = X End Set End Property . . . End Class End Namespace
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.
Namespace Animals Public Class Animal Public shared Overridable Function FoodType() As String Return "" End Function End Class Public Type AnimalClass Class Of Animal Public Class mouse : Inherits Animal Public shared Overrides Function FoodType() As String Return "cheese" End Function End Class Public Class cat : Inherits Animal Public shared Overrides Function FoodType() As String Return "fish" End Function End Class Public Class dog : Inherits Animal Public shared Overrides Function FoodType() As String Return "slippers" End Function End Class Public Function GetAnimalWithFoodType(food As String) As AnimalClass if food = mouse.FoodType Then Return mouse ElseIf food = cat.FoodType Then Return cat ElseIf food = dog.FoodType Then Return dog End If End Function Public Sub TestAnimal() Dim MyPetClass As AnimalClass Dim MyPet As Animal MyPetClass = GetAnimalWithFoodType("cheese") MyPet = New MyPetClass MyPet.Free End Sub End Namespace
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 Basic, 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
Public Class Cat ' Cat member variables and methods End Class Public Class Lion : Inherits Cat ' Lion additions to the members of the Cat Class End Class
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 typing Inherits AncestorClassName as the first element in a class declaration is how you define that you class in a descendant of the class referenced.|
To more clearly exemplify this extension by inheritance method of attacking core 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.
Public Class TPoint3D : Inherits TPoint Private FZ As Integer Public property Z As Integer Get Return FZ End Get Set FZ = Z End Set End Property Public Overloads Sub New(XValue, YValue, ZValue As Integer) MyBase.New(XValue, YValue) FZ = ZValue End Sub Public Overloads Sub Move(XValue, YValue, ZValue As Integer) MyBase.Move(XValue, YValue) FZ = ZValue End Sub End Class
The TPoint3D class adds an instance variable to the original TPoint class and overloads some the methods in the TPoint class adding new versions which take three parameters.
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 overridable by adding the overridable modifier to its declaration in the class. When an overridable 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 overridable 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 overridable 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 overridable 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 an overridable Eat method added to the Animals class and reintroduced with the overrides modifier in the Cat and Dog classes.
Listing 11 – Animals module with overridable and overrides methods introduced into the Animals, Cat and Dog classes.
Public Class Animal protected QtyEaten As Integer Public overridable Sub Eat () QtyEaten = QtyEaten + 1 End Sub Public shared MustOverride Function FoodType() As String End Class Public type AnimalClass Class of Animal Public Class mouse : Inherits Animal Public shared Overrides Function FoodType() As String Return "cheese" End Function End Class Public Class cat : Inherits Animal Public overrides Sub Eat () QtyEaten = QtyEaten + 2 End Sub Public shared Overrides Function FoodType() As String Return "fish" End Function End class Public Class dog : Inherits Animal Public overrides Sub Eat () QtyEaten = QtyEaten + 5 End Sub Public shared Overrides Function FoodType() As String Return "slippers" End Function End class Public Function GetAnimalWithFoodType(food As String) As AnimalClass if food = mouse.FoodType Then Return mouse ElseIf food = cat.FoodType Then Return cat ElseIf food = dog.FoodType Then Return dog End if End Function Public Sub TestAnimal() dim MyPetClass As AnimalClass dim MyPet As Animal MyPetClass = GetAnimalWithFoodType("cheese") MyPet = New MyPetClass MyPet.Free End Sub End Namespace
MostOverride (Abstract) Methods
In Morfik Basic 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 MustOverride 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 MustOverride modifier where used the declaration would look like this:
Public shared MustOverride Function FoodType as string
|Note:||Abstract or MustOverride methods are specially 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 or MustOverride methods are not supported on browser side code.
Shared methods are methods which are declared with the Shared 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 shared function extracted from that listing.
Public shared Overrides Function FoodType() As String
One use of Shared 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 sub routine showing how to use it.
Listing 12 – A class with all methods marked as shared.
Public Class TString Public shared Function length(Str As String) As Integer Return Length(Str) End Function Public shared Function UpperCase(Str As String) As String Return UpperCase(Str) End Function Public shared Function LowerCase(Str As String) As String Return LowerCase(Str) End Function End class Public Sub TestTSringClass Dim Str As string = "my test string" Dim StringLength As Integer StringLength = TString.Length(Str) Str = TString.UpperCase(Str) End Sub
|Note:||Initialized variables as seen in Sub TestTStringClass of Listing 12 will not work if you have chosen to use Delphi as your platform backend compiler.|
Helper Methods can be attached to any data type, however, as classes already have methods of their own it is important to consider how they interact.
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 Basic 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 Basic and you should feel free to explore new possibilities.