Skip to content

Subclasses

Subclasses allow you to "extend" the functionality of another class (or subclass). Once you create a subclass, you gain all the methods and properties of the class you are subclassing and can then build upon it.

This is a powerful concept in OOP which spares you from rewriting functionality, and instead adding only what you need to an existing class.

Discussion

Conventions

Generally a subclass name will be represented as a capitalized string identifier. For example "Boss" is a good subclass identifier.

Subclass Creation

Subclasses are created using the extends method on the class you want to extend.

Super Constructor

When creating a subclass, we are inheriting functionality from another class. The class being subclassed is the "parent" or "superclass" of the subclass.

When we need access to the "parent", we use the super property of the subclass. This is used most often when a subclass contains a constructor function to pass information up the inheritance chain.

The following example and discussion demonstrates how this works:

local Classy = require("plugin.classy")

--Person base class
local Person = Classy.create("Person")
function Person:constructor(name)
  self.name = name
end

function Person:getName()
  return self.name
end

--Manager subclass
local Manager = Person:extends("Manager")
function Manager:constructor(name)
  --# Pass up the arguments to the parent class
  Manager.super.constructor(self, name)
end

--Boss subclass
local Boss = Person:extends("Boss")
function Boss:constructor(name)
  --# Pass up the arguments to the parent class
  Boss.super.constructor(self, name)
end

--instances
local manager = Manager:new("Jenny")
print(manager:getName()) --> Jenny

local boss = Boss:new("Jorge")
print(boss:getName()) --> Jorge

In the example above, we use a Person class to represent a person. We have only defined a name property and getName method, but could add many others. All sublasses of a Person class "type" will have a name associated with them.

We only want to store the name property once. We also have access to the Person:getName() method, which again, we only want to define once for any subclass of a Person "type".

Our subclasses represent a Manager and Boss, which both fit under the Person "type" and so we don't need to recreate the name property or getName method, which lives in the Person base class.

But, we do need to make sure the Person base class gets the name property, so we call the <Class>.super.constructor function in each of our subclasses constructor functions, which passes the name up.

Because both the Manager and Boss are subclasses of the Person class, their super property points to the Person base class. You can use the super property to call any methods or manipulate any properties on the "parent" class. This is shown in some examples below.

Pay attention to the signature of the super.constructor function. You need to make sure you pass a reference of self to it, along with any additional arguments, and use only dot (.) syntax:

<Class>.super.constructor(self[, args])

Subclass Examples

Basic SubClass

local Classy = require("plugin.classy")

--base class (with defaults)
local Person = Classy.create("Person", { age = 24 })

function Person:getAge()
  return self.age
end

--subclass
local Boss = Person:extends("Boss")

--#########################################################
--# Instance Examples
local boss = Boss:new()

print(boss:getAge()) --> 24 (Person default)

Subclass with Defaults

local Classy = require("plugin.classy")

--base class
local Person = Classy.create("Person", { name = "Unknown", age = 25 })

function Person:getAge()
  return self.age
end

function Person:getName()
  return self.name
end

--subclass
local Boss = Person:extends("Boss", { title = "Big Cheese" })

function Boss:getTitle()
  return self.title
end

--#########################################################
--# Instance Examples
local boss = Boss:new()

print(boss:getTitle()) --> Big Cheese
print(boss:getName()) --> Unknown
print(boss:getAge()) --> 25

SubClass with Constructor

local Classy = require("plugin.classy")

--base class
local Person = Classy.create("Person")
function Person:constructor(name, age)
  self.name = name
  self.age = age
end

function Person:getAge()
  return self.age
end

function Person:getName()
  return self.name
end

--subclass
local Boss = Person:extends("Boss")
function Boss:constructor(name, age, title)
  Boss.super.constructor(self, name, age)

  self.title = title
end

function Boss:getTitle()
  return self.title
end

--#########################################################
--# Instance Examples
local boss = Boss:new("Mary", 42, "Big Cheese")

print(boss:getName()) --> Mary
print(boss:getAge()) --> 42
print(boss:getTitle()) --> Big Cheese

local person = Person:new("Jane", 32)

print(person:getName()) --> Jane
print(person:getTitle()) -- error (method not on Person class)

Subclass Method Override

local Classy = require("plugin.classy")

--base class
local Person = Classy.create("Person")
function Person:constructor(name)
  self.name = name
end

function Person:getName()
  return self.name
end

--subclass
local Boss = Person:extends("Boss")
function Boss:constructor(name)
  Boss.super.constructor(self, name)
end

--override
function Boss:getName()
  return "I'm the boss, "..self.name
end

--#########################################################
--# Instance Examples
local person = Person:new("Jimmy")
local boss = Boss:new("Poppy")

print(person:getName()) --> Jimmy
print(boss:getName()) --> I'm the boss, Poppy

Subclass Method Override with Super Call

local Classy = require("plugin.classy")

--base class
local Person = Classy.create("Person")
function Person:constructor(name)
  self.name = name
end

function Person:printName()
  print("My name is "..self.name)
end

--sub class (with defaults)
local Boss = Person:extends("Boss", { title = "The Main Taco" })
function Boss:constructor(name)
  Boss.super.constructor(self, name)
end

--override
function Boss:printName()
  --call base class method
  Boss.super.printName(self)

  print ("I am "..self.title)
end

--#########################################################
--# Instance Examples
local boss = Boss:new("Marky")
boss:printName()
-- Outputs
--> My name is Marky
--> I am The Main Taco

Subclassing Subclasses

local Classy = require("plugin.classy")

--base class
local Person = Classy.create("Person")
function Person:constructor(name)
  self.name = name
end

--Boss subclass (with defaults)
local Boss = Person:extends("Boss", { title = "Top Dog" })
function Boss:constructor(name)
  Boss.super.constructor(self, name)
end

--Meal subclass
local Meal = Boss:extends("Meal")
function Meal:constructor(name, food)
  Meal.super.constructor(self, name)

  self.food = food
end

function Meal:getLunch()
  local sf = string.format

  local name = self.name
  local title = self.title
  local food = self.food

  return sf("%s the %s is having a %s for lunch.", name, title, food) 
end

--#########################################################
--# Instance Examples
local meal1 = Meal:new("Tony", "Sandwhich")
print(meal1:getLunch())
--> Tony the Top Dog is having a Sandwhich for lunch.

local meal2 = Meal:new("Tammy", "Salad")
print(meal2:getLunch())
--> Tammy the Top Dog is having a Salad for lunch.