OOP and callbacks

I have a script, say Parent and an object Child.

Parent:

local Child = require(Child).new()

local Callback = function()
	print("Bar")
end

Child.Callback = Callback
Child:Foo()

Child:

local Child = {}
Child.__index = Child

function Child.new()
	local self = setmetatable({}, Child)

	// fields

	return self
end

function Child:Foo()
	self.Callback()
end

return Child

When Foo is called in Child, I need the parent to know about it, hence the callback (Is that the right terminology?). I don’t like this implementation because it puts a field that belongs to the Child – Callback – in the Parent script. And if the Parent has a bunch of other classes with their own callbacks, that’s a lot of clutter. What is the best way to do this?

XY Problem

Specifically, I have a LocalScript that controls a customization GUI. It creates a ColorWheel object and three Menu objects. When the ColorWheel color is changed, it fires the callback. Then, the callback assigns the color to whichever menu is active.

The ColorWheel doesn’t know which menu is the active menu because that’s a variable in the LocalScript, which is why it doesn’t simply change the menu’s color itself.

3 Likes

Instead of a callback, maybe try using events instead? You can have a signal (either custom Lua object or a BindableEvent) instead the ColourWheel. When something happens, call :Fire() on the signal. The local script can then either connect directly to the ColourWheel event and pass it on to the menu, or when changing the active menu, disconnect the old from the signal and connect the new one.
E.g.

local colourWheel = ...
local activeMenu
local connection

local function activateMenu(menu)
    if connection then
        connection:Disconnect()
    end
    activeMenu = menu
    if activeMenu then
        connection = colourWheel.signal:Connect(activeMenu.handler)
    end
end
2 Likes

I quite like that idea. I was considering using a bindable but opted not to for a purely code approach, which I would still prefer for the sake of improving my OOP game. I’ll give it a shot regardless.

A callback would imply that your child function calls another function or returns out a value to the parent. The parent therefore shouldn’t be putting a function in the child object, it should be passing the function as an argument.

With your current code style, you’re encountering an anti-pattern where you set an index in the child object to call it. The appropriate solution is to pass a callback as an argument so the child function can call it.

local Child = require(Child).new()

local function Callback()
    print("Parent func")
end

Child:Foo(Callback)

--

local Child = {}
Child.__index = Child

function Child.new()
    local object = setmetatable({
        -- fields
    }, Child)

    return object
end

function Child:Foo(Callback)
    print(self.Something)
    Callback()
end

return Child

That being said, the parent won’t know about the call unless you use a signal of some kind that is intended to inform the parent that the call happened. For example, you can use a return statement so that when the parent calls the function, the child returns a value to the parent so it can use that value. Otherwise, just use a bindable, it’s still a pure code approach.

Small comment, you’ll notice I changed how the class creates it’s object. Table creation is more performant when you initialise with the values already in over creating a table and then adding elements. You create a templated table with a predefined size versus forcing it to reallocate and expand.

2 Likes