BBj Custom Objects Tutorial: Program #5 - Class Inheritance

Example Programs

Here are some example BBj programs. Each example is designed to demonstrate some aspect of using Custom Object Classes in BBj.

The complete source code for each program is included in an appropriately named sub-folder in the zip file you can download here.

Program #5: Class Inheritance

Let’s take a look at a BBj program that demonstrates how to use class inheritance to pay employees when there are multiple types of employees. We will create a simple Employee class to hold the attributes and operations that are common to all types of employees. Then we will extend the Employee class to create two subclasses, SalariedEmployee and CommissionedEmployee. It should be intuitive that the logic to pay employees of the different types will have to be different for each type. In this example, we create a PayMaster class to hold the logic for paying all of our employees. PayMaster in turn relies on information from the various Employee classes, together with its own “pay by writing a check” logic. For simplicity, we will use the same Check and PayrollCheck classes from Program 3 to help make the payments. Again, we show the code for each new source file using a different border color for readability:

REM /** ==============================================
REM * EmployeeIF.bbj
REM * This file defines the EmployeeIF Interface
REM ====================================================
USE ::Check.src::Check

INTERFACE public EmployeeIF
    METHOD public BBjString getName()
    METHOD public BBjString getID()

    REM Pay Methods
    METHOD public VOID markAsPaid()

    REM Salary information
    METHOD public BBjNumber isSalaried()
    METHOD public BBjNumber getSalaryToPay()

    REM Commission information
    METHOD public BBjNumber isCommissioned()
    METHOD public BBjNumber getCommissionToPay()
INTERFACEEND
REM /** ============================================================
REM  * Employee.bbj
REM  * This file defines the Employee class, which implements the
REM  *   EmployeeIF Interface
REM  =============================================================== */
USE ::EmployeeIF.bbj::EmployeeIF

CLASS public Employee implements EmployeeIF
    REM These attributes should only be settable in a constructor
    FIELD protected BBjString Name$       = ""
    FIELD protected BBjString EmployeeID$ = ""

    REM ===============================================================
    REM Constructors

    REM This protected constructor can be called from subclasses
    METHOD protected Employee(BBjString newName$,
:                             BBjString newEmployeeID$)
        #Name$       = newName$
        #EmployeeID$ = newEmployeeID$
    METHODEND

    REM This default private constructor can never be called
    METHOD private Employee()
    METHODEND

    REM ===============================================================
    REM Employee individual attributes such as Name...

    REM Anyone should be able to ask any Employee for his/her name
    METHOD public BBjString getName()
        METHODRET #Name$
    METHODEND

    REM Anyone should be able to ask any Employee for his/her ID
    METHOD public BBjString getID()
        METHODRET #EmployeeID$
    METHODEND

    REM ===============================================================
    REM Salary Methods

    REM Is this a salaried employee?
    REM   For generic Employees, the answer is "no"
    METHOD public BBjNumber isSalaried()
        METHODRET 0
    METHODEND

    REM What monthly salary should this employee be paid?
    REM   For generic Employees, the answer is "0"
    METHOD public BBjNumber getSalaryToPay()
        METHODRET 0
    METHODEND

    REM ===============================================================
    REM Commission Methods

    REM Is this a commissioned employee?
    REM   For generic Employees, the answer is "no"
    METHOD public BBjNumber isCommissioned()
        METHODRET 0
    METHODEND

    REM What commission is due to be paid to this employee?
    REM   For generic Employees, the answer is "0"
    METHOD public BBjNumber getCommissionToPay()
        METHODRET 0
    METHODEND
CLASSEND
REM /**
REM  * SalariedEmployee.bbj
REM  * This file defines the SalariedEmployee class, which
REM  *   extends the Employee class
REM  */
USE ::Employee.bbj::Employee

