使用服务定位器转换不守规矩的依赖注入模型
Converting an unruly dependency injection model with a service locator
我在游戏引擎项目中使用 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(), *_theRenderer.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-only 渲染器,但我希望最终支持 OpenGL/Linux;这就是为什么一切都作为引用传递并作为指针而不是具体类型创建的原因)
我遇到过几乎所有子系统都以某种方式依赖于其他所有子系统的情况。运行。
但是,由于构建顺序问题,依赖注入不起作用,因为一个或多个必需存在的子系统尚未构建。两阶段构造存在同样的问题:子系统可能在下游需要它时尚未初始化。
我看了 service locator pattern and 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;
}
我在游戏引擎项目中使用 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(), *_theRenderer.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-only 渲染器,但我希望最终支持 OpenGL/Linux;这就是为什么一切都作为引用传递并作为指针而不是具体类型创建的原因)
我遇到过几乎所有子系统都以某种方式依赖于其他所有子系统的情况。运行。
但是,由于构建顺序问题,依赖注入不起作用,因为一个或多个必需存在的子系统尚未构建。两阶段构造存在同样的问题:子系统可能在下游需要它时尚未初始化。
我看了 service locator pattern and 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;
}