Skip to content

Instantly share code, notes, and snippets.

@paulmoore
Created December 4, 2011 07:06
Show Gist options
  • Save paulmoore/1429475 to your computer and use it in GitHub Desktop.
Save paulmoore/1429475 to your computer and use it in GitHub Desktop.
A simple Class implementation in Lua for Object-Oriented Programming.
--- Animal.lua
--
-- A simple example of a base class usng the classes library.
--
-- @author PaulMoore
--
-- Copyright (C) 2011 by Strange Ideas Software
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
-- Import the classes library.
local classes = require "classes"
-- Create the class, with the default superclass.
local Animal = classes.class()
-- Two 'public' static class variables.
Animal.NOISE_1 = 0
Animal.NOISE_2 = 1
-- A 'private' static class variable.
local numAnimals = 0
--- Constructor.
--
-- @param name The name of this animal.
function Animal:init (name)
-- This instance of Animal now has its own name!
self.name = name
-- There is only one variable 'numAnimals' (it's static), so incrementing it each time we create an Animal gets as a unique id.
numAnimals = numAnimals + 1
self.id = numAnimals
end
--- Makes a noise, specific to whatever animal I am.
-- This is an example of an 'abstract' method.
-- We want each Animal to be able to make a noise, but we want each subclass of Animal to decide what that noise is.
--
-- @param noiseNum This is either 0 or 1, we will say that each Animal should be able to produce 2 noises.
function Animal:makeNoise (noiseNum)
error("I don't know what to do, I'm a generic Animal. This should be implemented by a subclass of Animal!")
end
--- This method is available to the Animal class, and all Subclasses of Animal as well.
--
-- @return Returns the id of this Animal.
function Animal:getAnimalId ()
return "Animal<"..self.id..">"
end
--- This is an example of a Class method. We can't access 'self', because it represents the whole Class, not an instance of this Class.
--
-- @return Returns the total number of Animals.
function Animal.totalAnimals ()
return numAnimals
end
-- This is important for module loading, the table returned by the 'require' method is the Animal class we created.
return Animal
--- Cat.lua
--
-- A simple example of Subclassing using the classes library.
--
-- @author PaulMoore
--
-- Copyright (C) 2011 by Strange Ideas Software
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
-- Import the classes library.
local classes = require "classes"
-- Make sure to import the Superclass!
local Animal = require "Animal"
-- Create the Subclass.
local Cat = classes.class(Animal)
--- Constructor. In addition to a name, it also takes in a cat breed.
--
-- @param breed The breed of the cat.
function Cat:init(name, breed)
-- Make sure to call the super constructor!
self.super:init(name)
-- Next, do some custom instantiation.
self.breed = breed
end
--- This is an instance method specific to the Cat class.
function Cat:meow ()
print("meow "..self.name.." meow")
end
--- This is another example of an instance method, also specific to the Cat class.
function Cat:purr ()
print("purr I'm a "..self.breed.." cat.")
end
--- This is an example of overriding an instance method.
-- When this method is called on a Cat instance, this method is used instead of the one declared in Animal.
-- If we didn't override this method, Animal:makeNoise would be called, and generate an error.
function Cat:makeNoise (noiseNum)
if noiseNum == Animal.NOISE_1 then
self:meow()
elseif noiseNum == Animal.NOISE_2 then
self:purr()
else
error("Unknown noise type: "..noiseNum.." for Cat with id: "..self.id)
end
end
-- Return the Cat class
return Cat
--- classes.lua
--
-- The classes library enables simple OOP constructs using prototypes and meta-tables.
--
-- @author Paul Moore
--
-- Copyright (C) 2011 by Strange Ideas Software
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
local classes = {}
-- Baseclass of all objects.
classes.Object = {}
classes.Object.class = classes.Object
--- Nullary constructor.
function classes.Object:init (...)
end
--- Base alloc method.
function classes.Object.alloc (mastertable)
return setmetatable({}, {__index = classes.Object, __newindex = mastertable})
end
--- Base new method.
function classes.Object.new (...)
return classes.Object.alloc({}):init(...)
end
--- Checks if this object is an instance of class.
-- @param class The class object to check.
-- @return Returns true if this object is an instance of class, false otherwise.
function classes.Object:instanceOf (class)
-- Recurse up the supertypes until class is found, or until the supertype is not part of the inheritance tree.
if self.class == class then
return true
end
if self.super then
return self.super:instanceOf(class)
end
return false
end
--- Creates a new class.
-- @param baseclass The Baseclass of this class, or nil.
-- @return A new class reference.
function classes.class (baseclass)
-- Create the class definition and metatable.
local classdef = {}
-- Find the super class, either Object or user-defined.
baseclass = baseclass or classes.Object
-- If this class definition does not know of a function, it will 'look up' to the Baseclass via the __index of the metatable.
setmetatable(classdef, {__index = baseclass})
-- All class instances have a reference to the class object.
classdef.class = classdef
--- Recursivly allocates the inheritance tree of the instance.
-- @param mastertable The 'root' of the inheritance tree.
-- @return Returns the instance with the allocated inheritance tree.
function classdef.alloc (mastertable)
-- All class instances have a reference to a superclass object.
local instance = {super = baseclass.alloc(mastertable)}
-- Any functions this instance does not know of will 'look up' to the superclass definition.
setmetatable(instance, {__index = classdef, __newindex = mastertable})
return instance
end
--- Constructs a new instance from this class definition.
-- @param arg Arguments to this class' constructor
-- @return Returns a new instance of this class.
function classdef.new (...)
-- Create the empty object.
local instance = {}
-- Start the process of creating the inheritance tree.
instance.super = baseclass.alloc(instance)
setmetatable(instance, {__index = classdef})
-- Finally, init the object, it is up to the programmer to choose to call the super init method.
instance:init(...)
return instance
end
-- Finally, return the class we created.
return classdef
end
return classes
--- main.lua
--
-- A simple program to test the functionality of the classes library.
--
-- @author Paul Moore
--
-- Copyright (C) 2011 by Strange Ideas Software
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.
-- Import the classes library.
local classes = require "classes"
-- Import the Animal class.
local Animal = require "Animal"
-- Import the Cat class.
local Cat = require "Cat"
-- Create an instance of the Animal class.
local spider = Animal.new("Charlotte")
-- Create some instances of the Cat class.
local cat1 = Cat.new("Mio", "Orange Tabby")
local cat2 = Cat.new("Quill", "Maine Coon")
-- As you can see, each instance has its own set of instance variables.
-- Also note how the Cat class 'inherits' the Animal:getAnimalId() method from its Superclass, Animal.
print("The spider's name is: "..spider.name.." and id: "..spider:getAnimalId())
print("The first cat's name is: "..cat1.name.." and id: "..cat1:getAnimalId())
print("The second cat's name is: "..cat2.name.." and id: "..cat2:getAnimalId())
print()
-- This will generate an error! Because spider is an instance of Animal and not Cat, Animal:makeNoise will be used
-- instead of Cat:makeNoise, thus producing an error.
--spider:makeNoise(Animal.NOISE_1)
--spider:makeNoise(Animal.NOISE_2)
--print()
-- This will not generate an error, because we have overriden the 'makeNoise' method for Cats.
cat1:makeNoise(Animal.NOISE_1)
cat1:makeNoise(Animal.NOISE_2)
print()
-- Because cat1 and cat2 are different instances of the Cat Class, we will get different results by calling the method on the other cat.
cat2:makeNoise(Animal.NOISE_1)
cat2:makeNoise(Animal.NOISE_2)
print()
-- This will generate an error, because by using '.' instead of ':', we are not giving the Cat instance reference to 'self'.
--cat1.makeNoise(Animal.NOISE_1)
-- This is a static method, thus it is called with a '.' and not a ':'.
print("The total number of Animals created is: "..Animal.totalAnimals())
print()
-- Returns 'yes', a Cat is a Cat.
print("Is cat1 an instance of the Cat class? "..tostring(cat1:instanceOf(Cat)))
-- Returns 'yes', a Cat is an Animal.
print("Is cat1 an instance of the Animal class? "..tostring(cat1:instanceOf(Animal)))
-- Returns 'true', an Animal is an Animal.
print("Is spider an instance of the Animal class? "..tostring(spider:instanceOf(Animal)))
-- Returns 'false', an Animal is not a Cat.
print("Is spider an instance of the Cat class? "..tostring(spider:instanceOf(Cat)))
-- Returns 'true', a Cat is an Object.
print("Is cat1 an instance of the root Object class? "..tostring(cat1:instanceOf(classes.Object)))
-- Returns 'true', a Spider is also an Object.
print("Is spider an instance of the root Object class? "..tostring(spider:instanceOf(classes.Object)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment