Ok. Brace yourself, this is going to be long.
---
The thing is that OOP is an overused, "bag term" that people use with many different meanings. Let me explain the ones I see more often.
Pure OOP: Objects
On its simplest form, OOP means "Programming using objects". On its purest form, an
object is just "A group of attributes and some functions that act on those attributes". These "functions that act on attributes" are usually called
methods.
Given that definition, you could create an object in Lua very easily:
Code: Select all
local peter = {name = 'Peter', speak = function(self) print("Hi, I'm " .. self.name) end }
peter:speak() -- Hi, I'm Peter
On that example,
peter is an object with 1 attribute (
name) and 1 method (
speak).
Classes (as instance creators)
"Class" is another of those "bag concepts" that people use with several different meanings. The one they tend to use the most is "something that, given some properties, can create an object". Objects created by a class are usually called
instances of that class. The instances of a class usually "work similarly", since their methods do the same things (only their attributes are different). This is very useful in games, where you very often have similar things that behave the same way (enemy ships in a shooter game, the bullets, powerups ...)
With that definition of class, a simple Lua function could be a class:
Code: Select all
local Person = function(name)
return {name = name, speak = function(self) print("Hi, I'm " .. self.name) end }
end
local john = Person('John')
local mary = Person('Mary')
john:speak() -- Hi, I'm John
mary:speak() -- Hi, I'm Mary
So
john and
mary are now
instances of
Person. They both have an attribute called
name and a method called
speak.
Classes (as method repositories)
On the previous example, each instance of the class had its own copy of the
speak method. While this works in certain scenarios, it is usually not very efficient in terms of speed (Lua creates functions very fast, but it still takes some time) and memory (there can be many copies of the same function). In other languages they have the same issue: it would be very desirable to put the methods in 1 place, and share them among all the instances. This task usually falls upon the classes too: they act as a "repository of methods". When an instance needs to invoke a method, it "requests it to the class", instead of having its own copy.
In Lua, this behavior is very easily accomplished with metatables & metamethods:
Code: Select all
local Person = {}
Person.new = function(name) return setmetatable({name=name}, {__index=Person}) end
Person.speak = function(self) print("Hi, I'm " .. self.name) end
local jason = Person.new('Jason')
local angelo = Person.new('Angelo')
jason:speak() -- Hi, I'm Jason
angelo:speak() -- Hi, I'm Angelo
On this example,
Person is able to create instances by using a function called
new.
jason and
angelo are
instances of
Person, but they only have the
name attribute. The
speak method is on the
Person class, and there's only one copy of it. This saves memory and instantiation speed, in exchange of making every invokation of the
speak method a bit slower ("asking the class for the method" takes a bit of time, too).
Composition
One important thing that people tend to overlook is the fact that attributes are not limited to basic objects, like strings or numbers. An attribute can be an object, too. When an object X has another object Y as attribute, we say that X it is
composed of Y, or that X
has a Y.
Code: Select all
local Person = {}
Person.new = function(name, parent) return setmetatable({name=name, parent=parent}, {__index=Person}) end
Person.speak = function(self)
if self.parent then
print("Hi, I'm " .. self.name .. ", son of " .. self.parent.name)
else
print("Hi, I'm " .. self.name)
end
end
local mogh = Person.new('Mogh')
local worf = Person.new('Worf', mogh)
mogh:speak() -- Hi, I'm Mogh
worf:speak() -- Hi, I'm Worf, son of Mogh
On the previous example,
Person instances have
two attributes:
name &
parent (which can be
nil).
mogh has its parent set to
nil, while
worf has its parent set to
morgh. The
speak method, shared by both instances in the
Person class, prints a different message depending on whether the instance invoking it has a
parent attribute or not.
Notice that you can do composition with instances of different classes, too:
Code: Select all
local Gun = {}
Gun.new = function(name, sound) return setmetatable({name=name, sound=sound}, {__index=Gun}) end
Gun.fire = function(self, target) print('<The ' .. self.name .. ' fires upon ' .. target.name .. '. ' .. self.sound .. '!>') end
local Person = {}
Person.new = function(name, gun) return setmetatable({name=name, gun=gun}, {__index=Person}) end
Person.attack = function(self, target)
print('Take this, ' .. target.name .. '!')
self.gun:soot(target)
end
local revolver = Gun.new('Revolver', 'Bang')
local phaser = Gun.new('Phaser', 'Zap')
local flash = Person('Flash', revolver)
local ming = Person('Ming', phaser)
flash:attack(ming) -- Take this, Ming! <The Revolver fires upon Ming. Bang!>
ming:attack(flash) -- Take this, Flash! <The Phaser fires upon Flash. Zap!>
On this example there are two classes,
Gun and
Person.
Gun has two attributes,
name &
sound, and one method,
fire. It has two instances,
revolver &
phaser.
Person has two attributes,
name &
gun, and one method,
attack. It has two instances,
flash (who uses a revolver) &
ming (who uses a phaser).
Inheritance
Now this is something that trips people a lot. The relationship between a class and its instances is clear: we say "an instance IS A class": "peter IS A person".
There are some people that like to extend this to classes as well. They want to express things like, "A Mammal is an Animal", where both Mammal and Animal are classes with instances. And have Mammal "inherit" some properties from Animal, so that instances of Mammal "borrow" the methods of Animal in addition of those of Mammal. And they even want to extend this hierarchy with lots of levels: Bat is a Mammal is an Animal is a Vertebrate etc.
Defining relationships between classes with "is A" is difficult because a class can "be" many other things in the real world. A Mammal is a vertebrate, but it is also Solid. And is is also Visible. And needs food for living. The relationship between instances and their class is clear (the class creates the instances) but when you have 2 classes it's much more complex.
In my opinion, composition can usually express the same concepts as inheritance with more exactitude: a Mammal
has a SolidBody,
has a VisibleShape and
has DietaryNeeds. For this reason, I am not going to show you how to do inheritance in Lua, which is possible, but is not really needed 90% of the time.
Conclusion
People mean very different things when they say "OOP". Most often it's enough to have classes that act as instance creators and method repositories. That can be done very easily with metamethods. It is very useful in games, where very often you need things that behave the same way but have slightly different properties.
I hope this explanation helps you get started. Good luck!