Object Oriented Programming

Making Classes with AM

The template

local MyClass = newClass"com.theincgi.MyClass"

--Constructor
local _new = MyClass.new   --get default instructor
function MyClass:new( ... )
  local obj = _new( self ) --constructs object, also calls super constructors as needed
  
	--initalize object
	obj.foo = "Hello World!"

  return obj
end

--instance function
function MyClass:foo()
  log( self.foo )  --uses self instead of obj
end

return MyClass

(for anyone who remembers advancedMacros.inherit, it still exists, but this requires far less setup and offers some additonal features (ps, it’s not indented like that in the page source… thanks markup))

In the constructor self refers to the class definition of MyClass instead of the instance

Best Practices

To avoid naming a class the same thing as someone else, include your minecraft username or reversed domain name before the class name.
Consider also including a project name if it’s related to something. Any of these are fine:

  • com.theincgi.Foo
  • theincgi.Foo
  • theincgi.PathFinder.Foo
  • com.theincgi.PathFinder.Foo

Keep class files in ~/macros/libs/username/ and load them using require instead of run
If relevant to a project you plan to share then instead add your project root to the require path and keep classes there.

While developing, it may be easier to load them with run, but keep in mind that type checking will not work correctly if
an instance was created with a different copy of a class.
This is because isA( class ) doesn’t check the class name (string), it checks the class object and it’s parents if any for matches.
Loading with require ensures that you should only ever have one class definition for some class.

If you have functions that do not rely on an instance of your class, put it in .static
ex: MyClass.static = {}
This makes it easier to know if you should use . or : on your function calls.

Have your directory in libs match the formating of your class name

You can set default values by setting them in your class, when an instance trys to write to it, it will write to it’s own table instead, I personally prefer to keep the setup limited to the constructor in most cases however.

Recommendations

Less important stuff, consistance can help avoid some issues

  • When overriding a function, leave a comment above it such as --override
    This makes it easier to tell which functions are from not from the parent class
  • don’t use spaces in your class name
  • classes start with an upper case letter, instances with a lowercase
  • . for package names when defining a new class
  • camelCase
  • constructor(s) towards the top of your class
  • local functions above constructor
  • comment usage of functions / constructors if it’s not obvious

Basic example

~macros/libs/com/theincgi/Counter.lua

local Counter = newClass"com.theincgi.Counter"

local _new = Counter.new
function Counter:new( ... )
  local obj = _new( self )
  
	obj.count = 0

  return obj
end

function Counter:inc()
  self.count = self.count + 1
end

return Counter

Somewhere else

local Counter = require"com/theincgi/Counter"
local counter = Counter:new()
counter:inc()
log( "Counter is &b"..counter.count ) --1

Inheritance

~/macros/libs/com/theincgi/AdvCounter

local Counter = require"com/theincgi/Counter"
local AdvCounter = newClass"com.theincgi.AdvCounter"

--you can skip the constructor if it's the same

--override
function Counter:inc()
  self.count = self.count + 5
end

return Counter

Somewhere else

local AdvCounter = require"com/theincgi/AdvCounter"
local advCounter = AdvCounter:new()
advCounter:inc()
log( "Counter is &b"..counter.count ) --5

Super calls

~/macros/libs/com/theincgi/AdvCounter

--...
--override
function Counter:inc()
  self:super().inc( self ) -- +1
  self.count = self.count + 5
end
--...

Somewhere else

local AdvCounter = require"com/theincgi/AdvCounter"
local advCounter = AdvCounter:new()
advCounter:inc()
log( "Counter is &b"..counter.count ) --6

Type Checking

Sometimes you want to make sure your argument isn’t just a table, but is a certain class, heres how

isClass( x )

log( isClass( advCounter ) ) --true
log( isClass( 45 )) --false
log( isClass( {} )) --false

isA( class )

log( advCounter:isA( AdvCounter ) ) --true
log( advCounter:isA( Counter ) )    --true
log( counter:isA( AdvCounter ) ) --false
log( counter:isA( Counter ) )    --true

typeMatches

lets you check for multiple types at the same time

local typeMatches = advancedMacros.utils.typeMatches

log( typeMatches( advCounter, {AdvCounter} ) ) --true
log( typeMatches( counter, {AdvCounter} ) ) --false
log( typeMatches( advCounter, {Counter} ) ) --true
log( typeMatches( 15, {AdvCounter,"string","boolean"} ) ) --false
log( typeMatches( advCounter, {"class:com/theincgi/AdvCounter"} ) ) --true
Notice: You can also specifify a class by providing a string starting with class: and ending with the path used by require

MetaEvents

Making meta events work on objects can get confusing, luckly you don’t have to worry about that.

Just call obj:__enableMetaEvents() in your constructor any functions that have the event name should be triggered with self included

Supported Events
__index
__newindex
__mode
__call
__metatable
__tostring
__len
__pairs
__ipairs
__gc
__name
__unm
__add
__sub
__mul
__div
__mod
__pow
__concat
__eq
__lt
__le
__gc is listed but may not be triggered

VS Code Snippets

Using VS Code? FilePreferencesConfigure User Snippetslua.json

Class setup

"Lua Class Template": {
	"prefix":["class"],
	"body": [
		"local $TM_FILENAME_BASE = newClass\"${1:PACKAGE_NAME}.$TM_FILENAME_BASE\"",
		"",
		"local _new = $TM_FILENAME_BASE.new",
		"function $TM_FILENAME_BASE:new( ... )",
		"  local obj = _new( self )",
		"  ",
		"  return obj",
		"end",
		"",
		"",
		"",
		"return $TM_FILENAME_BASE",
	]
}

Constructor only

"Lua constructor": {
	"prefix":["new","constructor"],
	"body": [
		"local _new = $TM_FILENAME_BASE.new",
		"function $TM_FILENAME_BASE:new( ... )",
		"  local obj = _new( self )",
		"  ",
		"  return obj",
		"end"
	 ]
}

Function

"Class Function": {
	"prefix":["cfunc"],
	"body": [
		"function $TM_FILENAME_BASE:$1($2)",
		"  $3",
		"end"
	]
}

Super Call

"Call Super": {
    "prefix": ["super"],
    "body": ["self:super().$1( self$2 )"]
}

Since

MCAM
1.12.27.11.0