Object Oriented Programming

Top  Previous  Next

Object Oriented Programming, or OOP as it is commonly known, is a programming technique that uses "objects" to design applications and computer programs. An object can be thought of as a collection of subroutines and related data encapsulated into a distinct package. Each subroutine in an object is referred to as a method and the data associated with the object as members.

To create an object the compiler must know what methods and member variables are included. In Emergence BASIC this is done by specifying a class definition. A class definition is similar to defining a UDT with the exception that you will also be providing the names of the methods for that class.  Classes and UDT's are related in the fact that internally member variables of a class are stored in that same manner as members of a UDT.

Class Definitions

The CLASS statement begins the definition of a new class.  A class definition is ended with the END CLASS statement.  Each variable defined within the class definition specify the member variables that are part of the created object.  Methods of the class are specified by using a DECLARE statement in much the same manner that you would declare a subroutine.  Here is a short example:
 

CLASS employee
    'methods
    DECLARE CalculatePay(hours as float),float
    DECLARE SetPayRate(amount as float)
    DECLARE SetEmployeeID(id as int)
    DECLARE PrintPaycheck( )
    'members
    int m_employeeID
    float m_payrate
    float m_lastpay
    string m_name
END CLASS

In the example above we defined the class of an object named 'employee'. The member variables are prefixed with m_ as a convenience for recognizing them as members later on and is a personal preference.  The next step in creating a class is writing the method subroutines. This is known as the implementation of a class. A method subroutine is written in the same manner as a regular subroutine with a few important differences. The name of a method subroutine is comprised of two parts separated by a double colon:
 

SUB employee::CalculatePay(hours as float),float

On the left side of the double colon is the name of the class the method belongs to. On the right side of the double colon is the name of the method itself. A method subroutine follows the same syntax as a regular subroutine with some added benefits.  For example the member variables of a class are directly accessible by the method subroutine.  This is an important concept to learn as it is the heart of OOP. The encapsulation of related data and subroutines. Let's continue the writing of the method subroutines:
 

SUB employee::CalculatePay(hours as float),float
    m_lastpay = m_payrate * hours
    RETURN m_lastpay
END SUB
 
SUB employee::SetPayRate(amount as float)
    m_payrate = amount
END SUB
 
SUB employee::SetEmployeeID(id as int)
    m_employeeID = id
END SUB
 
SUB employee::PrintPayCheck( )
    PRINT "Pay to the order of "+ m_name +" ***$"+USING("###.##",m_lastpay)
END SUB

Creating and using the Object

An object is created, or instantiated,  by using it like any other variable type.  You can create an object either locally, globally or by using the NEW statement.  Using the example class above we can create an instance of an employee object:
 

DEF emp as employee

The member variables of the object are accessed in the same way as a UDT, using the dot operator. The method subroutines of an object are also accessed using the dot operator:
 

emp.m_name = "John Doe"
emp.SetEmployeeId(1111)
emp.SetPayRate(7.25)
emp.CalculatePay(40.5)
emp.PrintPayCheck( )

When creating and object dynamically you must set the type of the pointer used.  This is necessary for DELETE to work properly with classes and to call the class destructor:
 

POINTER pEmp
SETTYPE pEmp,employee
pEmp = NEW(employee,1)
#pEmp.m_name = "John Doe"
#pEmp.SetEmployeeId(1111)
#pEmp.SetPayRate(7.25)
#pEmp.CalculatePay(40.5)
#pEmp.PrintPayCheck( )
DELETE pEmp

Constructors and Destructors

A constructor is a method of an object that is executed when the object is created.  Similarly a destructor is executed when the object is destroyed. Objects are destroyed when the DELETE command is used on dynamically allocated objects, or when the object goes out of scope for statically created ones.  For example an object that is created statically in a subroutine will automatically be destroyed when the subroutine ends. An object that is created globally, outside of a subroutine, will be destroyed when the program ends.

The constructor is a method that has the same name as the class of the object and a destructor has the same name as the class of the object prefixed with an underscore.  For example let's add a constructor and destructor method to our employee class:
 

 CLASS employee
    'methods
    DECLARE employee( ) : 'The object constructor
    DECLARE _employee( ): 'The object destructor
    DECLARE CalculatePay(hours as float),float
    DECLARE SetPayRate(amount as float)
    DECLARE SetEmployeeID(id as int)
    DECLARE PrintPaycheck( )
    'members
    int m_employeeID
    float m_payrate
    float m_lastpay
    string m_name
    pointer m_history
END CLASS

Constructors are generally used to initialize the member variables of an object.  This is an important step since an object that is created statically in a subroutine will have random data stored in the member variables. Constructors are also used to allocate any memory needed by the object.
 

'Constructor
SUB employee::employee( )
    m_employeeID = -1
    m_payrate = 0.0f
    m_lastpay = 0.0f
    m_name = "None"
    m_history = NEW(CHAR,4096)
END SUB

A destructor is commonly used to clean up any memory allocated by an object.  In the above constructor we allocated memory for a 4K string, supposedly as a text description of the employees history. We can delete that dynamically created string in the destructor:
 

SUB employee::_employee( )
    DELETE m_history
ENDSUB

This is another powerful feature of OOP.  The object manages its own data and cleans up after itself.

Access protection

It is generally considered bad form to directly access a member variable from outside of a method implementation. In the above examples we have been setting the name of the employee directly using a dot operator.  While this might not seem like a bad thing, consider the dynamic string that is allocated in the constructor. If the program that uses your object sets m_history to NULL or tries to use it for a different purpose than a text description, it may undermine the intended operation of the class.  Also there may be methods of your class that are only used by that class internally and should not be executed outside of the object.

To aid in limiting outside access to an objects data Emergence provides three keywords, PUBLIC, PRIVATE and PROTECTED that control how an objects methods and members may be used.  By default all methods and members have PUBLIC access meaning they are accessible by both the object and the outside world that uses the object.

A PRIVATE method or member can only be accessed by a method of that object.

A PROTECTED method or member can only be accessed by a method of that object, or any objects that are derived from that class.  Derived classes will be covered shortly.

When one of the access protection keywords is encountered all of the methods and members following that keyword will have that access until the next access protection keyword is used.  Let's change our employee class to control access to the methods and members:
 

CLASS employee
    'methods
    DECLARE employee( ) : 'The object constructor
    DECLARE _employee( ): 'The object destructor
    DECLARE CalculatePay(hours as float),float
    DECLARE SetPayRate(amount as float)
    DECLARE SetEmployeeID(id as int)
    DECLARE PrintPaycheck( )
    DECLARE SetHistory(history as STRING)
    DECLARE SetEmployeeName(name as STRING)
PRIVATE
    DECLARE ClearHistory( )
    'members
PROTECTED
    int m_employeeID
    float m_payrate
    float m_lastpay
    string m_name
    pointer m_history
END CLASS

In the changed class definition we have one method that is marked as PRIVATE to the class.  This method is only executable from within another method of the same object.  Attempting to execute the ClearHistory method from outside of the object will result in the compiler generating an error message.  All of the member variables are marked as PROTECTED which limits there accessibility to this class or any derived classes.  Attempting to access a protected member will also generate a compiler error.

Inheritance

Inheritance is the technique of extending one class, known as a base class, to add additional functionality.  The new class, known as a derived class, contains all of the methods and members of the base class.  The terminology is important as the relationship between base and derived classes is often confused with a parent/child relationship. In fact, inheritance is an "is-a" relationship: manager is a type of employee.

Emergence supports single inheritance and the name of the base class is specified using the optional parameter of the CLASS statement.  For example if we wish to extend the functionality of the employee class and define a new class called manager we don't have to duplicated all of the methods of the employee class. We simply have to derive from the employee class:
 

CLASS manager, employee
'Constructor
    DECLARE manager( )
    DECLARE SetDividend(amount as float)
    DECLARE SetBonus(amount as float)
    DECLARE CalculateManagerPay(hours as float)
PROTECTED
    float m_dividend
    float m_bonus
END CLASS
 
SUB manager::manager
    m_dividend = 0f
    m_bonus = 0f
END SUB
 
SUB manager::CalculateManagerPay(hours as float)
    m_lastpay = (m_payrate * hours) + m_bonus + (m_dividend * company_profit)
END SUB

The derived class contains all of the methods of the employee class and the methods declared in the manager class.  When using the derived class nothing special has to be done to access the methods defined in the employee class.  They are part of it.
 

DEF man as manager
man.SetEmployeeName("Jim Doe")
man.SetEmployeeId(1111)
man.SetPayRate(15.25)
man.SetDividend(.015f)
man.SetBonus(500f)
man.CalculateManagerPay(40.5)
man.PrintPayCheck( )

In the above class we have a constructor that zeros out the two member variables of the manager object.  What about the constructor of the employee object? The compiler executes all constructors when the object is created starting with the base class all the way up to the last derived class.  Similarly when the object is destroyed all of the destructors are called in reverse order starting with the last derived class all the way down to the base class.

Method overriding and polymorphism

Method overriding is a technique used by the compiler to call the correct method in a derived class when a method of the same name exists in a base class. These methods are known as virtual methods and are declared with the VIRTUAL keyword.  Consider our two classes defined so far, a manager is a type of employee but has a different method of calculating a paycheck.  What if we had ten different classes derived from employee that all require a unique calculation.  Coming up with a separate method name for each type of employee would soon become hard to manage.  When using dynamically created classes it becomes even more difficult to know what kind of class the pointer refers to.  We solve this problem by using virtual methods:
 

CLASS employee
    'methods
    DECLARE employee( ) : 'The object constructor
    DECLARE _employee( ): 'The object destructor
    DECLARE VIRTUAL CalculatePay(hours as float),float
    DECLARE SetPayRate(amount as float)
    DECLARE SetEmployeeID(id as int)
    DECLARE PrintPaycheck( )
    DECLARE SetHistory(history as STRING)
    DECLARE SetEmployeeName(name as STRING)
PRIVATE
    DECLARE ClearHistory( )
    'members
PROTECTED
    int m_employeeID
    float m_payrate
    float m_lastpay
    string m_name
    pointer m_history
END CLASS
 
CLASS manager, employee
'Constructor
    DECLARE VIRTUAL CalculatePay(hours as float)
    DECLARE manager( )
    DECLARE SetDividend(amount as float)
    DECLARE SetBonus(amount as float)
PROTECTED
    float m_dividend
    float m_bonus
END CLASS
 
SUB manager::CalculatePay(hours as float)
    m_lastpay = (m_payrate * hours) + m_bonus + (m_dividend * company_profit)
END SUB

The method CalculatePay is said to be overridden in the manager class.  It does not replace the method since both methods do exist and are usable.  What it does is allow for polymorphism when using dynamically created objects. Polymorphism, as discussed previously, allows the compiler to call the correct method when your code doesn't know what kind of class a pointer points to. Let's say we have a subroutine that takes a pointer to an employee object.
 

POINTER man, emp
SETTYPE man, manager
SETTYPE emp,employee
man = NEW(manager,1)
emp = NEW(employee,1)
'fill in employee details here
'...
AddToPayroll(man)
AddToPayroll(emp)
DELETE man
DELETE emp
 
SUB AddToPayroll(emp as POINTER, hours as FLOAT)
    total_payroll += #<employee>emp.CalculatePay(hours)
ENDSUB

When the compiler encounters the #<employee>emp.CalculatePay(hours) statement it will call the correct method depending on the type of the class.  For the manager object it will call the CalculatePay method of the manager class which adds in a bonus and dividend to the base wage. For the employee object it will call the CalculatePay method of the employee class which returns a straight wage * hours worked calculation.

The compiler accomplished this magic feat by using a virtual function table (VTABLE) which is just an array of addresses.  Each array element contains the address of a method that was marked as VIRTUAL.  When a class overrides a method from a base class that address is replaced with the address of the method declared in the derived class.

Scope Resolution

When writing a member subroutine it is sometimes necessary to call the base class version of a method, or to call a program subroutine that may share the same name as a classes method.  For example the CalculatePay method of the manager class can be simplified by calling the CalculatePay method of the employee class:
 

SUB manager::CalculatePay(hours as float)
    m_lastpay = employee::CalculatePay(hours) + m_bonus + (m_dividend * company_profit)
END SUB

The double colon is used as the scope resolution operator in Emergence BASIC.  When used to call a base class method the name of the base class is specified on the left hand side of the double colon.  To execute a program subroutine, or API function, that might have the same name as a method simply prefix the call with the double colon.  For example if you had a class called graphics that encapsulated some of the drawing commands used by Emergence and wanted to have a method called LINE which uses the built in LINE command you would need to tell the compiler to call the built in version.
 

SUB graphics::LINE(x as int, y as int, x2 as int, y2 as int)
    FRONTPEN m_window, m_fcolor
   'call the built in LINE command
    ::LINE(m_window,x,y,x2,y2)
END SUB

The THIS pointer

Every class method has a hidden first parameter known as the THIS pointer.  It is a pointer to the instance of the created object. The THIS pointer is how the compiler knows which object a method subroutine is currently dealing with.  Consider a method from the employee class:
 

SUB employee::SetEmployeeID(id as int)
    m_employeeID = id
END SUB

Internally the compiler sees it as:
 

SUB employee::SetEmployeeID(id as int)
    *<employee>this.m_employeeID = id
END SUB

Which are both functionally equivalent. You can use the THIS pointer in your own code to pass the current object to some other method or subroutine.

Design considerations

When designing a class that will be used in multiple source files place the class definitions in an include file and the method implementations in a source file that is part of the project.  There is no need to use GLOBAL or EXTERN when referring to class methods as the compiler automatically handles this.  Just $include the file with the class definitions and add the methods source file to the project.