You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Roblox Studio中如何无直接依赖连接多个服务?

Roblox Studio服务协同解耦问题

问题描述

我在Roblox Studio开发中遇到服务间协同的难题:当Service #1触发事件时,需要Service #2做出响应,但不想让Service #2直接依赖Service #1,避免代码结构混乱。目前用setup模块作为临时方案,但不想持续新增这类模块来实现服务协同。试过基于setup模块的方案,感觉有问题,AI给出的建议也不是最优解。

当前客户端初始化代码

if not game:IsLoaded() then
    game.Loaded:Wait()
end

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local LinkHandler = require(ReplicatedStorage.Shared.Modules.LinkHandler)
local safeRequire = require(ReplicatedStorage.Shared.Modules.Utils.safeRequire)
local PlayerLinkServiceClient = require(ReplicatedStorage.Shared.Services.PlayerLinkService.PlayerLinkServiceClient)
local MountUI = require(ReplicatedStorage.UI.MountUI)

local DataService = require(ReplicatedStorage.Packages.DataService).client
DataService:init()

PlayerLinkServiceClient:SetLinkHandler(LinkHandler)

local function bootstrapServices()
    local bootstrapedServices = {}
    for _, serviceModule: ModuleScript in ReplicatedStorage.Shared.Services:GetDescendants() do
        if not serviceModule:IsA("ModuleScript") then
            continue
        end
        local service, requireResult = safeRequire(serviceModule)
        if not service then
            warn(`[{script.Name}] Could not bootstrap service "{serviceModule}": {requireResult}`)
            continue
        end
        if type(service) ~= "table" or type(service.Init) ~= "function" then
            --warn(`[{script.Name}] Service "{serviceModule}" does not have an init function.`)
            continue
        end
        local success, initResult = pcall(service.Init, service)
        if not success then
            warn(`[{script.Name}] Failed to initialize service "{serviceModule}": {initResult}`)
            continue
        end
        table.insert(bootstrapedServices, serviceModule.Name)
    end
    return bootstrapedServices
end

local services = bootstrapServices()
print(`[{script.Name}] Successfully bootstrapped services: {table.concat(services, ", ")}`)

local function bootstrapSetupModules()
    local bootstrapedModules = {}
    local setupModules = ReplicatedStorage.Shared.Modules.Setup:GetDescendants()
    table.sort(setupModules, function(left: Instance, right: Instance)
        return left:GetFullName() < right:GetFullName()
    end)

    for _, setupModule in setupModules do
        if not setupModule:IsA("ModuleScript") then
            continue
        end

        local setup, requireResult = safeRequire(setupModule)
        if not setup then
            warn(`[{script.Name}] Could not bootstrap setup module "{setupModule:GetFullName()}": {requireResult}`)
            continue
        end

        if type(setup) ~= "function" then
            warn(`[{script.Name}] Setup module "{setupModule:GetFullName()}" is not a function`)
            continue
        end

        local success, initResult = pcall(setup)
        if not success then
            warn(`[{script.Name}] Failed to initialize setup module "{setupModule:GetFullName()}": {initResult}`)
            continue
        end

        table.insert(bootstrapedModules, setupModule:GetFullName())
    end

    return bootstrapedModules
end

local setupModules = bootstrapSetupModules()
print(`[{script.Name}] Successfully bootstrapped setup modules: {table.concat(setupModules, ", ")}`)

MountUI()
print(`[{script.Name}] Successfully mounted UI`)

task.wait(5)

local PlayerReadyServiceClient = require(ReplicatedStorage.Shared.Services.PlayerReadyService.PlayerReadyServiceClient)
PlayerReadyServiceClient:Ready()

print(`[{script.Name}] Player is ready now for PlayerReadyService`)

优化方案

1. 事件总线(Event Bus)模式

实现全局事件总线模块,所有服务通过总线发布、订阅事件,无需直接依赖彼此,彻底解耦服务间的关联。

事件总线实现示例

-- Shared/Modules/EventBus.lua
local EventBus = {}
local events = {}

-- 订阅事件,返回取消订阅的函数
function EventBus:Subscribe(eventName, callback)
    if not events[eventName] then
        events[eventName] = {}
    end
    table.insert(events[eventName], callback)
    return function()
        local index = table.find(events[eventName], callback)
        if index then
            table.remove(events[eventName], index)
        end
    end
end

-- 发布事件,触发所有订阅的回调
function EventBus:Publish(eventName, ...)
    if events[eventName] then
        for _, callback in ipairs(events[eventName]) do
            task.spawn(callback, ...)
        end
    end
end

return EventBus

服务中使用方式

  • Service #1发布事件:
local EventBus = require(ReplicatedStorage.Shared.Modules.EventBus)
-- 触发事件并传递数据
EventBus:Publish("PlayerDataLoaded", playerData)
  • Service #2订阅事件:
local EventBus = require(ReplicatedStorage.Shared.Modules.EventBus)
-- 订阅事件并处理逻辑
local unsubscribe = EventBus:Subscribe("PlayerDataLoaded", function(playerData)
    -- 更新UI或执行其他业务逻辑
end)

-- 服务销毁时取消订阅,避免内存泄漏
function Service2:Destroy()
    unsubscribe()
end

2. 依赖注入(Dependency Injection)

在服务初始化阶段,通过Bootstrap模块统一注入所需依赖,而非让服务自行require其他服务,既保留服务间的协同能力,又避免硬依赖导致的结构混乱。

修改Bootstrap函数实现依赖注入

local function bootstrapServices()
    local servicesMap = {}
    local bootstrapedServices = {}

    -- 先加载所有服务但不初始化
    for _, serviceModule: ModuleScript in ReplicatedStorage.Shared.Services:GetDescendants() do
        if not serviceModule:IsA("ModuleScript") then
            continue
        end
        local service, requireResult = safeRequire(serviceModule)
        if not service then
            warn(`[{script.Name}] Could not load service "{serviceModule}": {requireResult}`)
            continue
        end
        servicesMap[serviceModule.Name] = service
    end

    -- 初始化服务并注入所有服务引用作为依赖
    for serviceName, service in pairs(servicesMap) do
        if type(service) ~= "table" or type(service.Init) ~= "function" then
            continue
        end
        local success, initResult = pcall(service.Init, service, servicesMap)
        if not success then
            warn(`[{script.Name}] Failed to initialize service "{serviceName}": {initResult}`)
            continue
        end
        table.insert(bootstrapedServices, serviceName)
    end

    return bootstrapedServices
end

服务中使用注入的依赖

-- Service2.lua
local Service2 = {}

function Service2:Init(services)
    -- 从注入的依赖中获取Service1的引用
    self.PlayerDataService = services.PlayerDataService
    -- 监听Service1的事件
    self.PlayerDataService.DataLoaded:Connect(function(playerData)
        -- 执行对应业务逻辑
    end)
end

return Service2

3. 移除冗余Setup模块

采用上述两种方案后,原有的Setup模块可逐步移除,所有服务间的协同逻辑通过事件总线或依赖注入实现,代码结构更清晰,避免新增大量临时Setup模块。

内容的提问来源于stack exchange,提问作者Кирилл Неверлузов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.05 19:04:50