Quantcast
Download
(19 Kb)
Download
Updated: 08-27-18 03:19 AM
Compatibility:
Battle for Azeroth (8.0.1)
Shadows of Argus (7.3.0)
Tomb of Sargeras (7.2.0)
Return to Karazhan (7.1.5)
Legion (7.0.3)
Updated:08-27-18 03:19 AM
Created:08-23-17 03:23 PM
Downloads:567
Favorites:1
MD5:
8.0.1

LibMayronObjects

Version: 2.5
by: Mayron [More]

1: About LibMayronObjects

  • LibMayronObjects is a framework intended on making object-oriented programming easier for Lua developers.
  • You can create classes and call them to instantiate new instances/objects modeled from those classes.
  • You can create interfaces that enforce functions to be implemented by classes.
  • You can enforce strict typing rules to class and interface function parameters and return values.
  • Each class can inherit from at most one parent class. All classes either directly, or indirectly, inherit from the Object class.
  • There are many useful Object functions that all classes inherit and can use.
  • You can create and export packages, and import packages or separate classes and interfaces.
  • The framework also comes with some standard collection classes (List, Stack, Map, LinkedList).

1.1: NOTE:

There is a Test.lua file to see other working examples of how to use the Library!

2: Creating a Class

Lua Code:
  1. local LibMayronObjects = LibStub:GetLibrary("LibMayronObjects");
  2.  
  3. local MyPackage = LibMayronObjects:CreatePackage("MyPackage");
  4.  
  5. local MyClass = MyPackage:CreateClass("MyClass");
  6.  
  7. function MyClass:Print(data, message)
  8.     print(message);
  9. end
  10.  
  11. function MyClass.Static:Foo()
  12.     print("This is a static (non-instance) function!");
  13. end
  14.  
  15. local Instance = MyClass();
  16. Instance:Print("Hello, World!");

Line 3 creates a new package. This package is only available to the developer until they choose to export it. All entities (classes and interfaces) are created using
the package object and can be found inside the package. This will be explained in later.

Line 5 creates a new class with the class name "MyClass". No parent was assigned to it (2nd argument), so it directly inherits from the Object class.
It does not implement an interface (3rd argument).

Line 7 declares an instance function for this class. It cannot be called directly from the class. It can only be called from an instance of the class.
The first argument (data) is automatically given to the function by LibMayronObjects when called. This is the instance's private data that can only be accessed
when inside the function body (unless giving explicit access by the developer, usually through getters and setters).

Line 11 shows how to create a static class function. These functions can and should be called directly from the Class table rather than from a class instance.

Line 15 demonstrates how to create an instance from a class; you simply call the class like you would a function.

You may have noticed that line 14 calls the Print method with one string argument. This is passed as the 2nd argument to MyClass.Print declaration
because the first argument is always the instance's private data.

3: Getters and Setters

Lua Code:
  1. local LibMayronObjects = LibStub:GetLibrary("LibMayronObjects");
  2. local MyPackage = LibMayronObjects:CreatePackage("MyPackage");
  3.  
  4. local MyClass = MyPackage:CreateClass("MyClass");
  5.  
  6. function MyClass:GetTimeRemaining(data)
  7.     return data.timeRemaining;
  8. end
  9.  
  10. function MyClass:SetTimeRemaining(data, timeRemaining)
  11.     data.timeRemaining = timeRemaining;
  12. end
  13.  
  14. local Instance = MyClass();
  15. Instance:SetTimeRemaining(60);
  16. local timeRemaining = Instance:GetTimeRemaining();

Instance private data is passed in as the first argument to any instance (non-static) function call. This data is only accessible from within the function body
unless it is made explicitly accessible by the developer who created the class. This can be achieved using getter and setter functions as shown in the example
above. An external value is passed to the setter function (SetTimeRemaining) on line 15 and can be retrieved using a getter function as shown on line 16.
This technique can help to prevent outside interference against important class logic.

4: Function Definitions

This feature can help enforce strict rules upon your defined classes to ensure that parameter arguments and return values of class functions are the expected variable types. Note that including strict typing is completely optional.

Using the same example from the previous section, we can improve on this by adding some strict typing rules/definitions to the SetTimeRemaining
instance function:

