Inheritance is ubiquitous in the world of object-oriented programming. It makes your classes easy-to-maintain and allows you to reuse a lot of your software. In Part 4, we cleaned up some class code and packaged it all into one nice complete class. In this part, we’ll talk about how we can form a relationship between two classes in which a subclass inherits the state and behaviors of a superclass.
Consider the following Person class, which is similar to the Car class from the previous articles:
local Person = {}
Person.__index = Person
function Person.new(gender, name)
local self = setmetatable({
gender = gender; -- M or F
name = name;
health = 100;
}, Person)
return self
end
function Person:speak(sentence)
print(self.name .. ": " .. sentence)
end
function Person:greet()
self:speak("Hello, my name is " .. self.name)
end
return Person
Pretty straightforward stuff going on here. Recall from Part 1 that objects have a state and behavior. As described above, the state of a Person is their name and health , and a Person’s behavior is described in the speak and greet methods.
Let’s consider the creation of a Doctor class. What do we know about the state and behavior of doctors?
- A doctor is a specific kind of person.
- Doctors heal people.
- They usually specialize in some field of medicine.
- We usually refer with a specific title, like “Doctor So-and-So”.
If we were to copy the Person class to create the doctor, we’d have to make a number of edits. You can use the above facts to identify the changes and additions in the state and behavior: We’ll need to represent the doctor’s field of medicine as a field/property. We need to describe the behavior of healing a person as a method (function). Finally, we need to change the greet method so the doctor uses their proper title. Below, I’ve copied the Person class and changed it to fit our needs of a Doctor:
local Doctor = {}
Doctor.__index = Doctor
function Doctor.new(gender, name, field)
local self = setmetatable({
gender = gender; -- M or F
name = name;
health = 100;
field = field; -- Field of medicine
}, Doctor)
return self
end
-- Note: The function below is the exact same as Person:speak
function Doctor:speak(sentence)
print(self.name .. ": " .. sentence)
end
-- Note: this function is pretty similar, except "Dr." was added
function Doctor:greet()
self:speak("Hello, I am Dr. " .. self.name)
end
-- The person parameter could be a Doctor (they both have health and speak)
function Doctor:heal(person)
person.health = person.health + 10
self:speak("Good thing you're not American or this would cost a fortune!")
person:speak("Gee, thanks Dr. " .. self.name .. "!")
end
return Doctor
You can see that if we were to use both Person and Doctor in some code we’d be repeating ourselves to a degree. We’re even repeating ourselves in concatenating “Dr.” with the Doctor’s name several times. It would be useful if we could dedicate the code for the Doctor class to only state and behaviors unique to Doctors (i.e., skip anything to do with a Doctor also being a Person and just say “a doctor is a person” somehow).
The last part is the core concept of inheritance: the Doctor class inherits the state and behaviors described in the Person class. Recall the __index metamethod which was described in Part 2: If the Doctor class used a metatable with __index set to the Person class, then using a Person method with a Doctor would be possible! Here’s how this can be done:
-- First, require the Person class. This would look for a Person.lua file.
-- In Roblox, you'd provide a ModuleScript instead (e.g script.Parent.Person)
local Person = require("Person")
local Doctor = setmetatable({}, {__index = Person})
Doctor.__index = Doctor
Now if we access some key in Doctor that doesn’t exist, Lua will look for the same key in Person. We’ll need to update the constructor for a Doctor: it will need to first construct a Person, then override the new Person’s metatable with the Doctor metatable. In addition, it’ll need to initialize any Doctor-related states. Here’s an updated constructor:
function Doctor.new(gender, name, field)
local self = setmetatable(Person.new(gender, name), Doctor)
self.field = field
return self
end
Notice how we’re merely passing on the gender and name parameters to the Person constructor, while also adding the field parameter of our own. Since we’ve already defined the speak method in the Person class, we need not redefine the exact same method in the Doctor class. Delete it. However, the greet method was updated to include the title “Dr” in the greeting. Keep that, since it overrides the Person’s greet method. Last but not least, the heal method doesn’t need changing since it works with both Person and Doctor already!
If you wanted to call an overridden method of Person on a Doctor, you’d need to refer to the function by name, such as Person.greet . This is the Call super pattern. Try avoiding this unless absolutely necessary:
-- The call super pattern
function Subclass:someFunction(arg1, arg2)
-- Since we can't do self:someFunction(arg1, arg2) without recursing infinitely,
-- call the superclass' someFunction by name and give it self:
local result = Superclass.someFunction(self, arg1, arg2)
-- Do something with result
return result
end
I’ll leave you with some terminology to help you talk about inheritance better:
- Remember: It’s spelled I-N-H-E-R-I-T-A-N-C-E (with an “A” – A quick way to remember it: Don’t be dense, it’s a dance!)
- The Doctor class is a subclass of the Person class.
- The Doctor class inherits from the Person class.
- The superclass of the Doctor class is the Person class.
- The Doctor class overrides the greet method.
- We say that a subclass “is a” superclass. For example, a “Doctor is a Person”.
And that’s it! Inheritance is extremely useful in adhering to the Don’t Repeat Yourself software development principle. It saves time and can really organize a codebase. If you’d like to check out the complete source code for the Person and Doctor classes, check out these pastebin links or this Roblox model file:
That’s all for this part! If you have questions don’t hesitate to reach out to me on Twitter. I’ll be happy to update this article or clarify things if you need help.