Object-Oriented Programming (OOP) is a very popular computer programming paradigm that designs applications by creating and manipulating objects.
The following programming languages support object-oriented programming:
- C++
- Java
- Objective-C
- Smalltalk
- C#
- Ruby
Lua is a lightweight scripting language. Although it doesn't have built-in powerful object-oriented (OO) features like Java or C++, it is very flexible and can implement object-oriented programming through some techniques.
Object-Oriented Features
- Encapsulation: Bundles data and methods together, hides implementation details, exposes only necessary interfaces, improving security and maintainability.
- Inheritance: Reuses and extends existing code by deriving new classes, reducing repetitive coding, improving development efficiency and scalability.
- Polymorphism: The same operation behaves differently when applied to different objects, supporting unified interface calls, enhancing flexibility and extensibility.
- Abstraction: Simplifies complex problems, defines core classes and interfaces, hides unnecessary details, making it easier to manage complexity.
Object-Oriented Programming in Lua
We know that objects consist of attributes and methods.
In Lua, classes can be simulated using tables and functions.
As for inheritance, it can be simulated using metatables (not recommended for complex use, but simulating the most basic objects is sufficient for most implementations).
In Lua, the most fundamental structure is the table. We can use tables to create objects.
ClassName = {} -- Create a table as a class
Create objects through the new method (or other names) and initialize the object's attributes.
function ClassName:new(...)
local obj = {} -- Create a new empty table as the object
setmetatable(obj, self) -- Set the metatable, allowing the object to inherit the class's methods
self.__index = self -- Set the __index metamethod
-- Initialize the object's attributes
obj:init(...) -- Optional: Call an initialization function
return obj
end
Tables are the most fundamental composite data type in Lua and can be used to represent an object's attributes.
Functions in Lua can represent methods:
function ClassName:sayHello()
print("Hello, my name is " .. self.name)
end
Use the new method to create objects and call the class's methods through the object.
local obj = ClassName:new("Alice") -- Create an object
obj:sayHello() -- Call the object's method
In Lua, a table can be considered a variant of an object. Like an object, a table has state (member variables) and can represent an independent entity.
A table not only has data members but can also contain member functions similar to object methods:
Example
-- Define the Person class
Person = {name = "", age = 0}
-- Constructor for Person
function Person:new(name, age)
local obj = {} -- Create a new table as the object
setmetatable(obj, self) -- Set the metatable, making it an instance of Person
self.__index = self -- Set the __index metamethod, pointing to Person
obj.name = name
obj.age = age
return obj
end
-- Add a method: print personal information
function Person:introduce()
print("My name is " .. self.name .. " and I am " .. self.age .. " years old.")
end
Code Explanation:
Personis a table with two attributes:nameandage. These are the default attributes of the class.Person:new(name, age)is a constructor used to create new Person objects.local obj = {}creates a new table as the object.setmetatable(obj, self)sets the metatable, making the table an instance of the Person class.self.__index = selfsets the __index metamethod, allowingobjto access Person class attributes and methods.introduceis a method of the Person class that prints the Person object's name and age.
Calling the Method:
-- Create a Person object
local person1 = Person:new("Alice", 30)
-- Call the object's method
person1:introduce() -- Output: "My name is Alice and I am 30 years old."
A Simple Example
The following simple class contains three attributes: area, length, and breadth. The printArea method is used to print the calculation result:
Example
-- Define the Rectangle class
Rectangle = {area = 0, length = 0, breadth = 0}
-- Constructor for creating Rectangle objects
function Rectangle:new(o, length, breadth)
o = o or {} -- If no object is passed, create a new empty table
setmetatable(o, self) -- Set the metatable, allowing it to inherit Rectangle's methods
self.__index = self -- Ensure methods and attributes can be found when accessed
o.length = length or 0 -- Set length, default to 0
o.breadth = breadth or 0 -- Set breadth, default to 0
o.area = o.length * o.breadth -- Calculate area
return o
end
-- Print the rectangle's area
function Rectangle:printArea()
print("Rectangle area is ", self.area)
end
Creating Objects
Creating objects is the process of allocating memory for instances of a class. Each class has its own memory and shares common data:
r = Rectangle:new(nil, 10, 20)
Accessing Attributes
We can use the dot . to access class attributes:
print(r.length)
Accessing Member Functions
We can use the colon : to access class member functions:
r:printArea()
Memory is allocated when the object is initialized.
Complete Example
Below we demonstrate a complete example of Lua object-oriented programming:
Example
-- Define the Rectangle class
Rectangle = {area = 0, length = 0, breadth = 0}
-- Constructor for creating Rectangle objects
function Rectangle:new(o, length, breadth)
o = o or {} -- If no object is passed, create a new empty table
setmetatable(o, self) -- Set the metatable, allowing it to inherit Rectangle's methods
self.__index = self -- Ensure methods and attributes can be found when accessed
o.length = length or 0 -- Set length, default to 0
o.breadth = breadth or 0 -- Set breadth, default to 0
o.area = o.length * o.breadth -- Calculate area
return o
end
-- Print the rectangle's area
function Rectangle:printArea()
print("Rectangle area is ", self.area)
end
-- Run the example:
local rect1 = Rectangle:new(nil, 5, 10) -- Create a rectangle with length 5 and breadth 10
rect1:printArea() -- Output: "Rectangle area is 50"
local rect2 = Rectangle:new(nil, 7, 3) -- Create a rectangle with length 7 and breadth 3
rect2:printArea() -- Output: "Rectangle area is 21"
Executing the above program, the output is:
Rectangle area is 50
Rectangle area is 21
Lua Inheritance
Inheritance refers to an object directly using the attributes and methods of another object. It can be used to extend the attributes and methods of a base class.
Inheritance in Lua is implemented by setting the metatable of a subclass.
We can create a new table and set its metatable to the parent class.
The following example shows the Square class inheriting from the Rectangle class's attributes and methods, and making modifications based on it.
-- Define the Rectangle class
Rectangle = {area = 0, length = 0, breadth = 0}
-- Constructor for creating Rectangle objects
function Rectangle:new(o, length, breadth)
o = o or {} -- If no object is passed, create a new empty table
setmetatable(o, self) -- Set the metatable, allowing it to inherit Rectangle's methods
self.__index = self -- Ensure methods and attributes can be found when accessed
o.length = length or 0 -- Set length, default to 0
o.breadth = breadth or 0 -- Set breadth, default to 0
o.area = o.length * o.breadth -- Calculate area
return o
end
-- Print the rectangle's area
function Rectangle:printArea()
print("Rectangle area is ", self.area)
end
-- Define the Square class, inheriting from Rectangle
Square = Rectangle:new() -- Square inherits from Rectangle class
-- Override the constructor (sides of a square are equal)
function Square:new(o, side)
o = o or {} -- If no object is passed, create a new empty table
setmetatable(o, self) -- Set the metatable, allowing it to inherit Rectangle's methods
self.__index = self -- Ensure methods and attributes can be found when accessed
o.length = side or 0 -- Set side length
o.breadth = side or 0 -- Square's breadth and length are equal
o.area = o.length * o.breadth -- Calculate area
return o
end
-- Run the example:
local rect = Rectangle:new(nil, 5, 10) -- Create a rectangle with length 5 and breadth 10
rect:printArea() -- Output: "Rectangle area is 50"
local square = Square:new(nil, 4) -- Create a square with side length 4
square:printArea() -- Output: "Rectangle area is 16"
Rectangle Class: Remains the basic class for rectangles, with length, breadth, and area attributes, and methods to calculate and print the area.
Square Class Inherits from Rectangle: The Square class inherits from the Rectangle class using Rectangle:new(). Since a square's length and breadth are equal, we override the constructor in the Square:new method, setting length and breadth to the same value (i.e., side).
Overriding the Constructor: The Square:new(o, side) method initializes the length and breadth attributes using the passed side length side when creating a square object, and calculates the area.
Running result:
Rectangle area is 50
Rectangle area is 16
Method Overriding
In Lua, method overriding (also known as method redefinition) refers to the redefinition or replacement of an existing method in a parent class by a subclass during inheritance.
A subclass can modify or extend the behavior of a parent class method as needed.
In the above example, the Square class overrides the Rectangle class's constructor, thereby changing the object's initialization method, specifically setting the rectangle's length and breadth to the same value, because the characteristic of a square is that its sides are equal.
Next, we demonstrate how to override methods through an Animal class and a Dog class that inherits from it.
-- Define the Animal class
Animal = {name = "Unknown"}
-- Constructor for Animal class
function Animal:new(o, name)
o = o or {} -- If no object is passed, create a new empty table
setmetatable(o, self) -- Set the metatable, allowing it to inherit Animal's methods
self.__index = self -- Allow the object to access Animal's methods
o.name = name or "Unknown" -- Set name, default to "Unknown"
return o
end
-- Animal class method: speak
function Animal:speak()
print(self.name .. " makes a sound.")
end
-- Define the Dog class, inheriting from Animal
Dog = Animal:new() -- Dog inherits from Animal class
-- Override the Dog class constructor
function Dog:new(o, name, breed)
o = o or {} -- If no object is passed, create a new empty table
setmetatable(o, self) -- Set the metatable, allowing it to inherit Dog and Animal's methods
self.__index = self -- Allow the object to access Dog's methods
o.name = name or "Unknown"
o.breed = breed or "Unknown"
return o
end
-- Override the Dog class's speak method (overrides Animal's speak method)
function Dog:speak()
print(self.name .. " barks.")
end
-- Create an Animal object
local animal = Animal:new(nil, "Generic Animal")
animal:speak() -- Output: "Generic Animal makes a sound."
-- Create a Dog object
local dog = Dog:new(nil, "Buddy", "Golden Retriever")
dog:speak() -- Output: "Buddy barks."
AnimalClass: Defines a base classAnimalwith anameattribute and aspeakmethod. Thespeakmethod is a default implementation that outputs "some animal makes a sound".DogClass Inherits fromAnimal: TheDogclass inherits fromAnimaland creates its own instances using theDog:new()method.- Overriding the
speakMethod: In theDogclass, thespeakmethod is overridden, changing its behavior from the parent class's "makes a sound" to "barks". This is an example of method overriding, where the subclass (Dog) changes the behavior of the parent class (Animal) method.
Running result:
Generic Animal makes a sound.
Buddy barks.
Polymorphism
Polymorphism in Lua is implemented through metatables and method overriding. When different types of objects call the same method, Lua executes different methods based on the object's actual type.
Example
-- Define a "class" (actually a table)
Person = {}
-- Add a constructor to the "class"
function Person:new(name, age)
local obj = {} -- Create a new table as the object
setmetatable(obj, self) -- Set the metatable, indicating it's an instance of Person
self.__index = self -- Set the __index metamethod, pointing to Person
obj.name = name
obj.age = age
return obj
end
-- Add a method
function Person:greet()
print("Hello, my name is " .. self.name)
end
-- Define a subclass Student inheriting from Person
Student = Person:new()
-- Subclass overrides the parent class's method
function Student:greet()
print("Hi, I'm a student and my name is " .. self.name)
end
local person2 = Person:new("Charlie", 25)
local student2 = Student:new("David", 18)
-- Polymorphism: Different types of objects call the same method
person2:greet() -- Output: "Hello, my name is Charlie"
student2:greet() -- Output: "Hi, I'm a student and my name is David"
Although person2 and student2 call the same greet method, because their types are different, Lua calls the appropriate version for each.
Running result:
Hello, my name is Charlie
Hi, I'm a student and my name is David
Other Object-Oriented Concepts
Encapsulation
Encapsulation is typically achieved by encapsulating data and methods within a table. We can simulate encapsulation by controlling access to the table, for example, using metamethods to restrict external access.
Example
-- Define a "class" (actually a table)
Person = {}
-- Add encapsulation: hide attributes
function Person:new(name, age)
local obj = {}
setmetatable(obj, self)
self.__index = self
obj.name = name
obj.age = age
return obj
end
function Person:setName(name)
self.name = name -- Provide a method to modify name
end
function Person:getName()
return self.name -- Provide a method to get name
end
In this way, we can control access to attributes, simulating encapsulation.
Abstraction
Abstraction refers to simplifying complex things and hiding unnecessary details. Although Lua itself doesn't have the concept of classes, we can achieve abstraction through encapsulation.
Example
-- Only expose the interface, not the implementation details
function Person:showInfo()
print("Name: " .. self.name)
print("Age: " .. self.age)
end
YouTip