To begin with, I will mention that this module is still in operation and I am trying to find a way to adequately work with generics, that is, dynamic typing of custom services.
- In fact, this is a singleton in a wrapper, just like any service in Roblox.
Open source code:
local singletonModulesFolder = script:FindFirstChild("SingletonModules")
local ServiceProvider = {}
ServiceProvider.singletons = {
--not table.insert for types
["Service"] = require(singletonModulesFolder.Service).new(),
["Players"] = require(singletonModulesFolder.Players),
}
function ServiceProvider:GetService<T>(className: string): T
if ServiceProvider.singletons[className] then
return ServiceProvider.singletons[className]
else
return game:GetService(className)
end
end
local game: DataModel & typeof(ServiceProvider) = setmetatable(ServiceProvider, { __index = game })
return game
In Roblox development, it is often necessary to create modules that provide specific functionality and are used in different parts of the game. To simplify access to these modules and make the code more organized, you can use the Service pattern
.
What is ServiceProvider?
ServiceProvider is a module that simulates the Roblox service system. It allows you to register your own modules as “services” and access them from any script in the game using the familiar syntax. game:GetService("MyServiceName")
Advantages of using ServiceProvider:
Imitation of the Roblox service system: Allows you to organize the code in a familar style
Centralized access to modules: All “services" are registered in one place, which simplifies their management and reduces the number of require()
in your code
Improved code readability: Instead of direct require()
, you use game:GetService(“ServiceName”), which makes the code more understandable and declarative
Example: Creating a custom “service”:
In fact, initially I wanted to register a QuickTimeEventService and I almost succeeded, but I abandoned this idea because I couldn’t type all parts of the code correctly, which lost the charm of writing code in the style of MetaClass.__index = Class
Let’s create a “service” that do nothing!
- in general, if I were you, you should write this in two different modules, but I thought it would be more readable for you to see everything in one code.
--!strict
-- ServiceName: The table that will hold the service's methods (the "class" definition).
local ServiceName = {}
-- ServiceNameMeta: The metatable for the service, used for the singleton pattern.
local ServiceNameMeta = {}
ServiceNameMeta.__index = ServiceName
-- Class: The table holding instance methods and properties.
local Class = {}
-- ClassMeta: Metatable for instances, enabling "inheritance" of methods from Class.
local ClassMeta = {}
ClassMeta.__index = Class
-- ServicePropertyes: A type definition for the instance's properties.
type ServicePropertyes = {
property: string
}
-- ServiceType: A type definition combining class methods and instance properties.
type ServiceType = typeof(Class) & ServicePropertyes
-- An example method that can be called on instances of the service.
function Class:SomeFunction1(): ()
end
-- Create: Creates new instances (objects) based on ServiceName.
function ServiceName:Create(property: string): ServiceType
local self = setmetatable({}, ClassMeta)
self.property = property
return self::ServiceType
end
-- ServiceNameType: Type definition for the service singleton.
type ServiceNameType = typeof(ServiceName)
-- new: Creates the singleton instance of the service.
local function new(): ServiceNameType
local self = setmetatable({}, ServiceNameMeta)
return self :: ServiceNameType
end
return { new = new }
I think you’ve noticed that I’ve separated the meta and class logic. I did this so that the player could not call __index from the service, could not cyclically call new() and separate dot and colon annotations. I think this way of writing is much more consistent with the service. Let me know if I’m wrong.
next: this module also supports the ability to change services.
here is an example of adding the KickAllPlayers feature for the Players service:
local PlayersModule = {}
type Enchant = typeof(PlayersModule)
function PlayersModule:KickAllPlayers(reason: string)
local PlayersService = game:GetService("Players")
for _, player in ipairs(PlayersService:GetPlayers()) do
player:Kick(reason)
end
end
local EnchantedPlayers = setmetatable(PlayersModule, { __index = game:GetService("Players") })
return EnchantedPlayers :: Players & Enchant
NOT TO FORGET TO TYPING!!
You just have to repeat what I did. After creating the wrapper, we type it into typeof(module) & initial service
How do I get typing with using this script?
since the module is still in the works, I have not completed the logic for better receiving services, and the ReplicatedStorage type will point us to the type itself, but will not tighten the hierarchy, so I suggest you use… A bit of a crappy way
local Save = game -- saving the game... So ugly
local game = require(game.ReplicatedStorage.ServiceProvider)
local ReplicatedStorage: typeof(Save.ReplicatedStorage) = game:GetService("ReplicatedStorage") -- this only RS, typechecker know ur hierarhy in it
local Players: typeof(game.singletons.Players) & typeof(Save.Players) = game:GetService("Players") -- its a union of datamodel players and updated players
local Service: typeof(game.singletons.Service) = game:GetService("Service") -- ur own service
Thanks for your attention, this is the end.
- Good
- Normal
- Baaad
0 voters