I have seen a lot of different OOP solutions for WoW addon development in the past. Still, I have been using my own for many years, and it has saved me much time debugging tedious issues while also providing an exceptionally smooth development experience. It's packed full of useful features (all of which I have ended up needing) while also providing low memory usage due to how it recycles unused resources or removes them if they expire. I have also included many unit tests, one of which checks for memory leaks.
A primary goal for the design of this framework is to preserve the Lua programming style. Therefore, MayronObjects should be simple to understand and pick up for Lua developers, but understanding OOP fundamentals is beneficial.
Someone once said on a similar thread post that an OOP framework for WoW addon development is over the top and unnecessary. Still, several features in this framework make me disagree with this, as outlined below:
The Benefits of using MayronObjects
- MayronObjects lets you define strict typing rules easily without repeating yourself, instead of remembering to throw your errors with many assert checks at the beginning of each function. You can define a rule to declare that an object's method must take a specific type of argument, which also works for return values. This type can be a simple type (e.g., strings, numbers, tables, functions) or a complex type (blizzard widgets and classes or interfaces created from the framework). If MayronObjects detects rule violations, it will throw a typical "bad argument" or "bad return value" detailed error message. This feature has saved me many times during development.
- MayronObjects assigns each instance a unique private instance data table, and classes can have private methods. Private data allows for strong encapsulation to ensure that other devs are using your packages correctly. Additionally, other external code cannot corrupt the instance's private data by mistake.
- The framework provides a way of exporting and importing packages containing classes to be reused across your projects or by other addon authors. I have created some simple collection classes for reusability purposes and have published one package (Pkg-GridPanels). One day, I would love to see another addon author publish some utility package into the ecosystem.
- MayronObjects allows you to define well-structured packages using interfaces for improved collaboration with other devs if needed. This feature has worked exceptionally well for my UI's plugin/module system. Also, as a bonus, Generic classes allow you to reuse the same classes and class logic with strict typing rules that can be modified depending on the types of data you are using.
- You can attach attributes to methods, and MayronObjects will call these to perform pre-execution logic before calling the targetted method. Attributes can even prevent methods from being called and can modify arguments before being sent to the method. I use InCombatAttribute to prevent methods from being called if the player is in combat, but there are several use-cases for attributes where I think they would shine.
- Having a parent base class to control the modular system I use in my UI (i.e., MayronUI) has helped me control the life-cycles of modules from a top-level perspective, which has streamlined the whole process.
These are just some of the useful things off the top of my head, but I'm sure I've missed some. If you are interested in reading more, the full documentation is available at:
New Features and Improvements
In my previous blog post, I outlined all the latest exciting changes to MayronUI. I also mentioned that MayronObjects had undergone some significant changes but avoided going into detail about them. You will find these details described below:
- LibStub has been replaced with a new built-in control system - You now call the
GetFrameworkmethod from the global
LibStub is excellent for situations where your library is stateless. For example, if two versions of the same library have the same function and can output the same results each time, it does not matter what version the addon developer imported from LibStub. However, MayronObjects is a framework that must manage the state of all registered classes and instances. When used with LibStub, this causing uncommon edge cases.
For example, suppose one of your addons depends on another, and the first loaded addon creates a new package or class. In that case, the other dependent addon will fail to retrieve it because it is registered with another framework version. The built-in version control resolves this in two steps. Firstly, it provides you with the last framework version registered instead of the latest version found. Therefore,
GetFramework will always return the embedded version of MayronObjects loaded by a given addon. This difference from LibStub means that it avoids unexpected confusion as MayronObjects registers entities with the expected framework version. Secondly,
GetFramework accepts an optional argument specifying the exact framework version you want to import. In the previously suggested scenario, the second addon could ensure that it receives the same version of MayronObjects used by the first addon. The optional argument provided to
GetFramework also accepts a
"latest" string value. This value has the same effect as LibStub by returning the framework with the highest framework version, although I do not recommend using this approach.
- MayronObjects now supports strict-typing rules on private and static methods.
In previous versions, strict-typing rules only worked on the public instance (non-static) methods (i.e., calling class methods from an instance object). However, you can now attach strict-typing rules, using the
DefineReturns package methods, onto methods assigned to a class's
You still need to call static methods in the same way as perform. Private methods, on the other hand, require a different approach. You cannot call them from an instance object because otherwise, they will not be public; developers who have access to the instance object can access the private methods. To resolve this problem, you need to call them from the private instance
data table. This table now includes a new
Call method, and you call it with the name of the private method, followed by any arguments to pass to it. Private instance methods also receive the
data table, as demonstrated below:
MyPackage:DefineParams("string", "string"); MyPackage:DefineReturns("boolean"); function MyClass.Private:MyPrivateMethod(data, arg1, arg2); print(arg1 .. ", " .. arg2); return true; end function MyClass:PrintMessage(data); local success = data:Call("MyPrivateMethod", "Hello", "World"); end