Lua Object Oriented
Lua - Object-Oriented Programming
Section titled “Lua - Object-Oriented Programming”Introduction to OOP Concepts
Section titled “Introduction to OOP Concepts”Object-Oriented Programming (OOP) is a paradigm based on the concept of “objects”, which can contain data in the form of fields (often known as attributes or properties) and code in the form of procedures (often known as methods). Key OOP features include:
- Objects: Instances that bundle state (data) and behavior (methods).
- Classes: Blueprints or templates for creating objects (less direct in Lua, which is prototype-based).
- Encapsulation: Bundling data and methods that operate on the data within one unit, and restricting direct access to some of an object’s components.
- Inheritance: A mechanism where new classes (or objects in Lua) derive properties and methods from existing ones.
- Polymorphism: The ability of an object to take on many forms, typically allowing methods to do different things based on the object it is acting upon.
OOP in Lua: Prototype-Based
Section titled “OOP in Lua: Prototype-Based”Lua does not have built-in classes in the traditional sense like Java or C++. Instead, OOP in Lua is typically implemented using tables and metatables, following a prototype-based approach. Objects inherit directly from other objects (their prototypes).
- Objects as Tables: A Lua object is usually represented by a table. Its fields store the object’s state, and its methods are functions stored in the table (or its prototype).
- Prototypes and
__index: Inheritance is achieved by setting a table’s metatable and using the__indexmetamethod to point to its prototype. If a field or method is not found in an object, Lua looks it up in the prototype via__index. selfconvention: Methods are often defined and called using the colon operator (:), which implicitly passes the object itself as the first argument, conventionally namedself.
A Simple Class-like Structure: Rectangle
Section titled “A Simple Class-like Structure: Rectangle”Let’s create a ‘class’ (prototype table) for a Rectangle.
-- Rectangle.lua (our prototype or 'class' table)local Rectangle = {}Rectangle.__index = Rectangle -- Key for inheritance: methods looked up in Rectangle itself
-- Constructor function (conventionally 'new')function Rectangle:new(initial_length, initial_breadth) local o = {} -- Create a new empty table (the instance) setmetatable(o, self) -- Set its metatable to Rectangle (or a derived class) -- self here will be Rectangle when called as Rectangle:new() o.length = initial_length or 0 o.breadth = initial_breadth or 0 return oend
-- Method to calculate areafunction Rectangle:calculateArea() return self.length * self.breadthend
-- Method to print areafunction Rectangle:printArea() print("The area of the rectangle is: ", self:calculateArea())end
-- To make this a module, you would typically return the Rectangle table-- return RectangleCreating and Using Objects (Instances)
Section titled “Creating and Using Objects (Instances)”To use our Rectangle ‘class’, we first need to ensure it’s loaded (e.g., via require if it’s in a separate file, or just defined in the same scope).
-- Assuming Rectangle definition from above is available
-- Create an instance (object) of Rectanglelocal rect1 = Rectangle:new(10, 5)
-- Access propertiesprint("Rectangle 1 Length:", rect1.length) -- Output: 10print("Rectangle 1 Breadth:", rect1.breadth) -- Output: 5
-- Call methodsrect1:printArea() -- Output: The area of the rectangle is: 50
local rect2 = Rectangle:new(7, 3)rect2:printArea() -- Output: The area of the rectangle is: 21How self and : work:
- When you define
function Rectangle:new(...), it’s syntactic sugar forRectangle.new = function(self, ...). - When you call
rect1:printArea(), it’s syntactic sugar forrect1.printArea(rect1).
Inheritance: Creating a Square from Shape
Section titled “Inheritance: Creating a Square from Shape”Let’s first define a more generic Shape prototype and then have Square inherit from it.
-- Shape.lua (Base prototype)local Shape = {}Shape.__index = Shape
function Shape:new() local o = {} setmetatable(o, self) return oend
function Shape:getType() return "Generic Shape"end
-- Square.lua (Derived prototype)local Square = Shape:new() -- Create Square object, inheriting from Shape initially -- (or Square = setmetatable({}, Shape) then Square.__index = Square for methods specific to Square)Square.__index = Square -- Methods not in instance look in Square, then Shape via metatable chainingsetmetatable(Square, Shape) -- Square's metatable is Shape, so Square 'is-a' Shape prototype
-- Square's own constructorfunction Square:new(side) local o = Shape.new(self) -- Call Shape's 'new' but ensuring metatable is set to Square -- A more robust way: local o = {}; setmetatable(o, self) o.side = side or 0 return oend
-- Override Shape's getType methodfunction Square:getType() return "Square"end
-- Square-specific methodfunction Square:calculateArea() return self.side * self.sideend
function Square:printArea() print("The area of the square (side " .. self.side .. ") is: " .. self:calculateArea())end
-- Example Usage:local generic_shape = Shape:new()print(generic_shape:getType()) -- Output: Generic Shape
local my_square = Square:new(7)print(my_square:getType()) -- Output: Square (overridden method)my_square:printArea() -- Output: The area of the square (side 7) is: 49
-- Demonstrating inheritance chain for methods not in Square itself:-- If Square had a metatable like { __index = Shape } and Square.getType was nil,-- my_square:getType() would resolve to Shape:getType().-- Our current setup Square.__index = Square; setmetatable(Square, Shape) achieves this for 'class' methods-- and instance methods are resolved via instance_mt.__index = Square.Explaining the inheritance for Square:
local Square = Shape:new()or more clearlySquare = {}.setmetatable(Square, { __index = Shape }). This makesSquareinherit fromShape. If a key is not inSquare, Lua looks inShape.Square.__index = Square. For instances ofSquare, if a key is not in the instance, Lua looks in theSquaretable itself.- The constructor
Square:new(side): Creates an instanceo, sets its metatable toSquare(sooinherits fromSquare), and initializes its specific properties.
A more common pattern for `Square:new` related to `Shape:new`:-- More robust Square constructor, assuming Shape has a constructor Shape:new(o_template)-- Shape.lua-- ... (Shape prototype as before)-- function Shape:new(o_template)-- o_template = o_template or {}-- setmetatable(o_template, self)-- self.__index = self -- ensure instances lookup methods in their class prototype-- return o_template-- end
-- Square.lua-- ... (Square prototype setup, Square.__index = Square, setmetatable(Square, {__index = Shape}))-- function Square:new(side)-- local o = Shape:new(self) -- 'self' here is Square, so Shape:new correctly sets metatable for Shape part-- -- However, o's metatable is now Shape. We need it to be Square.-- -- A better way:-- local o = {}-- setmetatable(o, self) -- self is Square, so o's metatable points to Square-- Shape.initialize(o) -- if Shape had an initialize method (optional)-- o.side = side or 0-- return o-- end
-- The simplest pattern for Square inheriting Rectangle (from first example):-- Rectangle prototype as defined initially
-- Square prototypelocal Square = {}setmetatable(Square, {__index = Rectangle}) -- Square inherits from RectangleSquare.__index = Square -- Instances of Square look up methods in Square first
function Square:new(side) local o = {} setmetatable(o, self) -- self is Square table when called as Square:new() o.length = side -- Use fields expected by Rectangle if reusing its methods o.breadth = side -- o.side = side -- Or use its own field return oend
-- Optionally override methodsfunction Square:printArea() -- If using .side: -- print("The area of the square is: ", self.side * self.side) -- If reusing Rectangle's calculateArea via inherited fields: print("The area of the square is: ", Rectangle.calculateArea(self))end
local sq = Square:new(4)sq:printArea() -- Will call Square's printArea-- To call Rectangle's calculateArea if it wasn't shadowed:-- print(Rectangle.calculateArea(sq))Practical Application: OOP in Lua is used extensively in game development for character entities, UI elements, and game systems. It’s also used in larger applications for structuring code into manageable and reusable components.
Further Learning: Explore concepts like mixins, multiple inheritance (can be emulated with __index functions), and different OOP implementation patterns in Lua. The book “Programming in Lua” by Roberto Ierusalimschy provides excellent chapters on this topic.