Lua Code:
  1. local LibMayronObjects = LibStub:GetLibrary("LibMayronObjects");
  2. local MyPackage = LibMayronObjects:CreatePackage("MyPackage");
  3.  
  4. local MyClass = MyPackage:CreateClass("MyClass");
  5.  
  6. MyPackage:DefineReturns("number");
  7. function MyClass:GetTimeRemaining(data)
  8.     return data.timeRemaining;
  9. end
  10.  
  11. MyPackage:DefineParams("number");
  12. function MyClass:SetTimeRemaining(data, timeRemaining)
  13.     data.timeRemaining = timeRemaining;
  14. end
  15.  
  16. local Instance = MyClass();
  17. Instance:SetTimeRemaining(60);
  18. local timeRemaining = Instance:GetTimeRemaining();

A package contains information relating to classes and function definitions, therefore declaring parameter and return value types must be done using the package object.

Lines 10 and 11 must be included straight before declaring the function, otherwise, the instructions for strict typing will not be set to the correct function definition. However, as mentioned previously, you can also choose to not include the definition calls at all as enabling strict typing of parameter and return values is optional. The first argument (the private instance data) can be ignored when creating strict typing definitions. Line 10 states that the first argument passed to the SetTimeRemaining function must be of type "number", which in this case it is. Any developer using the class must comply with this enforced rule.
Line 11states that the return value must be of type "number". The developer who created the class must comply with his own promise by returning a string value.

You can also declare that any value should be returned, as long as it is not nil:

Lua Code:
  1. MyPackage:DefineParams("any");
  2. function MyClass:SetTimeRemaining(data, timeRemaining)
  3.     data.timeRemaining = timeRemaining;
  4. end

In this case, the return value and first argument can be a table, number, string, boolean or function, but not nil.

4.1: Optional Function Parameters and Return Types

Additionally, you can declare optional parameter arguments and return values by adding a question mark("?") character in front of the definition type.
However, they must be declared at the end of the declaration list after any non-optional definitions:

Lua Code:
  1. MyPackage:DefineParams("string", "number", "?table");
  2. MyPackage:DefineReturns("any", "function", "?boolean");

4.2: Object and Widget Names as Function Parameters and Return Types

Because all objects inherit the "GetObjectType()" function from the Object class, you can also validate parameter and return values to check whether or not
they are of a certain class type:

Lua Code:
  1. MyPackage:DefineParams("Frame", "Button", "?MySlider");
  2. MyPackage:DefineReturns("IHandler", "MyClass", "?SomeWidget");


5: Exporting a Package

Entities (such as classes and interfaces) do not need to be exported. These are created from a package and are automatically added to the package.
You should only be exporting packages. By default, packages are not made available outside of the file they are created in. To export a package, you need to
supply a namespace. A namespace is similar to a key. It is used to locate a package during the import process.

You can export in 2 ways. Using the libraries Export function directly, or when creating the package by supplying the namespace as the 2nd argument instead:

Lua Code:
  1. local LibMayronObjects = LibStub:GetLibrary("LibMayronObjects");
  2. local MyPackage = LibMayronObjects:CreatePackage("MyPackage");
  3. LibMayronObjects:Export(MyPackage, "RootPackage.AnotherPackage");

Lua Code:
  1. local LibMayronObjects = LibStub:GetLibrary("LibMayronObjects");
  2. local MyPackage = LibMayronObjects:CreatePackage("MyPackage", "RootPackage.AnotherPackage");

In this example, both "RootPackage" and "AnotherPackage" are other packages, where "AnotherPackage" is a sub-package of "RootPackage".
Both of these packages do not have to be explicitly created using the CreatePackage function; instead, these are both created during the exporting process.
"MyPackage" is now a sub-package of "AnotherPackage".

If a developer wanted to create a class that inherits "MyParent" from another file where no reference to "MyParent" exists, they would either need to supply the full namespace:

Lua Code:
  1. local MyChild = MyPackage:CreateClass("MyChild", "MyPackage.MyClasses.MyParent");

Or, import it first using the full namespace and then including the parent class directly to the 2nd argument:

Lua Code:
  1. local MyParent = LibMayronObjects:Import("MyPackage.MyClasses.MyParent");
  2. local MyChild = MyPackage:CreateClass("MyChild", MyParent);

6: Importing Packages or Entities

Unlike the Export function, the Import function can import not just another package with a valid namespace (a package that has previously been exported will have been
assigned a namespace), but it can also import a single entity (a class or interface).

