Written by rdc
FreeBasic is moving towards implementing Object Oriented programming. While classes have not yet been added to the language, the Type definition has been extended to include some Object Oriented constructs as a first step towards full class support. This article introduces some of the concepts of Object Oriented design and explains some of the extended type constructs.
Object Oriented Programming, usually shortened to OOP, is a methodology that enables the programmer to build code units called objects. An object is a thing; it is a unit of code that represents something that needs to be manipulated in a program. You can think of an object as a noun: a person, place or thing. An object could be a sprite, a drawing primitive or something more elaborate like a tank in a game. Any concrete entity that has a set of characteristics and actions can be represented as an object.
An object contains both the data needed by the object, and the methods (subroutines and functions) that act on the data. This grouping of data and methods into a single entity is called encapsulation. Encapsulation allows you to create modular units that can be reused in multiple programs. This idea of code reuse was the main motivation in the creation of the OOP paradigm.
Another beneficial consequence of encapsulation is information hiding. The data inside the object is shielded from the outside world so that unwanted changes to the data cannot occur. Instead of accessing a variable directly, the object has a public interface that the external program should use to access and change data members. By using an interface, you can control how the object behaves and ensure that its operation is consistent across many programs.
The interface also allows you to make internal changes to the code, without changing the way the object is accessed. As long as you don’t change the published interface, that is change any existing public methods, you can improve the object without breaking any existing code that relies on the object. In the cases where a program my need an improved method, you can leave the old method in place to maintain compatibility, and just add a new method with the improved functionality. New programs can use the new method, while old programs can still use the old method.
Another advantage of using a public interface is so that other programmers can use your object without worrying about the internal details of the object. As long as the published interface is stable and well documented, anyone should be able to use your object, even beginners.
As already stated, OOP was designed to enable code reuse among programmers. In order for code reuse to be helpful, the published interface must remain stable. That is, once an object has been released and is being used in programs, the published interface should not change so that programs that use the object continue to work correctly. There is an implicit contract between you as the author of the object and the end-user of your object that you will maintain the published interface across changes that may need to be made to the object. This implicit contract between author and user is the main strength of the OOP paradigm, and is the main reason that OOP has become such a powerful programming methodology.
As already mentioned, an object contains both data and methods. The data describes the properties of an object, while the methods describe what the object can do. A simple, and not-really-useful example will illustrate this concept.
Suppose you want to create an object that draws a rectangle on the screen. A rectangle can have several properties that would be contained within the data members of the object. A rectangle has an origin on the screen, normally the top left corner, which can be represented by x and y data members. A rectangle has a width and a height, so the object would have width and height data members. A rectangle can either be outlined or filled, so a filled flag data member can be added to the object. Of course, if you are going to draw a rectangle, you will want to draw it in a particular color, so the object will need to have a color data member, and to have the object be a bit more flexible, you can add a color member for the outline and a different color member for the fill. Of course you will need a method to actually draw the rectangle on the screen, so you can add a draw routine to the object definition.
So our rectangle object has the following preliminary properties and methods:
Property: x and y origin
Property: width
Property: height
Property: filled
Property: outline color
Property: fill color
Method: DrawRectangle
This list is called the object definition. In FreeBasic you define an object using the extended Type definition. The extended Type is similar to the standard Type, with some added language constructs that implements a subset of OOP features.
The following code snippet is a partial rectangle definition:
Type myRect
Private:
X_ As Integer
Y_ As Integer
Width_ As Integer
Height_ As Integer
Filled_ As Integer
Otlncolor_ As Integer
Fillcolor_ As Integer
Public:
Declare Sub DrawRect()
End Type
As you can see, the extended Type looks much like a standard Type except for the Private: and Public: keywords and the sub declaration. The Private: keyword tells the compiler that the data members that follow are private to the type, that is cannot be accessed outside of the type. The private state extends to all object members until a new qualifier is encountered, which in this case is the Public: qualifier just above the Sub declaration. All of the data members are hidden from the outside world and cannot be changed from outside the scope of the Type, a process called information hiding. The underscore appended to the private variables is the common way to define private variables.
Information hiding is a way to maintain the integrity of the object. You should never allow an outside process to directly access a data member. All data access should be through the use of Property members so that you can control what is being passed to your object. Strict control over your object’s data will help prevent many errors that may occur when a programmer uses your object.
Type myRect
Private:
X_ As Integer
Y_ As Integer
Width_ As Integer
Height_ As Integer
Filled_ As Integer
Otlncolor_ As Integer
Fillcolor_ As Integer
Public:
Declare Sub DrawRect()
Declare Property X(ByVal xx_ As Integer)
Declare Property X() As Integer
Declare Property Y(ByVal yy_ As Integer)
Declare Property Y() As Integer
Declare Property Width(ByVal w_ As Integer)
Declare Property Width() As Integer
Declare Property Height(ByVal h_ As Integer)
Declare Property Height() As Integer
Declare Property Filled(ByVal f_ As Integer)
Declare Property Filled() As Integer
Declare Property Otlncolor(ByVal oc_ As Integer)
Declare Property Otlncolor() As Integer
Declare Property FillColor(ByVal fc_ As Integer)
Declare Property FillColor() As Integer
End Type
The Declare statements following the Public: qualifier comprises the public interface to your object. Since the variables of the type are defined with the Private: keyword, the only way to access the variables is through the Property members maintaining the integrity of the object. Since you define the code in each Property member, you have full control over what is being put into your object. A common example of this is to put range checking code in your property members so that the object does not contain invalid data.
In this example, the variables can be both written and read. The compiler distinguishes between a read Property and a write Property by the type of the method. A subroutine-formatted Property is a write property since you are passing a value that will be saved in a private variable. A function-formatted Property is a read property since a private variable will be returned to the caller. You can create read-only Properties by adding just a function-formatted Property or write-only Properties by just adding a subroutine-formatted Property.
The definition looks complete at this point, but there is a problem. What would happen if some or all of the variables were not initialized? The object would not perform correctly and potentially generate a runtime error. It would be better to have a set of default values for the object variables just in case one or more variables did not get initialized. You can initialize the object at the moment of creation by using a Constructor.
A Constructor is a subroutine that is called when the object is created using the Dim (or New) statement. Constructors are useful for initializing an object, either with default values, or values you pass to the Constructor. The updated type definition now looks like the following:
Type myRect
Private:
X_ As Integer
Y_ As Integer
Width_ As Integer
Height_ As Integer
Filled_ As Integer
Otlncolor_ As Integer
Fillcolor_ As Integer
Public:
Declare Sub DrawRect()
Declare Property X(ByVal xx_ As Integer)
Declare Property X() As Integer
Declare Property Y(ByVal yy_ As Integer)
Declare Property Y() As Integer
Declare Property Width(ByVal w_ As Integer)
Declare Property Width() As Integer
Declare Property Height(ByVal h_ As Integer)
Declare Property Height() As Integer
Declare Property Filled(ByVal f_ As Integer)
Declare Property Filled() As Integer
Declare Property Otlncolor(ByVal oc_ As Integer)
Declare Property Otlncolor() As Integer
Declare Property FillColor(ByVal fc_ As Integer)
Declare Property FillColor() As Integer Declare Constructor()
Declare Constructor(xx_ As Integer, yy_ As Integer, w_ As Integer, _
h_ As Integer, f_ As Integer, oc_ As Integer, _
fc_ As Integer)
End Type
You will notice in the definition that we have two Constructors, one that takes a set of parameters and one that doesn’t. This is called overloading and can be used not only with Constructors but also with other subroutines and functions. Overloading is useful for situations where you need to handle different parameter types with a single method call. The compiler will determine which method to call based on the parameters passed to the method. You can overload as many methods as you want, as long as the number and type of parameters for each method is unique.
In this instance, if the Constructor is not passed any parameter values, it will initialize the variables to a set of default values. If the Constructor is called with parameters, then it will use the passed values to initialize the object’s variables.
There is also a Destructor method that is called when the object is destroyed. You can use the Destructor to perform any cleanup tasks that must be carried out before the object is released from memory. If the object created any pointer references, or opened any files, then you would clean up those references in the Destructor. Since the Rectangle object doesn’t create any outside references, a Destructor is not needed.
The type definition is a template for the object type and tells the compiler how to set up the object in memory. However, in order to actually use the object, you need to create the actual method calls, which is shown in the next listing.
Type myRect
Private:
X_ As Integer
Y_ As Integer
Width_ As Integer
Height_ As Integer
Filled_ As Integer
Otlncolor_ As Integer
Fillcolor_ As Integer
Public:
Declare Sub DrawRect()
Declare Property X(ByVal xx_ As Integer)
Declare Property X() As Integer
Declare Property Y(ByVal yy_ As Integer)
Declare Property Y() As Integer
Declare Property Width(ByVal w_ As Integer)
Declare Property Width() As Integer
Declare Property Height(ByVal h_ As Integer)
Declare Property Height() As Integer
Declare Property Filled(ByVal f_ As Integer)
Declare Property Filled() As Integer
Declare Property Otlncolor(ByVal oc_ As Integer)
Declare Property Otlncolor() As Integer
Declare Property FillColor(ByVal fc_ As Integer)
Declare Property FillColor() As Integer
Declare Constructor()
Declare Constructor(xx_ As Integer, yy_ As Integer, w_ As Integer, _
h_ As Integer, f_ As Integer, oc_ As Integer, _
fc_ As Integer)
End Type
Sub myRect.DrawRect()
Line (this.x_, this.y_)-(this.x_ + Width - 1, this.y_ + this.height_ - 1), this.Otlncolor_, B
If this.Filled_ <> 0 Then
Paint (this.x_ + 1, this.y_ + 1), this.Fillcolor_, this.Otlncolor_
End If
End Sub
Property myRect.x(ByVal xx_ As Integer)
this.X_ = xx_
End Property
Property myRect.x() As Integer
Return this.X_
End Property
Property myRect.y(ByVal yy_ As Integer)
this.Y_ = yy_
End Property
Property myRect.y() As Integer
Return this.y_
End Property
Property myRect.Width(ByVal w_ As Integer)
this.Width_ = w_
End Property
Property myRect.Width() As Integer
Return this.Width_
End Property
Property myRect.Height(ByVal h_ As Integer)
this.Height_ = h_
End Property
Property myRect.Height() As Integer
Return this.Height_
End Property
Property myRect.Filled(ByVal f_ As Integer)
this.Filled_ = f_
End Property
Property myRect.Filled() As Integer
Return this.Filled_
End Property
Property myRect.Otlncolor(ByVal oc_ As Integer)
this.Otlncolor_ = oc_
End Property
Property myRect.Otlncolor() As Integer
Return this.Otlncolor_
End Property
Property myRect.FillColor(ByVal fc_ As Integer)
this.Fillcolor_ = fc_
End Property
Property myRect.FillColor() As Integer
Return this.Fillcolor_
End Property
Constructor myRect
this.X_ = 0
this.Y_ = 0
this.Width_ = 10
this.Height_ = 10
this.Filled_ = 0
this.Otlncolor_ = 15
this.Fillcolor_ = 7
End Constructor
Constructor MyRect (xx_ As Integer, yy_ As Integer, w_ As Integer, _
h_ As Integer, f_ As Integer, oc_ As Integer, _
fc_ As Integer)
this.X_ = xx_
this.Y_ = yy_
this.Width_ = w_
this.Height_ = h_
this.Filled_ = f_
this.Otlncolor_ = oc_
this.Fillcolor_ = fc_
End Constructor
The Methods and Properties are defined using the Sub/Function/Property methodname.TypeName syntax. This tells the compiler how to match up methods with the proper type definition. The Constructors are defined with the type name for the same reason. The
this identifier is a hidden parameter that is passed to the methods that refers to the defined type. You use the
this identifier to specify that you want to access the type constructs.
The object is now complete can be used in a program which is listed below.
Type myRect
Private:
X_ As Integer
Y_ As Integer
Width_ As Integer
Height_ As Integer
Filled_ As Integer
Otlncolor_ As Integer
Fillcolor_ As Integer
Public:
Declare Sub DrawRect()
Declare Property X(ByVal xx_ As Integer)
Declare Property X() As Integer
Declare Property Y(ByVal yy_ As Integer)
Declare Property Y() As Integer
Declare Property Width(ByVal w_ As Integer)
Declare Property Width() As Integer
Declare Property Height(ByVal h_ As Integer)
Declare Property Height() As Integer
Declare Property Filled(ByVal f_ As Integer)
Declare Property Filled() As Integer
Declare Property Otlncolor(ByVal oc_ As Integer)
Declare Property Otlncolor() As Integer
Declare Property FillColor(ByVal fc_ As Integer)
Declare Property FillColor() As Integer
Declare Constructor()
Declare Constructor(xx_ As Integer, yy_ As Integer, w_ As Integer, _
h_ As Integer, f_ As Integer, oc_ As Integer, _
fc_ As Integer)
End Type
Sub myRect.DrawRect()
Line (this.x_, this.y_)-(this.x_ + this.Width_ - 1, this.y_ + this.height_ - 1), this.Otlncolor_, B
If this.Filled_ <> 0 Then
Paint (this.x_ + 1, this.y_ + 1), this.Fillcolor_, this.Otlncolor_
End If
End Sub
Property myRect.x(ByVal xx_ As Integer)
this.X_ = xx_
End Property
Property myRect.x() As Integer
Return this.X_
End Property
Property myRect.y(ByVal yy_ As Integer)
this.Y_ = yy_
End Property
Property myRect.y() As Integer
Return this.y_
End Property
Property myRect.Width(ByVal w_ As Integer)
this.Width_ = w_
End Property
Property myRect.Width() As Integer
Return this.Width_
End Property
Property myRect.Height(ByVal h_ As Integer)
this.Height_ = h_
End Property
Property myRect.Height() As Integer
Return this.Height_
End Property
Property myRect.Filled(ByVal f_ As Integer)
this.Filled_ = f_
End Property
Property myRect.Filled() As Integer
Return this.Filled_
End Property
Property myRect.Otlncolor(ByVal oc_ As Integer)
this.Otlncolor_ = oc_
End Property
Property myRect.Otlncolor() As Integer
Return this.Otlncolor_
End Property
Property myRect.FillColor(ByVal fc_ As Integer)
this.Fillcolor_ = fc_
End Property
Property myRect.FillColor() As Integer
Return this.Fillcolor_
End Property
Constructor myRect
this.X_ = 0
this.Y_ = 0
this.Width_ = 10
this.Height_ = 10
this.Filled_ = 0
this.Otlncolor_ = 15
this.Fillcolor_ = 7
End Constructor
Constructor MyRect (xx_ As Integer, yy_ As Integer, w_ As Integer, _
h_ As Integer, f_ As Integer, oc_ As Integer, _
fc_ As Integer)
this.X_ = xx_
this.Y_ = yy_
this.Width_ = w_
this.Height_ = h_
this.Filled_ = f_
this.Otlncolor_ = oc_
this.Fillcolor_ = fc_
End Constructor
'Create a graphic screen
Screen 18
'Create an object using the default constrcutor
Dim aRect As myRect
'Create an object by explicitly setting the constructor values
Dim bRect As myRect = myRect(200, 200, 200, 100, 1, 15, 9)
'Draw the rectangles on the screen
aRect.DrawRect
bRect.DrawRect
'Update aRect properties
aRect.X = 90
aRect.Y = 20
aRect.Filled = 1
aRect.FillColor = 15
'Draw new rect
aRect.DrawRect
Sleep
End
To initialize the object using the default Constructor, you simply Dim the extended Type just as you would the standard type. If the Constructor only takes a single value then you can use the Dim var as Typename = value syntax. To initialize the object with a set of values, you Dim the type and then use the = typename(par1m parm1…) syntax. You can see that accessing the members of the object is just like accessing the member of a standard type.
Thanks to cha0s at the FreeBasic forums for the information regarding Properties.