Я видел два отличных видео (этот а также этот) о внедрении зависимости, законе деметры и глобальных состояниях (синглтоны считаются глобальными).
Я думаю, что у меня есть основная идея, но у меня уже есть несколько синглтон-классов в моей библиотеке. Однако, если я хочу тестируемый и «хорошо разработанный» или «менее связанный» код, я должен использовать DI и LoD. Это, конечно, означает, что Singletons (как шаблон проектирования) являются злом, потому что вызывающая сторона теперь не реализует, и любая зависимость от глобальной вещи плохая, по крайней мере, с точки зрения тестирования.
В частности, я создаю простой игровой движок без использования каких-либо больших сторонних библиотек. Это означает, что мне нужно работать с платформно-низкоуровневым кодом.
Давайте будем более конкретными. У меня есть раздел математики в моей библиотеке, где у меня есть класс Vector2. Он должен быть в состоянии «выбросить утверждение», когда для одной из его функций введены неверные данные. Или должен быть в состоянии зарегистрировать это как ошибку. Или оба. До этого времени я просто использовал Singleton<Logger>
так что я смог получить к нему доступ везде.
Но я согласен, эти вещи не должны использоваться, и DI решает эти проблемы. Например. Что делать, если регистратор еще не инициализирован? Что если я хочу использовать фиктивный логгер для тестов? И так далее … Что вы рекомендуете для этих случаев (например, классы Logger и Assert)?
Также LoD говорит, что я не должен использовать средства доступа для объектов (например, getObjectA()->getObjectB()->doSomething()
). Вместо этого передайте их в качестве параметра функции / конструктору. Ничего страшного, что все становится проще для тестирования (и отладки), но может быть больно пропускать эти функции.
Рассмотрим пример из движка Unity. В GameObject есть метод для получения компонента из этого объекта. Например. если я хочу вручную преобразовать мой объект, у меня нет выбора для вызова «объекта getter», что-то вроде этого:
this.GetComponent<Transform>().SetPosition(...);
Это против LoD, не так ли?
Это означает, что мне нужно работать с платформно-низкоуровневым кодом.
использование инверсия зависимости (не только инъекция).
Что вы рекомендуете для этих случаев (например, классы Logger и Assert)?
DI требует, чтобы вы изменили свои API, чтобы позволить вам вводить вещи, где они используются. Чтобы избежать ситуации, когда вам нужно добавить множество дополнительных параметров (один для регистратора, один для реализации assert или глобальные параметры конфигурации и т. Д.), Сгруппируйте их вместе:
Также в LoD говорится, что я не должен использовать методы доступа к объектам (например, getObjectA () -> getObjectB () -> doSomething ()). Вместо этого передайте их в качестве параметра функции / конструктору.
Есть несколько проблем с этим типом цепочки вызовов:
это поощряет повторение (если у вас есть getObjectA()->getObjectB()->
много раз в вашем коде, это уже проблема обслуживания)
это плохая замена незавершенного дизайна. LoD говорит, что если вам нужно doSomething()
начиная с экземпляра ObjectA
тогда ObjectA должен иметь метод doSomething:
void ObjectA::doSomething(ObjectA& a)
{
getObjectB()->doSomething();
}
(или похожие).
Это добавляет естественную точку для расширения того, как «doSomething выполняется, начиная с экземпляра ObjectA», что хорошо для обслуживания.
Это накладывает на весь клиентский код, который должен doSomething
тот факт, что нужно знать об интерфейсе ObjectB. Это звучит немного, но проблема широко распространена, и при применении в качестве политики проектирования она плохо работает (если у вашего ObjectA есть не только ObjectB, но также ObjectC и ObjectD, этого может быть достаточно, чтобы заставить вас тратить много время просто поддерживая отношения зависимости).
this.GetComponent<Transform>().SetPosition(...);
Это против LoD, не так ли?
Да. Код можно разделить следующим образом:
void SetPosition(Transform& t) { t.SetPosition(); }
код клиента:
SetPosition(this.GetComponent<Transform>());
Таким образом, чтобы установить позиции в клиентском коде, вам больше не нужен интерфейс Transform. Вы также не заботитесь о реализации void SetPosition(Transform& t)
, что есть API под названием GetComponent
,
Альтернативная реализация LoD для вашего примера выше:
void YourObject::SetTransformPositions()
{
GetComponent<Transformation>.SetPosition();
}
… где тип this
является YourObject*
,
Других решений пока нет …