I’ve just recently started doing research on how I can implement OOP into my workflow. I’ve always used a single script architecture approach.
In my main script I would have 2 tables, “Shared” and “Modules”. The Shared table holds all modules which are shared between the client and server and the Modules table holds the client’s modules or server’s modules and are both passed in to the .Init function in every ModuleScript which allows for communication between all scripts in the game. So I expanded on this idea and added a third table in the main script called “Classes”. This table holds all classes and is again passed into the .Init function of all ModuleScript’s.
Here’s what that looks like:
-- Server script but looks the exact same on Client
local Modules = {}
local Classes = {}
local Shared = {}
local SharedFolder = game.ReplicatedStorage:WaitForChild("Common")
local ClassesFolder = script:WaitForChild("Classes")
local ModulesFolder = script:WaitForChild("Modules")
local LoadedPlayers = {}
for _, Module in ipairs(ModulesFolder:GetChildren()) do
if Module:IsA("ModuleScript") then
Modules[Module.Name] = require(Module)
end
end
for _, Module in ipairs(SharedFolder:GetDescendants()) do
if Module:IsA("ModuleScript") then
Shared[Module.Name] = require(Module)
end
end
for _, Module in ipairs(ClassesFolder:GetDescendants()) do
if Module:IsA("ModuleScript") then
Classes[Module.Name] = require(Module)
end
end
for _, Module in pairs(Modules) do
if Module.Init then
coroutine.wrap(function()
Module.Init(Modules, Shared, Classes)
end)()
end
end
for _, Player in ipairs(game.Players:GetPlayers()) do
table.insert(LoadedPlayers, Player.Name)
for _, Module in pairs(Modules) do
if Module.PlayerAdded then
coroutine.wrap(function()
Module.PlayerAdded(Player, Modules, Shared)
end)()
end
end
end
game.Players.PlayerAdded:Connect(function(Player)
if not table.find(LoadedPlayers, Player.Name) then
table.insert(LoadedPlayers, Player.Name)
for _, Module in pairs(Modules) do
if Module.PlayerAdded then
coroutine.wrap(function()
Module.PlayerAdded(Player, Modules, Shared)
end)()
end
end
end
end)
game.Players.PlayerRemoving:Connect(function(Player)
if table.find(LoadedPlayers, Player.Name) then
table.remove(LoadedPlayers, table.find(LoadedPlayers, Player.Name))
end
for _, Module in pairs(Modules) do
if Module.PlayerRemoving then
coroutine.wrap(function()
Module.PlayerRemoving(Player, Modules, Shared)
end)()
end
end
end)
Is this the best way to manage classes? I can’t find any examples of how to actually organize classes in your code. Should I be requiring all classes and passing them into each ModuleScript? Or is that inefficient and should manually require the classes I need? I’ve heard the term “Composition over Inheritance” but what should my hierarchy look like if I’m using composition? For inheritance I’d imagine you place each subclass under it’s superclass and have a hierarchy of ModuleScripts. But with composition would I just throw all ModuleScripts into one Folder?
I’ve been looking for an answer to these questions for hours and can’t find anything. Nobody seems to give examples on what your codebase should actually look like when using OOP and I can only find examples of simple classes and objects.