CLASS public SalariedEmployee extends Employee
    FIELD public BBjNumber MonthlySalary = 0

    REM ===============================================================
    REM Constructors

    REM Use this constructor - we always have to have a name and ID
    METHOD public SalariedEmployee(BBjString newName$, BBjString newID$,
:                                  BBjNumber newMonthlySalary)
        REM Pass the name and ID to the parent Employee class
        #super!(newName$, newID$)

        #MonthlySalary = newMonthlySalary
    METHODEND

    REM The default constructor is private so that it can never be called
    METHOD private SalariedEmployee()
    METHODEND

    REM ===============================================================
    REM SalariedEmployee Fields
    REM BBj provides implicit get/setMonthlySalary() methods

    REM ===============================================================
    REM Since this the salaried employees class, we need to fill in the
    REM   methods the EmployeeIF says a salaried employee should have

    REM Is this a salaried employee? Yes (1).
    METHOD public BBjNumber isSalaried()
        METHODRET 1
    METHODEND

    REM What should this employee be paid for this month?
    REM   For salaried Employees, the answer is "one month's salary"
    METHOD public BBjNumber getSalaryToPay()
        METHODRET #MonthlySalary
    METHODEND

    REM Once a salaried employee is paid, call this method to update any
    REM   internal record keeping values. In this case, we have none,
    REM   BUT we define it to fully implement the EmployeeIF Interface
    METHOD public VOID markAsPaid()
    METHODEND
CLASSEND
REM /**
REM  * CommissionedEmployee.bbj
REM  * This file defines the CommissionedEmployee class, which
REM  *   extends the Employee class
REM  */
USE ::Employee.bbj::Employee

CLASS public CommissionedEmployee extends Employee
    FIELD public BBjNumber CommissionPercentage  = 0
    FIELD public BBjNumber SalesDueForCommission = 0

    REM ===============================================================
    REM Constructors

    REM Use this constructor - we always have to have a name and ID
    METHOD public CommissionedEmployee(BBjString newName$,
:                                    BBjString newID$,
:                                    BBjNumber newCommissionPercentage)
        REM Pass the name and ID to the parent Employee class
        #super!(newName$, newID$)

        #CommissionPercentage = newCommissionPercentage
        REM New employees always start with "0" sales so far...
    METHODEND

    REM The default constructor is private so that it can never be called
    METHOD private CommissionedEmployee()
    METHODEND

    REM ===============================================================
    REM CommissionedEmployee Fields
    REM BBj provides implicit get/setX() methods for the public fields

    REM When the employee makes a sale, add the value to the running
    REM   total so commission will be paid on it at the next pay day
    METHOD public void addSale(BBjNumber newSaleAmount)
        #SalesDueForCommission = #SalesDueForCommission + newSaleAmount
    METHODEND

    REM Once the employee is paid, call this method to reset the
    REM   "unpaid sales" back to zero. Even if we did not have any
    REM   actions to take, we would still have to define markAsPaid()
    REM   to fully implement the EmployeeIF Interface
    METHOD public VOID markAsPaid()
        #SalesDueForCommission = 0
    METHODEND

    REM ===============================================================
    REM Since this the commissioned employees class, fill in the methods
    REM   the EmployeeIF says a commissioned employee must have

    REM Is this a commissioned employee? Yes (1).
    METHOD public BBjNumber isCommissioned()
        METHODRET 1
    METHODEND

    REM What should this employee be paid for commissions?
    REM   For commissioned employees: "commission on sales"
    REM NOTE: Be sure to call markAllSalesPaid() once the employee is
    REM   actually paid, to avoid paying multiple commissions!
    METHOD public BBjNumber getCommissionToPay()
        METHODRET #CommissionPercentage * #SalesDueForCommission
    METHODEND
CLASSEND
REM /**
REM  * PayMaster.bbj
REM  * This file defines the PayMaster class, which pays employees
REM  */
USE ::Employee.bbj::Employee
USE ::PayrollCheck.src::PayrollCheck

