Table of Contents
We have mentioned that Biferno is a true object oriented language, or OO language. This is one of the strongest points of the language and makes it a very versatile and suitable tool for the implementation of large projects in environments where development is done by a team of developers rather than by a single individual.
A general introduction to object oriented programming is not one of the goals of this manual. With the beginner in mind, we believe it is useful anyway to present a number of examples of creation of new classes and extension of predefined classes. These capabilities are key to understand and fully exploit the potential offered by OO languages such as Biferno.
Biferno supports the main concepts of OO programming:
Code encapsulation, the possibility to make all or some of the properties that define a class invisible from the outside world and accessible only via specific methods (the object as a logical entity is derived from this concept).
Class inheritance, the mechanism that allows to derive new classes starting from already defined classed, keeping their characteristics and functionality and adding new ones as needed.
Polymorphism, the capability of several derived classes to share the same methods that execute completely different actions. Polymorphism and inheritance are closely related.
These mechanisms foster code reusability, reduce the possibility of error, and make application update and maintenance easier.
A new class is defined as follows:
class class_name [extends class_name]
{
[static][public, private, protected][const] class property_name
[static][public, private, protected] class method_name (parameter
list)
{
code block
}
}
Notice the similarity between method definition and function definition,
while properties are declared by specifying the class they belong to.
Constants can be defined by using the keyword const in front of the
property name. Properties and constants are different entities in Biferno.
For sake of simplicity in the following we will blur the distinction and
talk generically of class properties, meaning both properties and
constants.
A class can have as many methods and properties as desired, even none.
The keywords public, private and protected define the visibility of
members (properties and methods) of a class. Members of a class defined as
public are visible, i.e. directly accessible, by all scripts of an
application, including all functions and all base and derived classes.
Members defined as private are visible only from within the class they
belong to. Members defined as protected are visible only from within the
class they belong to and from within classes derived from the latter. Class
derivation is described in the following. public, private and protected
are optional keywords (the default is public).
A static property is a property that does not belong to a single object,
but to the entire class it belongs to. static properties are similar to
global variables having the entire class as their scope. When the value of
a static property is changed, the change is reflected in all objects of
that class. If a static property is called from outside the class it
belongs to, the class identifier must be used in front of the property,
according to the class.property syntax. The class identifier can be omitted
from within the class the property belongs to. In addition to this, the
class identifier in front of the property name can be always omitted when a
static property of a class is passed as a parameter to a method of the same
class.
The public constants of a class are usually defined as static, because they
contain values that are common to all objects of the same class. We will
see examples of the use of constants in a class in Chapter 13, File Management and in Chapter 16, Error Handling and Debugging.
A method defined as static is a public method that can be called only
statically, i.e. it can not be applied to an object of that class but only
called using the class.method(parameters) syntax.
A special method, called a constructor, can be defined for each class. The constructor method creates an object of that class and initializes data (properties) describing its characteristics. The constructor has the same name as the class and a variable parameter list depending on the operations it has to execute and on the properties it has to initialize. The prototype of a class constructor is as follows:
void class_name (parameter list)
The constructor method for a given class is called automatically each time a variable of that class is created. If a class does not declare a constructor, no method is invoked at variable creation time, and a class instance is generated with all its properties set to default values (as we will see, derived classes inherit the base class constructor).
Besides a constructor method, a class can have a destructor method. A destructor method frees the memory used to store data items used by an object. When a new class is defined, it is rarely necessary to declare a destructor, because data stored in internal properties of a class is automatically eliminated together with the variable corresponding to the object.
A destructor method for a class should be defined if the object, when created via the constructor method, dynamically allocates data structures or resources external to the class, which have to be released separately from the object. The destructor method is automatically called when a variable is destroyed. This happens when, during script execution, control exits the variable scope. E.g., local variables are deleted after script or function execution terminates. The destructor method must be named "Destructor" and has no parameters:
void Destructor (void)
There are two important rules that must be followed when writing a destructor method:
Within a destructor, no reference can be made to application,
persistent or session variables.
Within a destructor, no variables of classes that have a destructor method themselves or variables of user-defined classes can be instantiated.
To inquire if a class has a destructor method the following call can be used:
boolean classInfo(className).wantDestructor
This function always returns true for user classes, because such classes
always have a destructor (an internal one if the user has defined none),
which frees memory resources allocated by Biferno to manage the class
instance. The result of classInfo(className).wantDestructor for predefined classes depends on
the implementation of the class itself.
We will discuss an example of the use of destructors when describing special methods for user classes.
As we have seen, some Biferno classes are static (e.g. the ansi class),
i.e. no variables of that class can be instantiated. All members of a
static class are therefore static. Normally a class can have both static
and non- static (public or private) members. A Biferno class is static if
all its public members are static.
As an example, we define a simple class that represents a person identified by its name, surname, gender and age:
<?
class person
{
string firstName
string lastName
char gender
int age
void person (string n1, string n2, char s, int a)
{
this.firstName = n1
this.lastName = n2
if (s == "M" || s == "F")
this.gender = s
else
this.gender = "-"
if (a >= 1)
this.age = a
else
this.age = -1
}
}
?>
The person class has four public properties and one method, the constructor method. The constructor takes four parameters and uses them to initialize the class properties. Let's see how we create an object of the person class:
<?
John = person("John", "Smith", "M", 42)
?>
Initializing the John variable is done by calling the constructor of the
person class passing the required parameters. The parameter values are
assigned to the properties of the object which is created.
Properties of the person class are public and can be directly accessed for
both reading and writing. E.g. we can write <? $John.firstName ?>, or
<? John.age = 43 ?>. Notice that the properties of a class are
referred to within the constructor method by using the this identifier
followed by a dot in front of the property name. The this keyword is
meaningful only when used within a class, denotes the object of the current
class, and can be omitted when redundant. We will drop the this identifier
in the following examples whenever it is not strictly necessary.
If a constructor does not initialize some declared properties, these will
remain uninitialized. In this case, Biferno tries to assign defaults to
these properties using the mechanism explained in Chapter 10 when passing
parameters to functions. Properties of primitive classes would be assigned
the value "", 0 or false, other classes would remain uninitialized (see
Section 4, “Passing Parameters to a Function” in Chapter 10, Functions).
In the constructor of the person class, parameters are first checked before
values are assigned to the gender and age properties. Since these are
public properties, any values could be assigned to them directly, without
any kind of control by the class. This can lead to inconsistencies in
property values. In such cases it is advisable to use protected or private
properties and write specific methods to modify their value.
Let's modify the person class according to the considerations above:
<?
class person
{
string firstName
string lastName
protected char gender
protected int age
void person (string n1, string n2, char s, int a)
{
firstName = n1
lastName = n2
SetGender(s)
SetAge(a)
}
char GetGender(void)
{
return gender
}
void SetGender(char s)
{
if (s == "M" || s == "F")
gender = s
else
gender = "-"
}
int GetAge (void)
{
return age
}
void SetAge(int a)
{
if (a >= 1)
age = a
else
age = -1
}
array GetPersonData(void)
{
return array("FirstName":firstName, "LastName":lastName,
"Gender":gender, "Age":age)
}
}
?>
The example shows that it has been necessary to add specific methods to the
class to allow objects of the person class to read and write the gender and
age properties, which are now protected properties. E.g., the GetGender
method returns the value of the gender property, while the SetGender method
assigns the value of the s parameter to the gender property if this value
is "M" or "F", or otherwise the value "-" (which indicates absence of value
for the class). The GetAge and SetAge methods work in a similar fashion for
the age property. Notice that also from within the constructor the methods
SetGender and SetAge are called in order to avoid duplication of the code
that executes the checking.
The GetPersonData method returns an associative array with four elements of
the string class that contain the values of properties of the person class.
This method allows to obtain in one structure all information relative to
the person represented by an object of the person class.
Similarly to functions, classes can be of the local or application
kind. In this respect, the same rules described for functions in Section 2, “Application and Local Functions” apply to classes as well.
In a Biferno class properties can be declared that are arrays of elements of a given class. Since, within a class, the class a member belongs to is always specified, we use a syntax that is different from the syntax used for the declaration of array class variables. If we write:
int nome[]
we have declared a public property of the int class that is a one
dimensional array of integers.
To declare multidimensional arrays we specify a pair of square parentheses for each dimension of the array.
It is also possible to declare properties of the array class. In this case
the class of the elements is determined by the first value assigned to an
element.
It has been mentioned that in Biferno a class can be derived from an existing class. A derived class can be considered the extension of a class, in the sense that it inherits properties and methods of another class and adds new ones. The original class is usually called the base class, while the derived class is called a subclass (or child class).
The base class can be any Biferno class, both predefined or created ex novo. Multiple classes can be derived from a single base class, and classes can be derived in turn from a derived class.
In general a derived class adds new functionality to the base class, both by adding new methods and properties, and by redefining methods of the base class. The fundamental advantage of derived classes consists in the possibility of expanding or personalizing the functionality of a base class without having to modify the base class itself.
Let's see an example of a derived class. Assume we want to store data
relative to the users of an online service that requires input of a
username and password to gain access. Since our users will be identified by
name, surname, and other personal data described by the person class, we
can create a new class called user, use the person class as a base, and
implement only the additional data and functionality required.
The keyword extends between the name of the new class and the base class
must be used to declare the extension of a class, as in the following
example:
<?
class user extends person
{
protected string username
protected string password
void user (string n1, string n2, char s, int a, string u, string p)
{
super(n1, n2, s, a)
SetUsername(u)
SetPassword(p)
}
string GetUsername(void)
{
return username
}
void SetUsername(string u)
{
username = u
}
string GetPassword(void)
{
return password
}
void SetPassword(string p)
{
password = p
}
}
?>
Notice that the user class only contains additional properties and methods
with respect to the person class. All public and protected methods of the
base class are automatically inherited by the derived class.
A derived class may have no constructor. In this case the base class constructor is automatically called when a new object of the derived class is created, with the same parameter number and ordering. For the user class we need to initialize two new properties and therefore we have declared a constructor that has the same parameters as the constructor of the base class, plus two new parameters corresponding to the additional properties of the derived class. If a derived class has its own constructor, it needs to call internally the base class constructor.
As seen in the example, the super identifier followed by the parameters
required by the base class constructor between parentheses must be used to
call the base class constructor. In the case of the user class we pass the
first four parameters to the person class constructor and then initialize
the two new ones.
The super identifier usually indicates the base class object that we intend
to extend, similarly to the use of the this identifier to denote the object
of the current class (base or derived). Instead, when followed by the '('
character, super denotes the constructor of the class we are extending
("super()").
Let's initialize a variable of the user class:
<?
user_1 = user("John", "Smith", "M", 30, "admin", "AM001232001ZA")
?>
After its creation, the user_1 object can be manipulated using both methods
and properties of the user class and of the person class, as in the
following example:
<?
$user_1.firstName
user_1.SetAge(31)
user_1.SetUsername("guest")
user_1.SetPassword("")
?>
We can also use the GetPersonData method inherited form the person class,
but this method only returns values of properties defined in the base
class. This issue can be solved by redefining the method in the derived
class in such a way that it will now return in the array also the username
and password properties. The GetPersonData method of the user class will
look as follows:
array GetPersonData(void)
{
return array("FirstName":firstName, "LastName":lastName,
"Gender":gender, "Age":age, "Username":username, "Password":password)
}
The characteristics of OO languages that allow a derived class to share methods (i.e. to have methods with the same name) of the base class is called overriding and is at the root of the concept of polymorphism, mentioned in the introduction to this chapter. Thanks to this characteristic a derived class can be specialized maintaining a strong homogeneity with the base class.
Assume that we have redefined a method of the base class in a derived
class, and that we now want to invoke the method of the base class from
within the derived class. This can be done by using the super keyword
(necessary in this case) to denote the object of the base class. E.g., if,
in a method of the user class, we write super.GetPersonData(), we are
calling the GetPersonData method of the person base class, and not the
method with the same name of the user class.
A user class can optionally add one or more special methods that are automatically invoked by Biferno when certain events happen.
There are several predefined special methods for Biferno user classes. Of course, being optional methods, these methods will be called only if effectively present (implemented) within a user class.
The SetProperty method is invoked every time an object property is
modified, except for private properties. This method must have the following
prototype:
void SetProperty (string propertyName)
The propertyName parameter contains the name of the property that has been
assigned a new value.
This method is useful to implement validity checks on data assigned to a property, or when the value of a property is strictly related to the value of another property, and every time one of the two is modified, the other has to be modified.
Considering the user class, we might want to assign empty passwords to
guest users, i.e. whenever the username property is set to "guest",
automatically the password property should be assigned the value "" (empty
string).
This simple mechanism can be implemented using the SetProperty method,
which, for the user class, is the following:
void SetProperty(string propertyName)
{
if (propertyName == "username")
{
if (username == "guest")
password = ""
}
}
It is also necessary to use the SetProperty method when we want to
implement public properties with read-only access. To demonstrate this
mechanism, consider the following example where a fictitious class with
two public properties is declared. The second property is modified only
internally to the class by the SetProperty method. If a script attempts to
assign directly a value to the property2 property, the class returns the
Err_PropertyIsOnlyRead error.
<?
class myClass
{
int property1
int property2 // Read only
void myClass (int par1)
{
property1 = par1
}
void SetProperty (string propertyName)
{
if (propertyName == "property1")
property2 = property1 + 10
else if (propertyName == "property2")
{ // An error is generated
error.ThrowException(Err_PropertyIsOnlyRead)
}
}
}
?>
The SetProperty method does not call itself, and therefore we can freely
assign values to properties within the SetProperty method.
When the error is thrown by calling the ThrowException method of the error
class, and passing it a predefined constant (in this case of the same
error class), Biferno interrupts the execution of the script and returns
the error corresponding to the specified constant. More information on the
error class and a complete discussion of error handling in Biferno is
contained in Chapter 16, Error Handling and Debugging.
The SuperIsChanged method is invoked every time the super object of the base
class is modified, e.g. when a new value is assigned to one of its (non
private) properties. This method is meaningful only for derived classes
and must have the following prototype:
void SuperIsChanged (string propertyName)
As for the SetProperty method, the propertyName parameter of the
SuperIsChanged method contains the name of the property of the super
object that has been modified, or is empty if the entire super object has
been modified (without use of any property).
This method should be declared when one or more properties of the derived
class depend on the value of one or more properties of the base class.
Since the SetProperty method of the derived class is not called if a
property of the base class changes value, the SuperIsChanged method allows
to intercept this event and to take action.
In the following example we derive a class from the myClass class defined
in the last example and we create a property that must always have the
same value as the property2 property of the myClass class, plus one.
<?
class myClassExt extends myClass
{
int propertyExt
void myClassExt(int par1)
{
super(par1)
propertyExt = super.property2 + 1
}
void SuperIsChanged(string propertyName)
{
if (propertyName == "property1")
propertyExt = super.property2 + 1
}
}
?>
The constructor of the myClassExt class calls the constructor of the base
class and assigns the value of the property2 property of the super object
incremented by one to the propertyExt property. When the value of the
property1 property is changed externally (remember that property2 is
read-only), the SuperIsChanged method is invoked and the propertyExt
property is automatically updated.
As for the SetProperty method, the SuperIsChanged method does not call
itself.
The Clone method is automatically invoked every time the object is duplicated,
either because it has been assigned to another variable, or because it has
been passed as a parameter to a function or method.
This method is rarely used because, when an object is copied, the properties of the new object are automatically initialized with the values of the corresponding properties of the original object. This is usually sufficient to guarantee the integrity and coherency of the copy object.
To illustrate the use of this method, assume we would like to know how
many objects of a class we defined are active, i.e. exist, at a certain
time. This can be done using a static counter, shared by the entire class,
that is incremented every time an object of the class is created and
decremented every time an object of the class is deleted. In the following
code we declare a fictitious class containing only the members that are
strictly necessary to implement this mechanism.
<?
class myClass
{
static int counter = 0
void myClass (void)
{
myClass.counter++
}
void Destructor (void)
{
myClass.counter--
}
void Clone (void)
{
myClass.counter++
}
}
?>
The constructor of the myClass class simply increments the static counter
and the destructor simply decrements it. This simple approach is not
sufficient to guarantee that the counter always reports the accurate count
of active objects of the myClass class, because, when an object is copied,
the constructor of the class of the copy object is not called. If the
class implements the Clone method, this method is always called on the
copy object, after all its properties have been initialized. In the case
of the myClass class, also the Clone method increments the static counter.
In the following example, we show what happens when we create and then
copy and object of the myClass class.
<?
obj1 = myClass() // The constructor increments the counter
obj2 = obj1 // The Clone method of obj2 increments the counter
?>
If the class would not implement the Clone, method, the second assignment
instruction would not cause an increment of the counter, although it
actually does create a new object of the myClass class.
Notice that the fact that the static counter is initialized to zero does not imply that the counter is zeroed at every subsequent instantiation of the class. In fact, the default value for the static properties of a class is assigned only once, when the class is created and loaded in memory for the first time. The initialization to zero of the counter might have been omitted, because Biferno implicitly assigns the default value zero to numerical static properties and the default value empty string to string class static properties.
The Operation method allows to implement arithmetic, and bit-wise operators
for a user class.
This method, if present in a class definition, is called automatically on the first (or on the second in case of forced typecast) of two objects of a user class that are passed as arguments to one of the operators listed above.
Assume we would like to implement the point class, which represents a
point in the plane via a pair of numerical coordinates, and that the sum
and difference of points should be implemented as the sum and difference
of the corresponding coordinates. The point class can be defined as
follows:
<?
class point
{
double x
double y
void point (double xval, double yval)
{
x = xval
y = yval
}
void Operation (point ptObj, string oper)
{
switch (oper)
{
case "+":
x += ptObj.x
y += ptObj.y
break
case "-":
x -= ptObj.x
y -= ptObj.y
break
default:
error.ThrowException(Err_IllegalOperation)
}
}
}
?>
The point class constructor assigns the values passed as parameters to the
x and y properties, which represent the actual coordinates of the point.
The Operation method takes as parameters a point class object and a string
that represents the operator to be applied.
The following script initializes two point class objects and assigns their
sum to a third point object.
<?
pt1 = point(1, 2)
pt2 = point(0.5, 1.5)
pt3 = pt1 + pt2 // pt3 vale (1.5, 3.5)
?>
The use of the + operator in the sum expression causes a call to the
Operation method of the pt1 object, which, after cloning itself, adds the
corresponding properties of the pt2 object to its x and y properties. The
resulting object is assigned to the pt3 variable, and the pt1 and pt2
objects are not modified.
As implemented in our example, the Operation method of the point class
handles only the + and - operators. The Err_IllegalOperation error is
generated for all other operators (we will describe the ThrowException
method of the error class in Chapter 16, Error Handling and Debugging).
The Compare method is invoked when it is necessary to perform a comparison
operation between two objects of the class. This method is similar to the
Operation method, the only difference being that the return value of the
comparison operation between two objects of the class is a Boolean
value. The prototype for the Compare method must be:
boolean Compare([classname] theobj, string oper)
This method is part of the mechanism that allows a class to generate its own errors, using error constants defined internally to the class. We will discuss this method and will see how to declare error constants in Chapter 16, Error Handling and Debugging, which is devoted to error handling.
We have seen that it is possible to convert values from a primitive class into another implicitly or explicitly, e.g. when passing parameters to a function or method, and in the concatenation of numerical variables and strings.
This mechanism is implemented in Biferno using implicit conversion methods provided by the language for predefined non static classes.
These conversion methods can be used also for user defined classes. While
it is not mandatory, it is good practice to provide for all new classes at
least a string conversion method in order to be able to represent an object
with a printable value.
If you have already played with the language and have implemented your first classes, you will probably be familiar with an error screen where, instead of the value of a variable of a class you defined, you get the message "object not printable (method tostring not found)". Why? The reason is that your class does not contain a method to implicitly convert an object of the class to a string, and the system does not know how to build a string to output the value of the variable or to provide a description of the corresponding object.
To avoid this problem we can simply add to our classes the following method:
string tostring (void)
{
return string_expression
}
where string_expression can be any expression that results in a string
class entity. Let's define a tostring method for the person class:
string tostring (void)
{
return firstName + " " + lastName
}
With this method a person class object can always be represented by a
string containing the name and surname of the person described by the
object. Using the user_1 variable, initialized in one of the previous
examples, we can now write: <? $user_1 ?>, and obtain in output the
string "John Smith" (user_1 is of the user class and inherits the tostring
method from the person class).
Similarly to the tostring method, conversion methods into all Biferno
primitive classes (toint, tolong, tounsigned, todouble, toboolean, tochar)
can be provided for user classes, and, more in general, into any predefined
language class or user class. The same syntactic rule as for the tostring
method holds (e.g., toarray, toperson, etc.).
Method overloading is a characteristic of OO languages that allows to define two or more methods for a class that have the same name but different parameters list. The compiler (or interpreter) decides which method should be chosen on the base of the parameter list supplied by the calling program.
In Biferno this mechanism can be partially implemented by using the obj abstract class. As the name suggests, this identifier denotes an object of any class, and can be used as placeholder in a parameter list for a method or function.
Using the obj abstract class we can implement a class method that accepts
parameters of various nature and can execute different operations depending
on the class of the parameters that are actually passed.
Let's consider again the point class previously defined, and implement a
Distance method to calculate the distance of the points. Before looking at
the code implementing the class, let's see a usage example:
<?
pt1 = point()
pt2 = point(0.5, 1.2)
dist1 = pt1.Distance(1.2, 2.25) // dist1 vale 2,55
dist2 = pt1.Distance(pt2) // dist2 vale 1,3
?>
The first row of the script instantiates a pt1 variable of point class
with the default values (0.0, 0.0). The second row instantiates another
variable (pt2) by passing a coordinate pair to the constructor. The third
row invokes the Distance method and computes the distance between the pt1
point and the point corresponding to the coordinates (1.2, 2.25). The
fourth row invokes again the Distance method and computes the distance
between the points pt1 and pt2.
The noteworthy characteristic of the above script is that the Distance
method is called first with two numerical values as arguments, and a then
with a point class object as argument. Calling the same method with
parameters belonging to different classes is what is we have defined as
“overloading”.
According to our discussion of parameters passing rules for functions and
methods so far, this should not be legal in Biferno. We will now see how
the Distance method of the point class can be implemented to make such
mechanism possible in Biferno.
<?
class point
{
double x
double y
void point (double xval, double yval)
{
x = xval
y = yval
}
double Distance (obj par1, double par2)
{
if (par1.class == this.class)
{
xval = par1.x
yval = par1.y
}
else
{
xval = double(par1)
yval = par2
}
return (double(xval.Sqr() + yval.Sqr()).Sqrt())
}
}
?>
The Distance method has two parameters, par1 and par2, and par1 is declared
as obj. This allows to call the method passing an object of any class as
the first parameter. In particular, we can call the method passing an
object of the point class, as we have seen in the previous example (the
second parameter is optional and can be omitted).
Internally, the method checks the class of the par1 object, and, if the
class coincides with the class of the this object (i.e. point), it
initializes the two local variables xval and yval with the values of the x
and y properties of the par1 parameter. If par1 is not of the point class,
it is typecast to double and anyway assigned to xval (the
Err_IllegalTypeCast is returned if the typecast fails), while the value of
the par2 of class double is assigned to the yval variable.
Finally, the method returns the value of the square root of the sum of the
squares of xval and yval. Notice the use of the Sqr and Sqrt methods,
available for all primitive numerical classes.
In summary, we have shown how it is possible to realize “method overloading”
for a class in Biferno, even if it would be more correct to talk about
“parameter overloading”. Notice that the obj abstract class can also be used
in passing parameters to a function.