headings:
- Basic Understanding
- Examples
- Using Inheritance in OOP
- Interacting with other objects
- Using polymorphism and abstraction
- Encapsulation
- Composition
- More Examples
- Conclusion
Basic Understanding
Firstly, what the heck is OOP?
OOP, short for Object-Oriented Programming, is all about organizing your code around objects. Think of objects as these nifty little things that bundle together data and behavior in a nice, neat package.
In OOP, you have classes, which are like blueprints or templates for creating objects. You use a class to define the structure and behavior of an object, and then you can create multiple instances of that class, each with its own unique data.
so, why do we bother with this object stuff? well, it helps us manage complexity and make our code more organized and clean. Instead of dealing with a junk of functions and variables all over the place, we can group related things together in objects. It’s like putting your code into these little boxes that are easier to work with.
and hey, objects can interact with each other too! They can share data, and work together to get things done. It’s like a team effort, where each object plays its part to achieve the goal!
that’s OOP in a nutshell. It’s all about objects, classes, and organizing your code in a way that fits you.
more detailed info & the concepts of OOP
In OOP, objects are instances of classes, which serve as blueprints or templates for creating objects. Objects encapsulate data (attributes or properties) and behavior (methods or functions) into one.
(classes and objects are different)
some key concepts of OOP:
-
Classes
Classes are user defined data types that define the structure and behavior of objects. A class acts as a blueprint, specifying the attributes and methods that objects of that class. It defines the common properties and behaviors shared by all objects created from it. -
Objects
Objects are instances of classes. They represent specific things within a program. Each object has its own unique state, defined by the values of its attribute/properties, and can perform operations or actions through its methods. Objects interact with each other by method calls -
Inheritance
Inheritance enables the creation of new classes (derived or child classes) based on existing classes (base or parent classes). The derived classes inherit attributes and methods from their parent class, allowing for code reuse and the establishment of hierarchical relationships. Inheritance promotes the concept of “is-a” relationships, where a derived class is a specialized version of its parent class. -
Polymorphism
Polymorphism allows objects of different classes to be treated uniformly, enabling the use of a common interface. -
Abstraction
Abstraction provides a way to define interfaces or abstract classes with methods that must be implemented by derived classes. -
Encapsulation
Encapsulation is the bundling of data and methods within a class. It allows for data hiding and abstraction, where the internal workings and implementation details of an object are hidden from outside access. Encapsulation helps ensure data integrity and provides a clear interface for interacting with objects. -
Composition
Composition is a key concept in object-oriented programming (OOP) that describes a relationship between objects where one object is composed of one or more other objects. It represents a “has-a” relationship, where the composed objects cannot exist independently of the main object and have a strong lifecycle dependency.
Examples
lets create a house class with OOP!!!
local House = {}
local Static = {} -- a separate table for methods
function House.new()
return setmetatable({
rooms = {} -- define the "rooms" property
}, {__index = Static}) -- assign the new "House" object to the Static table (where the methods go)
end
function Static:AddRoom(roomName: string) -- a method to add a new room to the house
table.insert(self.rooms, roomName) -- self is the house object referenced
end
function Static:PrintRooms() -- another method to print the rooms in the house
table.foreach(self.rooms, print)
end
return House
you may say, bb-but what is __index and setmetatable!!!
in simple terms, setmetatable is a function in Lua that allows you to associate a metatable with a table. A metatable is essentially a table that defines special behavior and properties for the original table.
when you use setmetatable, you are essentially saying, “hey, this table should have a special metatable that defines how certain operations or lookups should do.”
- “ok how about __index??”
__index fires when table[index] is indexed, if table[index] is nil. can also be set to a table, in which case that table will be indexed.
in this case, __index is set to Static, which is where methods go, allowing us to access and call the methods
- “ok cool, but why and what is self”
in Lua, self
is a special variable that is used inside methods of an object-oriented Lua class. It refers to the instance of the class itself.
local Person = {}
local Static = {}
function Person.new()
return setmetatable({
chores_list = {"do the dishes"}
}, {__index = Static})
end
function Static.doChores(self) -- `Static.doChores(self)` is the same as `Static:doChores()`
print(self)
-- output:
-- {
-- chores_list = {"do the dishes"}
-- doChores = function
-- }
end
think of self as script, script
refers to the script its running the code from, and in this case, self refers to the person object
now lets create my dream house!!! which consists of a gaming room and a bedroom
local House = require(...) -- require the house module
local myHouse = House.new() -- creates a new house object with the .new constructor
myHouse:AddRoom("gaming room") -- add a gaming room
myHouse:AddRoom("bedroom") -- add a bedroom
myHouse:PrintRooms() -- (1, gaming room), (2, bedroom)
now, you might be asking, why the heck do i need to do all this? and that i respond with, you don’t, its a style ; as i has said, it is an approach to organizing your code around “objects”
Using Inheritance in OOP
i am going to make an Furniture class as the “Base” or “Parent” class
local Furniture = {}
local Static = {}
function Furniture.new(name, material)
return setmetatable({
name = name,
material = material
}, {__index = Static})
end
function Static:printAttributes()
for attr, v in self do
if type(v) == 'function' then continue end
print(attr, v)
end
end
return Furniture
now, lets say i want to make a Chair class with a new attribute, but i don’t want to repeat and copy paste the Furniture class code to the Chair class
local Furniture = require(...)
local Chair = {}
function Chair.new(name, material, legs)
local self = Furniture.new(name, material) -- create a new furniture class
self.legs = legs -- assign the new attribute/property
return self
end
return Chair
Interacting with other objects (Association)
lets create a “Planet” class with a method to get the distance between the planet and the other planet
local Planet = {}
local Static = {}
function Planet.new(x, y, z)
return setmetatable({
position = Vector3.new(x or 0, y or 0, z or 0) -- create a new vector3 object
}, {__index = Static})
end
function Static:GetDistanceFromPlanet(otherPlanet)
local diff = otherPlanet.position - self.position
local magnitude = diff.Magnitude -- the distance
return magnitude
end
return Planet
local Planet = require(...)
local planetA = Planet.new(0, 0, 0)
local planetB = Planet.new(100, -10, 100)
local dist = planetA:GetDistanceFromPlanet(planetB)
print(dist)
Using polymorphism and abstraction
-- Abstract base class: Shape
local Shape = {}
function Shape.new()
error("Shape is an abstract class and cannot be instantiated directly.")
end
return Shape
local Shape = require(...)
-- Derived class: Circle (inherit from Shape)
local Circle = setmetatable({}, {__index = Shape})
local Static = {}
function Circle.new(radius)
local self = setmetatable({}, {__index = Static})
self.radius = radius
return self
end
function Static:calculateArea()
return math.pi * self.radius * self.radius
end
return Circle
local Shape = require(...)
-- Derived class: Rectangle (inherit from Shape)
local Rectangle = setmetatable({}, {__index = Shape})
local Static = {}
function Rectangle.new(length, width)
local self = setmetatable({}, {__index = Static})
self.length = length
self.width = width
return self
end
function Static:calculateArea()
return self.length * self.width
end
return Rectangle
when you try to do Shape.new directly, it will error as programmed
local Rectangle = require(...)
local Circle = require(...)
local Shape = require(...)
local rect = Rectangle.new(10, 10) -- 10 by 10 rectangle
local circle = Circle.new(10) -- 10 radius
-- polymorphic function
function printArea(shape)
print("Area:", shape:calculateArea())
end
printArea(rect)
printArea(circle)
local shape = Shape.new() -- errors "Shape is an abstract class and cannot be instantiated directly."
Encapsulation
in simple terms, is like putting something inside a box or container and controlling access to it. In programming, encapsulation refers to bundling data and related functionality (methods or functions) together as a single unit, called an object.
Encapsulation helps in:
-
Data protection: Encapsulated data can be made private or hidden from external code, preventing unauthorized access or modification.
-
Code organization: Encapsulation allows for better organization of code by grouping related data and methods together, making it easier to understand and maintain.
-
Code reusability: Encapsulated objects can be reused in different parts of the program or in other programs, promoting code reusability and modularity.
-
Flexibility: By providing a public interface to interact with the object’s internal data, encapsulation allows you to modify the internal implementation without affecting the code that uses the object.
imagine you have a toy car that requires battery power to drive; encapsulation allows you to hide the internal mechanism of the toy
local ToyCar = {}
function ToyCar.new(color)
local self = {} -- public variables go here
-- private variables
local color = color
local battery = 100 -- 100% battery
function self:getColor()
return color
end
function self:charge()
battery = 100 -- recharge
print("fully charged!")
end
function self:drive()
if battery > 0 then
battery -= 10 -- decrease battery by 10%
print("driving!")
else
print("ran out of battery!")
end
end
return self
end
return ToyCar
local ToyCar = require(...)
local car = ToyCar.new(Color3.new(1, 0, 0)) -- red car
print(car.color) -- nil
print(car:getColor()) -- (1, 0, 0)
car:drive() -- driving!
car:drive() -- driving!
car:drive() -- driving!
car:drive() -- driving!
car:drive() -- driving!
car:drive() -- driving!
car:drive() -- driving!
car:drive() -- driving!
car:drive() -- driving!
car:drive() -- driving!
car:drive() -- ran out of battery!
car:charge() -- fully charged!
car:drive() -- driving!
Composition
imagine you’re building a car; a car consists of various components like an engine, wheels, seats, and a steering wheel; each component has its own specific functionality.
Composition is like putting together other classes to create a new class
-- Engine class
local Engine = {}
local Static = {}
function Engine.new()
return setmetatable({}, {__index = Static})
end
function Static:Start()
print("Engine Started")
end
return Engine
-- Fuel Tank class
local FuelTank = {}
local Static = {}
function FuelTank.new(capacity)
return setmetatable({capacity = capacity}, {__index = Static})
end
function Static:Refill()
print("Fuel tank refilled")
end
return FuelTank
local Engine = require(...)
local FuelTank = require(...)
local Car = {}
local Static = {}
function Car.new(fuelCapacity: number)
return setmetatable({
engine = Engine.new()
fuel_tank = FuelTank.new(fuelCapacity)
}, {__index = Static})
end
function Static:StartCar()
self.engine:Start() -- Engine Started
print("Car started")
end
function Static:RefillFuel()
self.fuel_tank:Refill() -- Fuel tank refilled
end
return Car
the Car class is composed of an Engine object and a FuelTank object. which represent the independent components with their own functionalities.
More Examples
Inheritance, Abstraction, Polymorphism
-- Abstract class: Animal
local Animal = {}
local Static = {}
function Animal.new(name)
local self = setmetatable({}, {__index = Static})
self.name = name
return self
end
return function(staticTable)
Static = staticTable
return Animal
end
local Animal = require(...)
-- Derived class: Dog
local Dog = {}
local Static = {}
function Static:makeSound()
print("Woof!")
end
-- Inherit from Animal
Dog = Animal(Static)
return Dog
local Animal = require(...)
-- Derived class: Cat
local Cat = {}
local Static = {}
function Static:makeSound()
print("Meow!")
end
-- Inherit from Animal
Cat = Animal(Static)
return Cat
local Dog = require(...)
local Cat = require(...)
-- polymorphic function
function makeAnimalSound(animal)
animal:makeSound()
end
local dog = Dog.new()
local cat = Cat.new()
makeAnimalSound(dog) -- Woof!
makeAnimalSound(cat) -- Meow!
Encapsulation
local Car = {}
function Car.new()
local self = {} -- public variables & methods
-- private variables
local engineStatus = "Off"
-- public methods
function self:startEngine()
engineStatus = "On"
print("Engine started.")
end
function self:stopEngine()
engineStatus = "Off"
print("Engine stopped.")
end
local function checkEngineStatus() -- a private method
return engineStatus
end
return self
end
return Car
local Car = require(...)
local myCar = Car.new()
myCar:startEngine() -- Engine started.
myCar:stopEngine() -- Engine stopped.
Composition & another approach to OOP
local Book = {}
function Book.new(title, author)
local self = {}
self.title = title
self.author = author
return self
end
return Book
local Library = {}
function Library.new()
local self = {}
self.books = {}
function self:addBook(book)
table.insert(self.books, book)
end
function self:displayBooks()
for _, book in self.books do
print(`Title: {book.title}. Author: {book.author}`)
end
end
return self
end
return Library
local Book = require(...)
local Library = require(...)
local book1 = Book.new("In Search of Lost Time", "Marcel Proust")
local book2 = Book.new("To Kill a Mockingbird", "Harper Lee")
local library = Library.new()
library:addBook(book1)
library:addBook(book2)
library:displayBooks()
-- Output:
-- Title: In search of Lost Time. Author: F. Scott Fitzgerald
-- Title: To Kill a Mockingbird. Author: Harper Lee
Conclusion
It’s a powerful paradigm used in a lot of programming languages as well as game development. With it, you can design and implement programs in a structured, intuitive way. It’s easier to build and maintain complex programs when you understand OOP principles.
There are actually many ways to do it and use its key concepts. You can think of OOP as a set of tools that help you organize your code and make it more reusable. There are different approaches like class-based inheritance, prototypal inheritance, mixins, and interfaces. Each approach has its own strengths and trade-offs, and it really depends on the programming language, project requirements, and personal preferences.
OOP is all about finding the right balance and using the concepts that fit your needs. So go ahead and explore the different ways to do OOP, and have fun building your code in style!!
i will be continuing this topic further and theres still a lot to discuss
some resources
- wha??
- ok
- huh
- ok i think i got it
- hmm
0 voters
if you still have problems understanding what something does, don’t hesitate to contact me