User Tools

Site Tools


object_20oriented_20programming_20_28lbb_29

Object Oriented Programming (LBB)

by Richard Russell, February 2015

Version 3.00 of LB Booster (LBB) incorporates some extensions to support Object Oriented Programming (OOP). I should emphasise that the use of these extensions is entirely optional, and their presence should not impair compatibility with non-OOP Liberty BASIC programs. But please don't stop reading now: OOP has a lot to offer!

Many people find the whole idea of Object Oriented Programming unfamiliar, new-fangled and frightening. They tend to flinch at the very mention of it! This is unfortunate because OOP is intended to make programming easier and result in more reliable and easy-to-maintain code.

The basic concept behind OOP is that of encapsulation. This is one of those long words which can seem rather intimidating, but all it really means is keeping related things together. The idea is that programs consist of code which operates on data - the code only makes sense in association with the data on which it operates, and the data is only useful in association with the code which operates on it.

In traditional programming languages there is usually no way of 'bundling together' related code and data: the data (typically numbers, strings and arrays) are declared somewhere near the start of the program - or worse, scattered throughout it - and the subroutines and functions which work on that data are somewhere near the end. The programmer knows which subroutine/function is related to what data (at least, he once did!) but this may not be at all obvious to somebody else trying to understand how the program works.

Classes and Objects


Object Oriented Programming has the concept of a class. A class is just a bundled collection of data along with the code which operates on it (the data are often referred to as the properties of the class and the code, consisting of subroutines and functions, as the methods of the class). Not only does the class bundle the related items together, it (typically) isolates the data from being accessed from outside the class - rather as variables (other than GLOBALS) referenced inside a function or subroutine are inaccessible from outside.

Hopefully you can see how this is helpful. It is obvious at a glance what data is related to what code, and the 'methods' can only operate on the 'properties' in the same class (see later for how classes can be combined). This makes the code more understandable and more reliable, and allows modifications to be made with more confidence.

But this is all rather theoretical, and it may be easier to understand by way of example. Imagine that in a program we want to represent a vehicle; all vehicles have certain properties in common, these might be things like the speed with which it is moving. We also want to be able to perform certain operations on that vehicle, such as increase its speed (accelerate) or decrease its speed (decelerate).

In Object Oriented Liberty BASIC (OOLB) we can define a class to represent a vehicle as follows:

      CLASS Vehicle
        DIM speed
 
        SUB accelerate rate
          speed += rate
        END SUB
 
        SUB decelerate rate
          speed -= rate
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS


The properties of the class are listed in one or more DIM statements; they may include numbers, strings and arrays (we will see later that it's also possible to include 'child' classes). The methods of the class provide the means to manipulate those properties, and a means to discover the value of a property (if it needs to be known outside the class).

So we have a simple class which represents a vehicle. But in the program there may be several different vehicles, with different speeds and other properties. This is where the real power of Objects comes in. The 'class' is like a template: it describes a vehicle in general. An 'object' is a specific case of that class (it's usually called an instance of the class) and describes a particular vehicle.

In OOLB we create an object (an instance of a class) as follows:

      NEW MyVehicle1 AS Vehicle


The object MyVehicle1 has a speed (and other properties, if any) independent of any other objects in the program. Let's put all this together into our first complete Object Oriented Liberty BASIC program:

      NEW MyVehicle1 AS Vehicle
 
      CALL MyVehicle1::accelerate 10
      PRINT MyVehicle1::get.speed()
      CALL MyVehicle1::accelerate 5
      PRINT MyVehicle1::get.speed()
      CALL MyVehicle1::decelerate 10
      PRINT MyVehicle1::get.speed()
 
      DISCARD MyVehicle1
      END
 
      CLASS Vehicle
        DIM speed
 
        SUB accelerate rate
          speed += rate
        END SUB
 
        SUB decelerate rate
          speed -= rate
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS

Here we have introduced a couple of things you haven't seen before. Firstly, to call a method (subroutine or function) in an object we use the familiar LB syntax but the name consists of the name of the object followed by two colons (this is called the scope resolution operator) and then the name of the method within that object, i.e. Object::Method.

Secondly, when we have finished with an object we DISCARD it. This frees the memory resources which were allocated to the object with NEW.

Private methods


Some methods may be intended to be called only from other methods within the same class, rather than from outside the class. It is possible to specify such methods as PRIVATE in which case an attempt to call them from outside the class will fail at run-time. Here is an example:

      NEW MyVehicle1 AS Vehicle
 
      CALL MyVehicle1::accelerate 10
      PRINT MyVehicle1::get.speed()
      CALL MyVehicle1::accelerate 5
      PRINT MyVehicle1::get.speed()
      CALL MyVehicle1::decelerate 10
      PRINT MyVehicle1::get.speed()
 
      DISCARD MyVehicle1
      END
 
      CLASS Vehicle
        DIM speed
 
        SUB accelerate rate
          CALL this::adjust rate
        END SUB
 
        SUB decelerate rate
          CALL this::adjust -rate
        END SUB
 
        PRIVATE SUB adjust adj
          speed += adj
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS

Here, instead of the accelerate and decelerate methods altering the speed value directly, they each call a PRIVATE method adjust which does so. Note the syntax for calling a method in the same class: specify the object name as this.

Constructors and Destructors


You will notice that in the class Vehicle the value of speed is not initialised. Just as with ordinary LB variables and arrays, the value is assumed to be zero or an empty string if not previously set. Of course we could have provided an explicit method for initialising the speed, but there is another approach.

It is possible to incorporate a special method called the constructor. The constructor takes the form of an ordinary subroutine but instead of being deliberately executed using CALL it is automatically called when the object is created. The constructor is distinguished by having a name which is the same as the name of the class. Let's modify our program accordingly:

      NEW MyVehicle1 AS Vehicle
 
      CALL MyVehicle1::accelerate 10
      PRINT MyVehicle1::get.speed()
      CALL MyVehicle1::accelerate 5
      PRINT MyVehicle1::get.speed()
      CALL MyVehicle1::decelerate 10
      PRINT MyVehicle1::get.speed()
 
      DISCARD MyVehicle1
      END
 
      CLASS Vehicle
        DIM speed
 
        SUB Vehicle ' constructor
          speed = 2
        END SUB
 
        SUB accelerate rate
          CALL this::adjust rate
        END SUB
 
        SUB decelerate rate
          CALL this::adjust -rate
        END SUB
 
        PRIVATE SUB adjust adj
          speed += adj
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS

Here the speed is automatically initialised to 2 when the object is created.

Suppose we want to be able to initialise the speed to a different value for each instance of the class. Again we could do that by calling a method, but it's also possible to pass one or more parameters to the constructor:

      NEW MyVehicle1 AS Vehicle 3 ' parameter passed to constructor
 
      CALL MyVehicle1::accelerate 10
      PRINT MyVehicle1::get.speed()
      CALL MyVehicle1::accelerate 5
      PRINT MyVehicle1::get.speed()
      CALL MyVehicle1::decelerate 10
      PRINT MyVehicle1::get.speed()
 
      DISCARD MyVehicle1
      END
 
      CLASS Vehicle
        DIM speed
 
        SUB Vehicle init ' constructor with parameter
          speed = init
        END SUB
 
        SUB accelerate rate
          CALL this::adjust rate
        END SUB
 
        SUB decelerate rate
          CALL this::adjust -rate
        END SUB
 
        PRIVATE SUB adjust adj
          speed += adj
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS


You probably won't be surprised to learn that as well as a constructor, which is called automatically when an object is created, we can also have a destructor which is called automatically when the object is discarded. A destructor has a name consisting of a tilde (~) followed by the name of the class; in this case there is nothing useful for it to do so we will just print a message:

      NEW MyVehicle1 AS Vehicle 3 ' parameter passed to constructor
 
      CALL MyVehicle1::accelerate 10
      PRINT MyVehicle1::get.speed()
      CALL MyVehicle1::accelerate 5
      PRINT MyVehicle1::get.speed()
      CALL MyVehicle1::decelerate 10
      PRINT MyVehicle1::get.speed()
 
      DISCARD MyVehicle1
      END
 
      CLASS Vehicle
        DIM speed
 
        SUB Vehicle init ' constructor with parameter
          speed = init
        END SUB
 
        SUB ~Vehicle ' destructor
          print "Destructor called"
        END SUB
 
        SUB accelerate rate
          CALL this::adjust rate
        END SUB
 
        SUB decelerate rate
          CALL this::adjust -rate
        END SUB
 
        PRIVATE SUB adjust adj
          speed += adj
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS


Inheritance


We have seen how to create a class which represents a vehicle, but there are different kinds of vehicles (cars, bicycles etc.). Suppose we want to create a class to represent a bicycle; we could start from scratch and define the properties and methods that a bicycle needs. But a better way is to recognise that a bicycle is a kind of vehicle, so any property or method relevant to a vehicle should also be relevant to a bicycle - although a bicycle may have properties and methods of its own.

We can do that using a variation of the CLASS statement as follows:

      CLASS Bicycle INHERITS Vehicle

The INHERITS keyword specifies that the class Bicycle inherits all the properties and methods of the class Vehicle. You can then specify additional properties and additional methods which are needed by a bicycle but not by vehicles in general. Here I have chosen the current gear and the gear ratios:

      NEW MyBicycle1 AS Bicycle 2,3,5,7,10 ' parameters passed to constructor
 
      CALL MyBicycle1::change.up
      PRINT MyBicycle1::get.ratio()
      CALL MyBicycle1::accelerate 10
      PRINT MyBicycle1::get.speed()
 
      DISCARD MyBicycle1
      END
 
      CLASS Vehicle
        DIM speed
 
        SUB Vehicle init ' constructor with parameter
          speed = init
        END SUB
 
        SUB ~Vehicle ' destructor
          print "Destructor called"
        END SUB
 
        SUB accelerate rate
          CALL this::adjust rate
        END SUB
 
        SUB decelerate rate
          CALL this::adjust -rate
        END SUB
 
        PRIVATE SUB adjust adj
          speed += adj
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS
 
      CLASS Bicycle INHERITS Vehicle
        DIM gear, ratio(5)
 
        SUB Bicycle r1, r2, r3, r4, r5 ' constructor with five parameters
          ratio(1) = r1
          ratio(2) = r2
          ratio(3) = r3
          ratio(4) = r4
          ratio(5) = r5
          gear = 1
          speed = 3
        END SUB
 
        SUB change.up
          IF gear < 5 THEN gear += 1
        END SUB
 
        SUB change.down
          IF gear > 1 THEN gear -= 1
        END SUB
 
        FUNCTION get.ratio()
          get.ratio = ratio(gear)
        END FUNCTION
      END CLASS

The order of declaration is important: ancestor classes must be declared before their descendant classes.

Overriding methods


As we have seen, when a class INHERITS another class it acquires the properties and methods of its ancestor class. However it is still possible to declare a method in the descendant class which has the same name as one in the ancestor class. In that case the declaration in the descendant class takes precedence. For example:

      NEW MyBicycle1 AS Bicycle 2,3,5,7,10 ' parameters passed to constructor
 
      CALL MyBicycle1::change.up
      PRINT MyBicycle1::get.ratio()
      CALL MyBicycle1::accelerate 10
      PRINT MyBicycle1::get.speed()
 
      DISCARD MyBicycle1
      END
 
      CLASS Vehicle
        DIM speed
 
        SUB Vehicle init ' constructor with parameter
          speed = init
        END SUB
 
        SUB ~Vehicle ' destructor
          print "Destructor called"
        END SUB
 
        SUB accelerate rate
          CALL this::adjust rate
        END SUB
 
        SUB decelerate rate
          CALL this::adjust -rate
        END SUB
 
        PRIVATE SUB adjust adj
          speed += adj
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS
 
      CLASS Bicycle INHERITS Vehicle
        DIM gear, ratio(5)
 
        SUB Bicycle r1, r2, r3, r4, r5 ' constructor with five parameters
          ratio(1) = r1
          ratio(2) = r2
          ratio(3) = r3
          ratio(4) = r4
          ratio(5) = r5
          gear = 1
          speed = 3
        END SUB
 
        SUB change.up
          IF gear < 5 THEN gear += 1
        END SUB
 
        SUB change.down
          IF gear > 1 THEN gear -= 1
        END SUB
 
        SUB accelerate rate
          speed += rate * ratio(gear)
        END SUB
 
        FUNCTION get.ratio()
          get.ratio = ratio(gear)
        END FUNCTION
      END CLASS

Here I have re-defined the accelerate method so that it takes account of the gear ratio.

Containment


As described in the previous section, inheritance represents an 'is a' relationship: a bicycle is a vehicle. A related concept is that of containment, which represents the 'has a' relationship. For example a bicycle has wheels. A class can only inherit from one ancestor class, but it can contain several child classes.

Here is an example. We will first declare a new class Wheel which has a property diameter, a method to set its value, and a method to get its value:

      CLASS Wheel
        DIM diameter
 
        SUB set.diameter d
          diameter = d
        END SUB
 
        FUNCTION get.diameter
          get.diameter = diameter
        END FUNCTION
      END CLASS

Note that a method which sets the value of a property is sometimes called a setter and a method which gets the value of a property a getter; together they are known as accessors.

We can now add some wheels to our class Bicycle:

      NEW MyBicycle1 AS Bicycle 2,3,5,7,10 ' parameters passed to constructor
 
      CALL MyBicycle1::change.up
      CALL MyBicycle1::accelerate 10
      PRINT MyBicycle1::get.speed()
      PRINT MyBicycle1::get.wheel.diameter(1)
 
      DISCARD MyBicycle1
      END
 
      CLASS Vehicle
        DIM speed
 
        SUB Vehicle init ' constructor with parameter
          speed = init
        END SUB
 
        SUB ~Vehicle ' destructor
          print "Destructor called"
        END SUB
 
        SUB accelerate rate
          CALL this::adjust rate
        END SUB
 
        SUB decelerate rate
          CALL this::adjust -rate
        END SUB
 
        PRIVATE SUB adjust adj
          speed += adj
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS
 
      CLASS Wheel
        DIM diameter
 
        SUB set.diameter d
          diameter = d
        END SUB
 
        FUNCTION get.diameter
          get.diameter = diameter
        END FUNCTION
      END CLASS
 
      CLASS Bicycle INHERITS Vehicle
        DIM gear, ratio(5), Front AS Wheel, Rear AS Wheel
 
        SUB Bicycle r1, r2, r3, r4, r5 ' constructor with five parameters
          ratio(1) = r1
          ratio(2) = r2
          ratio(3) = r3
          ratio(4) = r4
          ratio(5) = r5
          gear = 1
          speed = 3
          CALL Front::set.diameter 12
          CALL Rear::set.diameter 12
        END SUB
 
        SUB change.up
          IF gear < 5 THEN gear += 1
        END SUB
 
        SUB change.down
          IF gear > 1 THEN gear -= 1
        END SUB
 
        SUB accelerate rate
          speed += rate * ratio(gear)
        END SUB
 
        FUNCTION get.ratio()
          get.ratio = ratio(gear)
        END FUNCTION
 
        FUNCTION get.wheel.diameter(wheel)
          SELECT CASE wheel
            CASE 1: get.wheel.diameter = Front::get.diameter()
            CASE 2: get.wheel.diameter = Rear::get.diameter()
          END SELECT
        END FUNCTION
      END CLASS

Note that to call a method in a contained class the scope resolution operator is once again used, i.e. Child::Method.

Arrays of objects


Up to now we have instantiated objects individually, but it is possible to instantiate an array of objects. For example if we have five bicycles we can use an array for them:

      NEW MyBikes(4) AS Bicycle
 
      CALL MyBikes(2)::change.up
      CALL MyBikes(2)::accelerate 10
      PRINT MyBikes(2)::get.speed()
      PRINT MyBikes(2)::get.wheel.diameter(1)
 
      DISCARD MyBikes()
      END
 
      CLASS Vehicle
        DIM speed
 
        SUB Vehicle init ' constructor with parameter
          speed = init
        END SUB
 
        SUB ~Vehicle ' destructor
          print "Destructor called"
        END SUB
 
        SUB accelerate rate
          CALL this::adjust rate
        END SUB
 
        SUB decelerate rate
          CALL this::adjust -rate
        END SUB
 
        PRIVATE SUB adjust adj
          speed += adj
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS
 
      CLASS Wheel
        DIM diameter
 
        SUB set.diameter d
          diameter = d
        END SUB
 
        FUNCTION get.diameter
          get.diameter = diameter
        END FUNCTION
      END CLASS
 
      CLASS Bicycle INHERITS Vehicle
        DIM gear, ratio(5), Front AS Wheel, Rear AS Wheel
 
        SUB Bicycle ' default constructor
          ratio(1) = 2
          ratio(2) = 3
          ratio(3) = 5
          ratio(4) = 7
          ratio(5) = 10
          gear = 1
          speed = 3
          CALL Front::set.diameter 12
          CALL Rear::set.diameter 12
        END SUB
 
        SUB change.up
          IF gear < 5 THEN gear += 1
        END SUB
 
        SUB change.down
          IF gear > 1 THEN gear -= 1
        END SUB
 
        SUB accelerate rate
          speed += rate * ratio(gear)
        END SUB
 
        FUNCTION get.ratio()
          get.ratio = ratio(gear)
        END FUNCTION
 
        FUNCTION get.wheel.diameter(wheel)
          SELECT CASE wheel
            CASE 1: get.wheel.diameter = Front::get.diameter()
            CASE 2: get.wheel.diameter = Rear::get.diameter()
          END SELECT
        END FUNCTION
      END CLASS

There is one limitation of this technique: you cannot pass parameters to the constructor. The default constructor (a constructor with no parameters) will still be called however, and in the above program the necessary initialisation has been done there.

Method Overloading


OOP languages commonly allow you to have multiple methods with the same name but with different signatures; in this context a 'signature' means the number of parameters and their types. LBB supports this too, although only the number of parameters is distinguished. One use for this facility is to have multiple constructors; which constructor is called will depend on how many parameters are specified in the NEW statement:

      NEW MyVehicle1 AS Vehicle
      NEW MyVehicle2 AS Vehicle 5
 
      PRINT MyVehicle1::get.speed()
      PRINT MyVehicle2::get.speed()
 
      DISCARD MyVehicle1
      DISCARD MyVehicle2
      END
 
      CLASS Vehicle
        DIM speed
 
        SUB Vehicle   ' constructor with no parameters
          speed = 2
        END SUB
 
        SUB Vehicle s ' constructor with one parameter
          speed = s
        END SUB
 
        SUB accelerate rate
          speed += rate
        END SUB
 
        SUB decelerate rate
          speed -= rate
        END SUB
 
        FUNCTION get.speed
          get.speed = speed
        END FUNCTION
      END CLASS

Note that if you supply a default constructor, which takes no parameters, this will always be called, even if another constructor is called as well.

object_20oriented_20programming_20_28lbb_29.txt · Last modified: 2018/04/17 18:01 by tbest3112