C ++ правильный случай использования синглтона - PullRequest
0 голосов
/ 23 января 2019

Я делаю игру, чтобы попрактиковаться в хорошем дизайне / арке, и я думаю, что запрограммировал себя на проверку Я не уверен, что моя архитектура чиста сейчас, какой-то совет будет сцеплением.

У меня есть class Game, который содержит экземпляр class GraphicsManager class InputManager и class StateManager

Графический менеджер обрабатывает SDL, как и менеджер ввода. менеджер состояний представляет собой стек абстрактного типа class GameState

В каждом кадре игра вызывает обновление для GameState в верхней части стека состояний и передает указателю StateManager указанное состояние, чтобы GameState мог выдвинуть новое состояние или вытолкнуть себя из стека.

GameState* currentState = stateManager.getState() //return top currentState.update(&stateManager) // state might change here

Проблема в том, когда я представляю class Screen. Экран должен принадлежать различным реализациям GameState, поэтому, если бы у меня был OpenState, я бы ожидал, что он будет иметься на OpeningScreen. Проблема в том, что Screen требуется доступ к экземпляру Graphics, чтобы получить указатель на SDL_Renderer, и я не вижу подходящего места для передачи этого указателя вниз. Передача указателя из Game -> GameState -> Screen выглядит как-то странно.

Я чувствую, что мне нужно превратить каждого 'менеджера' в синглтон, но если есть что-то более элегантное, я бы с удовольствием изучил

Вот мой код спагетти для ref https://github.com/MicahMartin/FightingGame/tree/master/src

1 Ответ

0 голосов
/ 23 января 2019

Вы можете использовать синглтон вместо того, чтобы вводить свои зависимости явно, но имейте в виду, что этот дизайн приводит к менее тестируемому коду и высокофункциональной связи между компонентами.Другие экосистемы решили эту проблему, вводя средства для внедрения зависимостей декларативным способом, благодаря структурам, которые предоставляют механизм для замены фактических зависимостей на «тестовые двойники».Узнайте, как Spring MVC работает с Java-приложениями.

Если вы уверены, что это именно то, что вам нужно, вы можете использовать аналогичный (но все же менее гибкий) подход, реализующий шаблон «поиска сервисов», который состоит в следующем:единственная точка, которая содержит указатели на экземпляры «инъецируемых» объектов

ServiceLocator::inject<Screen*>(new Screen () , "screen1")

//

...

//
Screen* s = ServiceLocator::getInstance<Screen*>("screen1")

Таким образом, вы можете избежать передачи указателей вокруг и в то же время быть более разъединенными.Теперь предположим, что у вас есть MockScreen, который наследуется от Screen.Предположим также, что вы хотите протестировать метод int MyClass :: workWithScreen (), который зависит от экземпляра Screen, на который ссылается метка "screen1".Вы можете написать свой тест как

Myclass unitUnderTest;
ServiceLocator::inject<Screen*>(new MockScreen () , "screen1");

int result = unitUnderTest.workWithScreen()

assert ....

Я оставлю вас в качестве упражнения по реализации локатора службы!

Имейте в виду, что таким образом вы также вводите в свой кодНедостатки внедрения зависимостей: хуже всего то, что вы теряете контроль над дизайном компоновки вашего компонента, каждый объект может получить доступ к каждому объекту.Это не большая проблема, когда вы разрабатываете некоторый обработчик API REST, который основан на надежном и хорошо известном архитектурном паттерне (controller -> service -> repository -> db и затем возвращается), но вы должны быть осторожны в других контекстах!

...