I’m working on a Roblox game using OOP with classes like Plot, Animal, and ConveyorAnimal. Some classes are very dependent on others — for example, if I change Animal, it can break Plot and ConveyorAnimal.
I’m trying to reduce this tight coupling so changes in one class don’t cascade and break other parts of the system.
What are some best practices for decoupling classes in Roblox Studio Lua? Am I doing something wrong here?
have a top-level class connect the smaller classes together, instead of intertwining them together from the inside, use signals (there’s also many OS modules for it) from inside the smaller classes to help them communicate with each other from a higher level module
for example in a top-level handler module:
local Plot = plot.new()
Plot.Signals.AnimalAdded:Connect(function(animal)
-- animal1 would fire this signal in this case
Plot:Update()
end)
local animal1 = animal.new():SetPlot(Plot)
animal1.Signals.Destroyed:Connect(function()
Plot:Update()
end)
this answers the decoupling problem, as for the code breaking whenever you change an object, this can also contribute to making that happen less, you can utilize types to help you detect modules you have to update too!
Hey! What your running into is super common in Roblox Lua… tight coupling. If changing Animal breaks Plot or ConveyorAnimal it usually means your classes know too much about each other. Your not doing anything wrong just need some decoupling!
A few approaches that help:
Composition over inheritance
Instead of having ConveyorAnimal inherit from Animal, give it an Animal inside:
local ConveyorAnimal = {}
ConveyorAnimal.animal = Animal:new()
Now ConveyorAnimal can use Animal behavior without being tightly bound.
Interfaces / expected behavior Plot can just expect anything placed on it to have a GetSize method:
function Plot:addItem(item)
assert(type(item.GetSize) == "function", "Item must have GetSize method")
-- use item:GetSize() without knowing internal details
end
Dependency injection
Pass dependencies instead of creating them inside:
local myAnimal = Animal:new()
local plot = Plot:new(myAnimal)
Plot dosent care which Animal its given.
Event-driven communication
Instead of calling methods directly fire events: