Wrapper - Manage object-oriented classes based off tags!

You can checkout the GitHub here and download the rbxm here


Hey! If you’re like me and love the architecture of object-oriented code, you may find lots of use in my package, Wrapper.

Do note, if you don’t understand OOP principles or haven’t messed around much with it, this may not be the module for you! Also, while Wrapper is in a stable state, I’m always looking for suggestions and improvements.


Wrapper Overview

Wrapper makes object-oriented programming patterns easy to connect into YOUR world. An easy to understand example would be with a KillPart class.

Here we have a simple KillPart class. There is a constructor with the part, a Trove object, and a GUID.

local KillPartClass = {}
KillPartClass.__index = KillPartClass

type KillPart = typeof(setmetatable({} :: {
	Part: BasePart,	
	Trove: any,
	GUID: string
}, KillPartClass))

function KillPartClass.new(Part: Instance, Trove: any, GUID: string?): KillPart
	local self = setmetatable({}, KillPartClass)
	self.Part = Part :: BasePart
	self.Trove = Trove
	self.GUID = GUID :: string

	return self
end

function KillPartClass.Init(self: KillPart) -- automatically runs!
	self:WatchTouchedEvent()
end

function KillPartClass.WatchTouchedEvent(self: KillPart)
	self.Trove:Add(self.Part.Touched:Connect(function(Hit: BasePart)
		local Humanoid = Hit.Parent and Hit.Parent:FindFirstChildWhichIsA("Humanoid")
		if not Humanoid then return end

		Humanoid.Health = 0
	end))
end

function KillPartClass.Destroy(self: KillPart)
	print("This object has been destroyed!") -- the trove and metatable are cleaned externally, no need to do it here.
end

Now, we can use Wrapper to manage this class! What Wrapper will do is watch for any instances that get added with the tag “KillPart”. Once that instance is added, it will create a new object and store it internally. Not only does it instantiate objects in the class, but it also provides the constructor with a Trove object that gets automatically cleaned up so you don’t have to do any cleaning yourself! (This also means Trove:Extend() becomes quite useful.) Here is an example of how we could make this work:

local KillPartClass = require(path.to.class.above)
local Wrapper = require(path.to.wrapper)

local KillPartWrapper = Wrapper.new(KillPartClass, "KillPart")

It’s as simple as that! Now we have objects managed by Wrapper! But… what if we want some customization? Fear no longer, Wrapper supports plenty of customization! Here are all of the properties you can change of a Wrapper object.


Methods

There are four types of a methods your class will have: Init, Destroy, Startup, and Constructor. The constructor is .new() by default, the init is :Init by default, the destroy is :Destroy by default, and startup is .OnStart by default. If you have different naming conventions than me, I would suggest you change the method names to your like!

KillPartWrapper = Wrapper.new(KillPartClass, "KillPart", {
	Methods = {
		Init = "Initialize"
	}
})

So, if your class had the Init method named “Initialize”, you would do this!

AutoInit

This is on by default. Setting it to false makes the Init method inside of the class not run automatically. You will have to call Init yourself.

KillPartWrapper = Wrapper.new(KillPartClass, "KillPart", {
	AutoInit = true
})	
Debug

Setting debug to enabled will add extra logs, warnings, etc.

KillPartWrapper = Wrapper.new(KillPartClass, "KillPart", {
	Debug = true
})
Context

When the Startup method is called in a class, it is given the context. You set that context here!

KillPartWrapper = Wrapper.new(KillPartClass, "KillPart", {
	Context = { Foo = 1 },
})	
Logging

Setting logging to enabled will log all instances that are processed

KillPartWrapper = Wrapper.new(KillPartClass, "KillPart", {
	Logging = true
})
Filter

This is predicate method to filter instances you don’t want instantiated.

KillPartWrapper = Wrapper.new(KillPartClass, "KillPart", {
	Filter = function(Inst: Instance)
		if Inst.Name ~= "Jaeymo" then
			return false
		end

		return true
	end,
})
Resolver

This method is used for dynamic requires. If you’re into inheritance with child classes, you may find use in this. Lets say we have two modules under a folder, KillPartA and KillPartB.

local KillPartModules = game:GetService("ReplicatedStorage").KillPartModules
local KillPartA = require(KillPartModules.A)
local KillPartB = require(KillPartModules.B)

-- we do not pass a class here, it can just be an empty table.
KillPartWrapper = Wrapper.new({}, "KillPart", {
	Resolver = function(Inst: Instance)
        if Inst.Name == "A" then
			return KillPartA
		elseif Inst.Name == "B" then
			return KillPartB
		else
			warn(`Something went wrong!`)
			return nil
		end
    end
})

Thank you everyone for reading! Please leave any feedback if you have any, I’d greatly appreciate it! You can find all of the API on the GitHub. Enjoy Wrapper.

6 Likes

I downloaded this module via your Wally package, and it’s amazing.
I really like how it has support for unique identifiers too, it really allows for a lot more features.

2 Likes