CLASS public PayMaster
    FIELD private static BBjNumber TaxRate = 0.30

    METHOD public PayrollCheck pay(Employee employeeToPay!)
        DECLARE BBjNumber netPay
        netPay = 0

        IF (employeeToPay!.isSalaried()) THEN
            netPay = employeeToPay!.getSalaryToPay()
        ELSE
            IF (employeeToPay!.isCommissioned())
                netPay = employeeToPay!.getCommissionToPay()
            ENDIF
        ENDIF
        employeeToPay!.markAsPaid()

        REM Now create a check based on the net pay
        METHODRET new PayrollCheck(employeeToPay!.getName(),
:                         netPay, #TaxRate * netPay)
    METHODEND
CLASSEND
REM /** ============================================================
REM  * This file uses a number of classes to pay Employees
REM  * PayEmployees.bbj
REM  * ============================================================= */
USE ::Employee.bbj::Employee
USE ::SalariedEmployee.bbj::SalariedEmployee
USE ::CommissionedEmployee.bbj::CommissionedEmployee
USE ::PayMaster.bbj::PayMaster
USE ::PayrollCheck.src::PayrollCheck

DECLARE PayMaster PayMaster!
PayMaster! = new PayMaster()

REM Create a commissioned employee
DECLARE CommissionedEmployee James!
James! = new CommissionedEmployee("James Dean", "101C", 0.16)
REM Give James some sales for us to pay him commission on
James!.addSale(5219)
James!.addSale(811)

REM Create a salaried employee #201
DECLARE SalariedEmployee Bob!
Bob!= new SalariedEmployee("Bob Wills", "201S", 5000)

DECLARE Employee Employee!

REM Now pay James
Employee! = James!
DECLARE PayrollCheck checkForJames!
checkForJames! = PayMaster!.pay(Employee!)

REM Now pay Bob
Employee! = Bob!
DECLARE PayrollCheck checkForBob!
checkForBob! = PayMaster!.pay(Employee!)

REM Print out the check information
PRINT "James received check #", checkForJames!.getCheckNumber(),
PRINT "  for the amount of ", checkForJames!.getAmount()
PRINT "Bob received check #", checkForBob!.getCheckNumber(),
PRINT "  for the amount of ", checkForBob!.getAmount()

Employee! = James!
PRINT "> Does James work on commission? ",
:     IFF(Employee!.isCommissioned(),"YES","NO")
Employee! = Bob!
PRINT "> Does Bob work on commission? ",
:     IFF(Employee!.isCommissioned(),"YES","NO")

Program 5. The Classes and Other Code to Pay Employees

Execution

Creating the BBj source files and then running Program 5 (the code in PayEmployees.bbj) resulted in the following SysConsole output:

Figure 5. The Output from Program 5

Observations

Here are some details that this code demonstrates:

  • Only the code in PayEmployees.bbj explicitly uses the SalariedEmployee and CommissionedEmployee subclasses. All other access uses a reference to the Employee superclass. This means if, in the future, you add more subclasses than just those two, the various classes will continue to work. The exception is the code in PayEmployees.bbj, of course. That code is specifically interested in working with salaried vs. commissioned employees, and presumably is where any new subclasses would be needed.

  • The PayMaster.pay(Employee employeeToPay!) method is an example of object-oriented programming that relies upon classes and inheritance to work. It takes as an argument a reference to a superclass, when in reality the code will always pass an instance of one of its subclasses. As an alternative, you could have created two PayMaster.pay() methods, one for each of the subclasses we created. This, however, would mean that every time you add a new subclass, you would need to add another method to the PayMaster class.

  • You could easily enhance the PayMaster class by giving its constructor a parameter to take in the tax rate (instead of having the TaxRate field fixed at 0.30). Since the PayMaster class consistently uses the TaxRate field instead of the literal “0.30” value, this would be a simple improvement, and the class would be more generally useful.

  • The EmployeeIF Interface is not absolutely necessary in this simple case, but it demonstrates how you can share an interface definition without sharing any implementation details. This sharing is useful in object-oriented programming to help avoid coupling between class implementations.

Next Step: Example Program #6

BBj Custom Objects Tutorial Contents

BBj Custom Objects Tutorial: Introduction

BBj Custom Objects Tutorial: Interfaces

BBj Custom Objects Tutorial: Classes

BBj Custom Objects Tutorial: Fields

BBj Custom Objects Tutorial: Methods

BBj Custom Objects Tutorial: Using Custom Objects

BBj Custom Objects Tutorial: Program #1 - Writing a Check

BBj Custom Objects Tutorial: Program #2 - Protected and Private Fields

BBj Custom Objects Tutorial: Program #3: The Static Keyword

BBj Custom Objects Tutorial: Program #4 - Error Handling

BBj Custom Objects Tutorial: Program #5 - Class Inheritance

BBj Custom Objects Tutorial: Program #6 - Callback Choices

BBj Custom Objects Tutorial: Program #7 - Constructors and Field Initialization