Object Oriented Programming in Lua

Prerequisites

In order to get the best understanding from this blog post I suggest you have at least a basic understanding of the topics listed below. There are available resources to gain knowledge on each of these topics.

  • Metatables and Metamethods
  • Tables
  • Iteration
  • Modules

What is Object Oriented Programming

Object oriented programming is a programming paradigm based on the concept of classes and objects. It is used to structure a software program into simple, reusable pieces of code, called classes, which are used to create individual instances of objects.

Class

Think of a class as a blueprint. A class is a set of instructions on what an object should be. In a literal sense, these set of instructions are assigning attributes that every object of the class will contain.

Instance

Think of an object as a child of a class. For instance, if we have a class ‘Car’ then children of class ‘Car’ could be considered as ‘Audi’, ‘Nissan’, and ‘Volvo’.

Creating A Class in Lua

Unlike other programming languages, Lua doesn’t provide a quick and easy way to construct classes. So as developers, we have to use other means to create these classes. By using metatables you’re able to create a table that can clone itself to other tables. Let’s create a class ‘Car’ with a constructor ‘:new()’

object-oriented-example

local Car = {}; -- This table is acting as a Class.

function Car:Drive()
	print('Driving', self.BrandName..'.');
end

function Car:Park()
	print('Putting', self.BrandName, 'in park.');
end

function Car:new(BrandName) -- Constructor for class Car
	local newCar = {};
	newCar.BrandName = BrandName;

	setmetatable(newCar, self);
	self.__index = self;

	return newCar;
end

local audi = Car:new('Audi'); --Creating a new instance of Car.
audi:Drive(); --Output// Driving Audi.
audi:Park(); --Output//Putting Audi in park.

local nissan = Car:new('Nissan');
nissan:Drive(); --Output//Driving Nissan.
nissan:Park(); --Output// Putting Nissan in park.

local volvo = Car:new('Volvo');
volvo:Drive(); --Output// Driving Volvo.
volvo:Park(); --Output// Putting Volvo in park.

Above we’ve created a class ‘Car’ and set a Brand Name parameter to the constructor. Now when we called Car:new(‘Audi’), we’ll produce a new ‘Car’ instance with the attribute brand name and the methods Drive and Park. How are we able to gain access to the Drive and Park methods?

The reason why we’re assigning the table newCar a metatable is so that we can gain access to the metamethod __index. When the metamethod __index is assigned to a table the table that’s assigned gets indexed for the element.

index-metamethod

Let’s see an example of this below:

local tab1 = {};
local tab2 = {
	Orange = 'Oranges',
	Apple = 'Apples',
	Grape = 'Grapes'
};

setmetatable(tab1, tab2);
tab2.__index = tab2; -- When tab1 is indexed for something that doesn't exist check tab2.

print(tab1.Orange); --Output// Oranges

As you can see because we set the __index method equal to tab2, then when tab1, the parent table, gets indexed for ‘Orange’ which doesn’t exist. It checks itself for the element and returns it. This essentially is cloning everything from tab2 into tab1 which gives us access to all the elements that’s available in tab2. Now back to our Car example.

local Car = {}; -- This table is acting as a Class.

function Car:Drive()
	print('Driving', self.BrandName..'.');
end

function Car:Park()
	print('Putting', self.BrandName, 'in park.');
end

function Car:new(BrandName) -- Constructor for class Car
	local newCar = {};
	newCar.BrandName = BrandName;

	setmetatable(newCar, self);
	self.__index = self;

	return newCar;
end

Since we’re setting self as a metatable to newCar we’re giving newCar access to all the methods that the class ‘Car’ contains. This is how you would construct a basic class in Lua using metamethods and metatables.

Using Modules To Abstract Classes

One of the core principles of Object Oriented Programming is the act of abstraction or the hiding of methods that subscribers shouldn’t or needn’t use. Let’s take a look at how we can use a Module to abstract our ‘Car’ class.

First let’s create a Module named ‘CarClass’. (I prefer to have all of my class files end with ‘Class’, this is a personal convention.) Now inside of our module let’s define our ‘Car’ class, methods, and constructor.

-- MODULE --
local CarClass = {}; -- Car Class

function CarClass:Drive()
	print('Driving', self.BrandName..'.');
end

function CarClass:Park()
	print('Putting', self.BrandName, 'in park.');
end

function CarClass:new(BrandName) -- Constructor for class Car
	local newCar = {};
	newCar.BrandName = BrandName;

	setmetatable(newCar, self);
	self.__index = self;

	return newCar;
end

return CarClass;

And inside of a server script let’s create new instances of our ‘Car’ class.

local Car = require(game.ServerScriptService:WaitForChild('CarClass'));

local audi = Car:new('Audi'); --Creating a new instance of Car.
audi:Drive(); --Output// Driving Audi.
audi:Park(); --Output//Putting Audi in park.

local nissan = Car:new('Nissan');
nissan:Drive(); --Output//Driving Nissan.
nissan:Park(); --Output// Putting Nissan in park.

local volvo = Car:new('Volvo');
volvo:Drive(); --Output// Driving Volvo.
volvo:Park(); --Output// Putting Volvo in park.

Since our ‘Car’ class is now in a module we can create some private methods to abstract our ‘Car’ class. Let’s create some the methods ‘turnCarOff’ and ‘setUpPark’ that are private and abstracted from user view.

local CarClass = {}; -- Car Class

function CarClass:new(BrandName) -- Constructor for class Car
	local newCar = {};
	newCar.BrandName = BrandName;

	setmetatable(newCar, self);
	self.__index = self;

	return newCar;
end

function CarClass:Drive()
	setUpDrive(self);
	print('Driving', self.BrandName..'.');
end

function CarClass:Park()
	print('Putting', self.BrandName, 'in park.');
	turnOffCar(self);
end

-- Private
function setUpDrive(car)
	print('Putting on seat belt then putting '..car.BrandName..' in drive.');
end

function turnOffCar(car)
	print('Turning '..car.BrandName.. ' off.');
end

return CarClass;

Now when a user requires our ‘Car’ Module they won’t have access to the private methods that we’ve declared.

This concludes my blog post on object oriented programming in Lua. If you’d like more information refer to the links below.

Helpful Resources: