Skip to content

Inheritance

Just like in C++ and many other languages, Class++ allows you to inherit classes. We group the inheritance concept into two categories: derived class (child), and the base class (parent). Inheritance implements an is-a relationship between classes.

local class = ClassPP.class

local Vehicle = class "Vehicle" { -- Base Class
    Public = {
        Brand = "Tesla",
        Model = "S",
        License_Plate = "BITE 1987",
        Year = 2012,
        honk = function(self)
            print("honk honk!")
        end
    }
}

local Car = class "Car" (Vehicle, nil) { -- Derived Class
    Public = {
        License_Plate = "A1B2C3"
    }
}

local newCar = Car.new()
newCar:honk()
print(newCar.Brand, newCar.Model, newCar.License_Plate, newCar.Year) -- Prints "Tesla S A1B2C3 2012"!

In this example, we have 2 classes: The Vehicle class (base), and the Car class (child).
We created the Car class by providing the class function a list of classes to inherit from (in this case, only the Vehicle class), and the classData table after.

When you create a class using this method, you create a new derived class that inherits all of the members and member functions from the class(es) provided, so you don't need to re-declare them again. The members are overwritable in the derived class, like in the example above, you can modify the License_Plate's default value to anything you wish. The same applies to other members.

Question

"Why should I use Inheritence?" It's very useful for code reusability: reusing members and functions of an existing class when you're creating a new class will save you a lot of time and effort, defining same members over and over again may cause spaghetti code and decrease code readability.


Multilevel-Inheritance

A class can also be derived from one class, which can be derived from another class:

local class = ClassPP.class

local Person = class "Person" { -- Base Class
    Public = {
        Name = "",
        Age = 0,
        Gender = "",
        Height = 0
    }
}

local Child = class "Child" (Person, nil) { -- Derived Class
    Public = {
        Age = 9,
        Energetic = true
    }
}

local Student = class "Student" (Child, nil) { -- Derived Class from a Derived Class
    Public = {
        SchoolId = 0,
        Grade = 0,
        Behaviour = "Good"
    }
}

local newStudent = Student.new()
print(newStudent.Name, newStudent.Age, newStudent.Gender, newStudent.Height, newStudent.Age, newStudent.Energetic, newStudent.SchoolId, newStudent.Grade, newStudent.Behaviour)
-- Prints " 9  0 9 true 0 0 Good"! (Spaces represent empty strings)

Protected Access Specifier

In the Access Specifiers section, you have learned that there are 4 access specifiers in Class++, so far you have seen Public, Private and Friend. The fourth specifier, Protected, is pretty much the same as the Private, however, aside from the class members, inherited classes will also be able to access these members.

local class = ClassPP.class

local Car = class "Car" {
    Public = {
        Brand = "Lamborghini",
    },
    Protected = {
        License_Plate = "XXXX"
    }
}

local BiggerCar = class "BiggerCar" (Car, nil) {
    Public = {
        Brand = "Tesla"
    }
}

function BiggerCar.Public:printLicensePlate()
    print(self.License_Plate) -- Will print "XXXX"!
end

local newCar = BiggerCar.new()
newCar:printLicensePlate()

In this example, we put the member License_Plate under the Protected access specifier, and created a new class inherited from the Car class. The inherited class and it's member functions will now be able to access this member.


super

Let's say that you want to access a function in the base class from a child class, how would you do it? Creating a new object from the base class and calling the function would be tedious, as it would take longer to write and would decrease performance.

Fortunately, for this, you can call the default super method of the object, which allows you to call the function in the base class that has the same name of the function this method has been called from.

local class = ClassPP.class

local A = class "A" { 
    Public = {
        getVariable = function(self)
            return self.Variable_A
        end
    },
    Protected = {
        Variable_A = 1
    }
}

local B = class "B" (A, nil) { 
    Public = {
        Variable_B = 1,
        getVariable = function(self)
            return self:super()
        end
    }
}

local newObject = B.new()
print(newObject:getVariable()) -- 1

In this example, we created a new class called "B" that inherits from "A". In both classes, we have a function called "getVariable", in the base class, this function returns the "Variable_A" member's value, and in the child class, this function returns the value from the "getVariable" function from the parent class, by calling the super() method.

Warning

super cannot be used within classes that have multi-inheritance. This is due to ambiguity that occurs with functions that have the same name in classes that have multi-inheritance.


Multi-Inheritance

A class can also be derived from multiple classes:

local class = ClassPP.class

local A = class "A" { 
    Public = {
        Variable_A = 1
    }
}

local B = class "B" { 
    Public = {
        Variable_B = 1
    }
}

local C = class "C" (A, B) { -- Derived Class
    Public = {
        Variable_C = 1
    }
}

local newObject = C.new() -- {Variable_A: number, Variable_B: number, Variable_C: number}

The Diamond Problem

The Diamond Problem is an ambiguity that arises when two classes, let's say "B" and "C", that inherits from a class called "A", and another class "D" that inherits from both "B" and "C". If there is a method in "A" that "B" and "C" have overridden, and "D" does not override it, then which version of the method does "D" inherit from: that of "B", or that of "C"?

local class = ClassPP.class

local A = class "A" { 
    Public = {
        method = function(self)
            print("Method from A")
        end
    }
}

local B = class "B" (A, nil) { 
    Public = {
        method = function(self)
            print("Method from B")
        end
    }
}

local C = class "C" (A, nil) { -- Derived Class
    Public = {
        method = function(self)
            print("Method from C")
        end
    }
}

local D = class "D" (B, C) { -- Derived Class
    Public = {
        -- Which method will D have, B's or C's?
    }
}

How Class++ solves the Diamond Problem

In Class++, the class inheritation system does not work by referencing the parent classes, but rather for the sake of performance, it combines the given classes into a new one. The combination is done in an order, from the first given class argument to the last. Certain Access Specifiers such as Protected can change how this system behaves.

To create class "D", Class++ takes the class arguments one by one in an order, from left to right, and combines their classData and the given classData table into a new class. So due to this order, class "B"'s members and methods will automatically be overwritten by class "C"'s, therefore the method that will exist on class "D", will be the method of class "C"'s.

This is similar to the Python's MRO (Method Resolution Order).

local class = ClassPP.class

local A = class "A" { 
    Public = {
        method = function(self)
            print("Method from A")
        end
    }
}

local B = class "B" (A, nil) { 
    Public = {
        method = function(self)
            print("Method from B")
        end
    }
}

local C = class "C" (A, nil) { -- Derived Class
    Public = {
        method = function(self)
            print("Method from C")
        end
    }
}

local D = class "D" (B, C) {} -- Derived Class

local newObject = D.new()
newObject:method() -- Prints "Method from C"!