Lua Code:
  1. -- File 1:
  2. local LibMayronObjects = LibStub:GetLibrary("LibMayronObjects");
  3. local MyPackage = LibMayronObjects:CreatePackage("MyPackage");
  4. LibMayronObjects:Export(MyPackage, "MyAddOn.Widgets");
  5.  
  6. local CheckButton = MyPackage:CreateClass("CheckButton");
  7. local Button = MyPackage:CreateClass("Button");
  8. local Slider = MyPackage:CreateClass("Slider");
  9. local TextArea = MyPackage:CreateClass("TextArea");
  10. local FontString = MyPackage:CreateClass("FontString");
  11. local Animator = MyPackage:CreateClass("Animator");
  12.  
  13. -- File 2:
  14. local CheckButton = LibMayronObjects:Import("MyAddOn.Widgets.MyPackage.CheckButton");
  15.  
  16. local WidgetsPackage =  LibMayronObjects:Import("MyAddOn.Widgets.MyPackage");
  17. local Button = WidgetsPackage:Get("Button");
  18. local Slider = WidgetsPackage:Get("Slider");
  19. local TextArea = WidgetsPackage:Get("TextArea");
  20. local FontString = WidgetsPackage:Get("FontString");
  21. local Animator = WidgetsPackage:Get("Animator");

Line 14 shows how you can import just one entity.
Lines 16 - 21 show how you can import an entire package and then use the Package's Get function to retrieve individual entities.
You can also create new entities to be added to the package that you have imported.

7: Creating an Interface

Interfaces can be a great way of specifying rules/patterns for a system. Interfaces cannot inherit or implement other entities.
"MyPackage:CreateInterface()", therefore, only takes one argument: the interfaces name. Interfaces, like classes, can be both exported and imported.

Lua Code:
  1. local LibMayronObjects = LibStub:GetLibrary("LibMayronObjects");
  2. local MyPackage = LibMayronObjects:CreatePackage("MyPackage");
  3.  
  4. local IEventHandler = MyPackage:CreateInterface("IEventHandler");
  5.  
  6. MyPackage:DefineParams("string", "function");
  7. MyPackage:DefineReturns("boolean");
  8. function IEventHandler:Register(eventName, func) end
  9.  
  10. function IEventHandler:Execute() end
  11.  
  12. local Frame = MyPackage:CreateClass("Frame", nil, IEventHandler);
  13.  
  14. function Frame:Register(data, eventName, func)
  15.     -- do stuff
  16.     return true;
  17. end
  18.  
  19. function Frame:Execute(data)
  20.     -- do stuff
  21. end
  22.  
  23. local myFrame = Frame();
  24.  
  25. myFrame:Register("PLAYER_ENTERING_WORLD", function()
  26.     print("Hello, World!")
  27. end);

Lines 8 and 10 show 2 interface functions being created. Interface functions do not, and should not, have any function body code used for implementation.
Instead, classes that are declared as "implementing the interface" must supply the function's implementation.

You do not need to, and should not, redeclare the return and param definitions for functions that have already been defined by the interface. You simply use the same function signature and include the implementation at the class level.

All functions must be implemented for each interface attached to the class. You cannot implement from 2 or more interfaces that share the same function name as this would cause a clash.

You can implement many interfaces, but only inherit from one parent class:

Lua Code:
  1. local MyClass = MyPackage:CreateClass("MyClass", MyParent, IInterface1, IInterface2, IInterface3);


7.2: Defining Properties for Implementing
You can specify strongly-typed properties that must be implemented inside a constructor

Lua Code:
  1. local MyInterface= TestPackage:CreateInterface("MyInterface");
  2. MyInterface:DefineProperty("MyBoolean", "boolean");
  3.  
  4. local MyClass= TestPackage:CreateClass("MyClass", nil, MyInterface);
  5.  
  6. function MyClass:__Construct(data)
  7.     -- must define boolean property:
  8.     self.MyBoolean = true;
  9. end
  10.  
  11. local myInstance = MyClass();

Properties are publically available (not privately stored inside the data table) and must be attached to the instance using the "self" keyword.
These properties can also be assigned the "any" type so that they are required but the type of value is not important.
You can also specify the property type as optional:

Lua Code:
  1. MyInterface:DefineProperty("MyBoolean", "?boolean");

This is useful for when a property can either be nil, or the expected value type.

8: Constructors and Destructors

A class can be assigned a constructor and a destructor. A constructor is a function that is called when an instance is first created from that class. A destructor is called either by the garbage collector if the reference for an instance ceases to exist, or by explicitly calling "Destroy()" on the instance.

Lua Code:
  1. local LibMayronObjects = LibStub:GetLibrary("LibMayronObjects");
  2. local MyPackage = LibMayronObjects:CreatePackage("MyPackage");
  3.  
  4. local Parent = MyPackage:CreateClass("Parent");
  5.  
  6. function Parent:Talk(data)
  7.     print(data.Dialog);
  8. end
  9.  
  10. function Parent:__Construct(data)
  11.     data.Dialog = "I am a parent.";
  12. end
  13.  
  14. local Child = MyPackage:CreateClass("Child", Parent);
  15.  
  16. function Child:__Construct(data)
  17.     data.Dialog = "I am a child!";
  18. end
  19.  
  20. function Child:__Destruct(data)
  21.     data.Dialog = nil;
  22.     print("Child object Destroyed!");
  23. end
  24.  
  25. local child = Child();
  26.  
  27. child:Talk(); -- prints "I am a child!"
  28.  
  29. child:Destroy(); -- prints "Child object Destroyed!"

The private instance data from the child object is passed to the parent function. This is why line 27 prints "I am a child!" and not "I am a parent.".
__Construct and __Destruct do not need to be manually called. They are implicitly called by LibMayronObjects when required.

9: Calling Parent Functions from a Child Instance

An important Object function is "Parent()". Using parent on an instance object, whose class has a parent class, will cause the parent function to be executed instead.
This means that if a child and a parent class both have implementations of the same function (where both functions have the same key/name), the parent function
will be called and the private data sent to the function will be the original, child object.

Lua Code:
  1. local LibMayronObjects = LibStub:GetLibrary("LibMayronObjects");
  2. local MyPackage = LibMayronObjects:CreatePackage("MyPackage");
  3.  
  4. local SuperParent = MyPackage:CreateClass("SuperParent");
  5. local Parent = MyPackage:CreateClass("Parent", SuperParent);
  6. local Child = MyPackage:CreateClass("Child", Parent);
  7. local SuperChild = MyPackage:CreateClass("SuperChild", Child);
  8.  
  9. function SuperParent:Print(data)
  10.     print(data.origin == "SuperChild");
  11.     return "This is SuperParent!";
  12. end
  13.  
  14. function Parent:Print(data)
  15.     return "This is Parent!";
  16. end
  17.  
  18. function Child:Print(data)
  19.     return "This is Child!";
  20. end
  21.  
  22. function SuperChild:Print(data)
  23.     return "This is SuperChild!";
  24. end
  25.  
  26. function SuperChild:__Construct(data)
  27.     data.origin = "SuperChild";
  28. end
  29.  
  30. local instance = SuperChild();
  31.  
  32. assert(instance:Print() == "This is SuperChild!");
  33. assert(instance:Parent():Print() == "This is Child!");
  34. assert(instance:Parent():Parent():Print() == "This is Parent!");
  35. assert(instance:Parent():Parent():Parent():Print() == "This is SuperParent!");

Line 10 will print "SuperChild", because line 35 passes the private instance data belonging to the SuperChild instance object, through the chain of "Parent()" calls,
to the SuperParent class.

All of the assert functions in this example will not trigger an error. This shows that each function implementation is being called correctly.

11: Generic Types

Generic types can be used to specify what type of values an instance of a Generic class can use.
Use the "Of" class method when instantiating an instance to specify what types to use:

Lua Code:
  1. local KeyValuePair = TestPackage:CreateClass("KeyValuePair<K, V>");
  2.  
  3. TestPackage:DefineParams("K", "V");
  4. function KeyValuePair:Add(data, key, value)
  5.     -- do stuff...
  6. end
  7.  
  8. local myInstance = KeyValuePair:Of("string", "number")();
  9. myInstance:Add("testKey", 123);

11: Object Functions

As previously mentioned and demonstrated, classes are either assigned a parent class manually or are implicitly given the Object class as their default parent class. This means that all classes inherit from Object directly or indirectly (Object is the parent of the parent's, parent's class, etc...). Therefore, all Object functions can be used by all classes.

Below is a list of all Object functions (without including the private instance data as the first argument):

Object:GetObjectType()
Returns the class name (string).
Object:IsObjectType(objectName)
@param objectName (string): The class or interface name.

