使用服务定位器转换不守规矩的依赖注入模型

问题描述

我在游戏引擎项目中使用 DI 已经有一段时间了,但我只是碰壁了;给定以下创建顺序: 作业系统不依赖任何东西,一切都依赖于文件记录器。先创建作业系统,然后创建文件记录器,然后将每个依赖项的创建引用传递给其依赖项的构造函数

App::App(const std::string& cmdstring)
    : Enginesubsystem(),_theJobSystem{std::make_unique<JobSystem>(-1,static_cast<std::size_t>(JobType::Max),new std::condition_variable)},_theFileLogger{std::make_unique<FileLogger>(*_theJobSystem.get(),"game")},_theConfig{std::make_unique<Config>(keyvalueParser{cmdstring})},_theRenderer{std::make_unique<Renderer>(*_theJobSystem.get(),*_theFileLogger.get(),*_theConfig.get())},_theInputSystem{std::make_unique<InputSystem>(*_theFileLogger.get(),*_theRenderer.get())},_theUI{std::make_unique<UISystem>(*_theFileLogger.get(),*_theRenderer.get(),*_theInputSystem.get())},_theConsole{std::make_unique<Console>(*_theFileLogger.get(),_theAudioSystem{std::make_unique<AudioSystem>(*_theFileLogger.get()) },_theGame{std::make_unique<Game>()}
{
    SetupEnginesystemPointers();
    SetupEnginesystemChainOfResponsibility();
    LogSystemDescription();
}

void App::SetupEnginesystemPointers() {
    g_theJobSystem = _theJobSystem.get();
    g_theFileLogger = _theFileLogger.get();
    g_theConfig = _theConfig.get();
    g_theRenderer = _theRenderer.get();
    g_theUISystem = _theUI.get();
    g_theConsole = _theConsole.get();
    g_theInputSystem = _theInputSystem.get();
    g_theAudioSystem = _theAudioSystem.get();
    g_theGame = _theGame.get();
    g_theApp = this;
}

void App::SetupEnginesystemChainOfResponsibility() {
    g_theConsole->SetNextHandler(g_theUISystem);
    g_theUISystem->SetNextHandler(g_theInputSystem);
    g_theInputSystem->SetNextHandler(g_theApp);
    g_theApp->SetNextHandler(nullptr);
    g_theSubsystemHead = g_theConsole;
}

如您所见,将不同的子系统传递给其他子系统构造函数开始变得混乱。尤其是在处理作业、日志记录、控制台命令、UI、配置和音频(以及物理,未图示)时。

(旁注:这些最终将被通过工厂创建的接口替换以实现交叉兼容性,即渲染器严格来说是仅 DirectX/Windows 的渲染器,但我希望最终支持 OpenGL/Linux;这就是为什么一切都是作为引用传递并创建为指针而不是具体类型)

我遇到过几乎所有子系统都以某种方式依赖于其他所有子系统的情况。

但是,由于构建顺序问题,依赖注入不起作用,因为一个或多个必需的子系统尚未构建。两阶段构造也有同样的问题:子系统可能在下游需要时尚未初始化。

我查看了 service locator pattern 并且 this question 认为这是一个坏主意,但游戏行业喜欢使用坏主意(例如每个子系统的全局变量,以便使用特定于游戏的代码)工作。

转换为服务定位器会解决这个问题吗?

您知道哪些其他实现也可以解决该问题?

解决方法

我最终采用了 ServiceLocator 模式,将作为依赖项的每个子系统派生为服务:

App::App(const std::string& cmdString)
    : EngineSubsystem(),_theConfig{std::make_unique<Config>(KeyValueParser{cmdString})}
{
    SetupEngineSystemPointers();
    SetupEngineSystemChainOfResponsibility();
    LogSystemDescription();
}

void App::SetupEngineSystemPointers() {
    ServiceLocator::provide(*static_cast<IConfigService*>(_theConfig.get()));

    _theJobSystem = std::make_unique<JobSystem>(-1,static_cast<std::size_t>(JobType::Max),new std::condition_variable);
    ServiceLocator::provide(*static_cast<IJobSystemService*>(_theJobSystem.get()));

    _theFileLogger = std::make_unique<FileLogger>("game");
    ServiceLocator::provide(*static_cast<IFileLoggerService*>(_theFileLogger.get()));

    _theRenderer = std::make_unique<Renderer>();
    ServiceLocator::provide(*static_cast<IRendererService*>(_theRenderer.get()));

    _theInputSystem = std::make_unique<InputSystem>();
    ServiceLocator::provide(*static_cast<IInputService*>(_theInputSystem.get()));

    _theAudioSystem = std::make_unique<AudioSystem>();
    ServiceLocator::provide(*static_cast<IAudioService*>(_theAudioSystem.get()));

    _theUI = std::make_unique<UISystem>();
    _theConsole = std::make_unique<Console>();
    _theGame = std::make_unique<Game>();


    g_theJobSystem = _theJobSystem.get();
    g_theFileLogger = _theFileLogger.get();
    g_theConfig = _theConfig.get();
    g_theRenderer = _theRenderer.get();
    g_theUISystem = _theUI.get();
    g_theConsole = _theConsole.get();
    g_theInputSystem = _theInputSystem.get();
    g_theAudioSystem = _theAudioSystem.get();
    g_theGame = _theGame.get();
    g_theApp = this;
}

void App::SetupEngineSystemChainOfResponsibility() {
    g_theConsole->SetNextHandler(g_theUISystem);
    g_theUISystem->SetNextHandler(g_theInputSystem);
    g_theInputSystem->SetNextHandler(g_theRenderer);
    g_theRenderer->SetNextHandler(g_theApp);
    g_theApp->SetNextHandler(nullptr);
    g_theSubsystemHead = g_theConsole;
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...