Skip to content

Basic Usage

Devon Palma edited this page Nov 6, 2024 · 10 revisions

Creating a class

-- Base class creation
local Animal = LowerClass("Animal")

-- Adding functions
function Animal:__init(sound)
  self.sound = sound
end

function Animal:makeSound()
  print("Animal goes '" .. self.sound .. "'")
end

-- Creating an object
dog = Animal:new("Bark!")
dog:makeSound()

LowerClass.type()

Takes in a single argument and return the type of the passed argument.

local Animal = LowerClass("Animal")
local dog = Animal()

-- If a class is passed in, always returns "Class"
print(LowerClass.type(Animal)) -- "Class"

-- If an instance is passed in, returns the name of the class
print(LowerClass.type(dog)) -- "Animal"

-- If anything else is passed in, returns Lua's type()
print(LowerClass.type(5)) -- "number"

Inheritance

-- Single Inheritance
local Dog = LowerClass("Dog", Animal)

-- Multi Inheritance
local FurBaby = LowerClass("FurBaby")
local Dog = LowerClass("Dog", Animal, FurBaby)

-- "Super" does not exist in LowerClass, here is how you would imitate it though:
function Dog:__init()
  Animal.__init(self, "Bark!")
end

Class:is() / Instance:is()

Checks if an object/class inherits a class

local Dog = LowerClass("Dog", Animal)

print(Dog:is(Animal)) -- True
print(Animal:is(Dog)) -- False

local myDog = Dog()
print(myDog:is(Animal)) -- true

Note on "Static"

Unlike MiddleClass, LowerClass does not support the idea of "static" methods. Simply put when you define a function for a class, it is immediately accessible by both the class, inheriting classes, and any objects inheriting this class.

local Dog = LowerClass("Dog")

function Dog:walk()
  if type(self) == "Dog" then
    print("An instance of Dog is walking!")
  else
    print("The class 'Dog' has called walk')
  end
end

Dog:walk() -- The class 'Dog' has called walk
Dog.walk() -- The class 'Dog' has called walk

local myDog = Dog:new()
myDog.walk() -- The class 'Dog' has called walk
myDog:walk() -- An instance of Dog is walking!

Mixins

Mixins are simply tables that will have all their functions added to an object.

-- Simple mixin declaration
local WalkMixin = {
  walk = function(self)
    print("This object is walking")
  end
}

-- Adding the mixin
local Dog = LowerClass("Dog", WalkMixin)

-- Using the mixin
Dog:walk()
-- or
local myDog = Dog()
myDog:walk()

Functional Mixins

-- Mixins can be a function, the only arg passed to them will be the object mixing in. 
-- The return value does have to be a proper table mixin though.

local function WalkMixin(cls)
  if cls.name == "Dog" then
    return {
      walk = function()
        print("A dog is walking")
      end
    }
  else
    return {
      walk = function()
        print("Something is walking")
      end
    }
  end
end

local Dog = LowerClass("Dog", WalkMixin)

Class:include(...: class|mixin) and Instance:include(...: mixin)

Include is the foundation for Mixins and Inheritance

For example: when you call LowerClass(, ...), all args outside of name are simply passed to Include.

-- Class Include() can include both other classes and "mixins"
local FourLegged = LowerClass("FourLegged")
Dog:include(FourLegged)

local WalkMixin = {
  walk = function(self)
    print("I am walking!")
  end
}
Dog:include(WalkMixin)

-- Object Include() can only include "mixins"
local myDog = Dog()
myDog:include(WalkMixin)

Variable Propegation

The "most complex" part of LowerClass (and MiddleClass as well) is how variables are propegated.

To best understand how they are propegated, it is easiest to look at an example:

local ClassA = LowerClass("ClassA")
local ClassB = LowerClass("ClassB", ClassA)
local objA = ClassA()
local objB = ClassB()

-- prints the value of .val for ClassA, ClassB, objA, and objB
local function printer()
  print("CA=" .. tostring(ClassA.val) .. ", " 
    .. "CB=" .. tostring(ClassB.val) .. ", " 
    .. "OA=" .. tostring(objA.val) .. ", "  
    .. "OB=" .. tostring(objB.val))
end
-- Val hasn't been set anywhere, so all values will be nil
printer() -- CA=nil, CB=nil, OA=nil, OB=nil

-- When we set ClassA.val to 1, it will propegate to ClassB and all sub objects
ClassA.val = 1
printer() -- CA=1, CB=1, OA=1, OB=1

-- Resetting the val back to nil, will again propegate it all the way through
ClassA.val = nil
printer() -- CA=nil, CB=nil, OA=nil, OB=nil

-- Setting ClassB.val will only propegate it to classes and objects that inherit from ClassB
ClassB.val = 4
printer() -- CA=nil, CB=1, OA=nil, OB=1

-- Setting objB will only apply to objB as it's not a class.
objB.val = 10
printer() -- CA=nil, CB=1, OA=nil, OB=10

-- Setting ClassB back to nil, will ensure any objects inheriting val from it will also go back to nil
-- But objB.val is already set to 10, so it inherits it properly
ClassB.val = nil
printer() -- CA=nil, CB=nil, OA=nil, OB=10

-- Here we remove val from objB, meaing objB will revert to inheriting from ClassB
-- Then we set ClassA and ClassB to their own unique values, and we can see that their
-- objects inherit from them.
objB.val = nil
ClassA.val = 10
ClassB.val = 4
printer() -- CA=10, CB=4, OA=10, OB=4

-- Due to this behavior, we can actually do a really cool thing where we setup a class to have
-- val as a function, that will set val for us.
ClassA.val = function(self, val)
   self.val = val
end
ClassB.val = nil
objB:val(5)
printer() -- CA=<function>, CB=<function>, OA=<function>, OB=5
Clone this wiki locally