Returns a boolean value indicating if the instance type is directly, or indirectly, the same type as the objectName supplied.
If the class of the instance inherits from a parent (or a parent's parent, etc...), or implements an interface whose name is
equal to the objectName argument, then the instance is said to be of that type and true will be returned.
Object:Parent()

Returns the parent class ready to be used with a parent function. Usually, you cannot call a non-static function on a class object, however the original
private instance data from the instance object that called the Parent() function (or chain of Parent() functions) is passed along to the final function call.
Object:Equals(other)
@param other (table): Another LibMayronObjects class (or value).

Returns a boolean value indicating if two instances are equal. Two classes can be equal if all private data key and value pairs match
regardless of whether the instance table references are different.
Object:GetParentClass()
Returns the Parent Class of the instance.
Object:GetClass()
Returns the Class of the instance.
Object:Clone()
Returns a new instance whose private data key and value pairs match the original, cloned instance.

Example:
Lua Code:
  1. local Instance2 = Instance:Clone();
  2. print(Instance:Equals(Instance2)); -- prints true
Object: Destroy()
Removes all private instance data keys and calls the __Destruct function.
12: Other Package Functions not Previously Demonstrated

Package:ProtectEntity(class)
If you create a class or interface, you can mark it as protected. Protected entities cannot have functions re-assigned or modified.
Package:AddSubPackage(package)
You can add a previously created package to as a sub-package to another package without needing to export either of the packages.
Package:ForEach(func)
@param func (function): Apply a function to all entities found inside the package

Can be used to print all entity names using GetObjectType().
Package:Size()
Returns the size of the package, i.e. the total number of items found inside the package.
Package:GetName()
Returns the name of the package.
13: Other LibMayronObjects Functions not Previously Demonstrated

LibMayronObjects:SetSilentErrors(silent)
@param silent (boolean): LibMayronObjects errors will be added to the error log if set to true, rather than being alerted in-game.
LibMayronObjects:GetErrorLog()
Returns the error log (table), consisting of all accomulated errors triggered while LibMayronObjects was set to silent mode (LibMayronObjects:SetSilentErrors()).
LibMayronObjects:FlushErrorLog()
Empties the error log.
LibMayronObjects:GetNumErrors()
Returns the number of errors collected.

2.5:
-- Reduced more memory usage
-- Removed the "Implements" function: Implementing interface functions work the same way as implementing interface properties - If not implemented, after constructing an object from the class that implements the interface, an error will be thrown.

2.4.1:
-- Restructured code
-- Made defined properties strongly typed after constructor executes

2.4:
-- Added support for Defining Properties in Interfaces (must be implemented in constructors)
-- Added support for generic types
-- Updated toc for BFA expansion
-- Added more classes and added test lua file to see examples of how the library works

2.3:
-- Merged everything into 1 Lua file so that it is far easier to use

2.1:
-- Export function parameters changed
-- Export can only export 1 package at a time

2.0:
-- Added Package Class
-- Can only export packages!
-- Removed Import modifiers (*, +, -)
-- Added Object:Parent()
Optional Files (0)


Archived Files (5)
File Name
Version
Size
Author
Date
2.4.1
19kB
Mayron
08-26-18 02:14 AM
2.4
18kB
Mayron
08-25-18 07:39 AM
2.3
8kB
Mayron
12-22-17 02:14 PM
2.2
15kB
Mayron
08-27-17 10:04 AM
2.1
14kB
Mayron
08-27-17 09:17 AM


Post A Reply Comment Options
Unread 08-27-18, 03:20 AM  
Mayron
A Frostmaul Preserver
 
Mayron's Avatar
AddOn Author - Click to view AddOns

Forum posts: 251
File comments: 1233
Uploads: 9
2.5:
-- Reduced more memory usage
-- Removed the "Implements" function: Implementing interface functions work the same way as implementing interface properties - If not implemented, after constructing an object from the class that implements the interface, an error will be thrown.
__________________
Visit the MayronUI Discord server to contribute towards our community and say hello!
Report comment to moderator  
Reply With Quote
Unread 08-25-18, 07:40 AM  
Mayron
A Frostmaul Preserver
 
Mayron's Avatar
AddOn Author - Click to view AddOns

Forum posts: 251
File comments: 1233
Uploads: 9
Updated to version 2.4:

2.4:
-- Added support for Defining Properties in Interfaces (must be implemented in constructors)
-- Added support for generic types
-- Updated toc for BFA expansion
-- Added more classes and added test lua file to see examples of how the library works
__________________
Visit the MayronUI Discord server to contribute towards our community and say hello!
Report comment to moderator  
Reply With Quote
Post A Reply



Category Jump: