У меня простой вопрос, и я даже не уверен, что у него есть ответ, но давайте попробуем. Я пишу на C ++ и использую инъекцию зависимости, чтобы избежать глобального состояния. Это работает довольно хорошо, и я не часто сталкиваюсь с неожиданным / неопределенным поведением.
Однако я понимаю, что по мере роста моего проекта я пишу много кода, который я считаю стандартным. Хуже того: тот факт, что кода больше, чем фактический код, иногда делает его трудным для понимания.
Ничто не сравнится с хорошим примером, так что поехали:
У меня есть класс TimeFactory, который создает объекты Time.
Для более подробной информации (не уверен, что это актуально): Объекты времени довольно сложны, потому что Время может иметь разные форматы, и преобразование между ними не является ни линейным, ни простым. Каждое «Время» содержит Синхронизатор для обработки преобразований, и чтобы убедиться, что они имеют одинаковый, правильно инициализированный синхронизатор, я использую TimeFactory. TimeFactory имеет только один экземпляр и имеет широкое применение, поэтому он подходит для синглтона, но, поскольку он изменчив, я не хочу делать его синглтоном
В моем приложении много классов должны создавать объекты Time. Иногда эти классы глубоко вложены.
Допустим, у меня есть класс A, который содержит экземпляры класса B, и так далее вплоть до класса D. Класс D должен создавать объекты Time.
В моей наивной реализации я передаю TimeFactory конструктору класса A, который передает его конструктору класса B и так далее до класса D.
Теперь представьте, что у меня есть пара классов, таких как TimeFactory, и пара иерархий классов, подобных приведенной выше: я теряю всю гибкость и удобочитаемость, которые, я полагаю, можно получить с помощью внедрения зависимости.
Я начинаю задумываться, нет ли в моем приложении серьезного недостатка в дизайне ... Или это неизбежное зло использования инъекций зависимости?
Что вы думаете ?
источник
Ответы:
Кажется, что ваш
Time
класс - это очень простой тип данных, который должен принадлежать «общей инфраструктуре» вашего приложения. Я не работаю хорошо для таких классов. Подумайте о том, что это означает, если такой класс, как например,string
должен быть внедрен в каждую часть кода, которая использует строки, и вам нужно будет использоватьstringFactory
как единственную возможность создания новых строк - читаемость вашей программы снизится на порядок величина.Так что мое предложение: не используйте DI для общих типов данных, таких как
Time
. Напишите модульные тесты для самогоTime
класса, а когда это будет сделано, используйте его везде в своей программе, точно так же, какstring
класс, илиvector
класс, или любой другой класс стандартной библиотеки lib. Используйте DI для компонентов, которые должны быть отделены друг от друга.источник
Time
другими частями вашей программы. Таким образом, вы можете принять и принять жесткую связь сTimeFactory
. Однако я бы избегал иметь единственный глобальныйTimeFactory
объект с состоянием (например, информацию о локали или что-то в этом роде), что могло бы вызвать неприятные побочные эффекты для вашей программы и затруднить общее тестирование и повторное использование. Либо сделайте его безгосударственным, либо не используйте его как одиночный.Time
а такжеTimeFactory
от степени «эволюционируемости», которая вам понадобится для будущих расширений, связанных сTimeFactory
, и так далее.Что вы подразумеваете под «Я теряю всю гибкость и удобочитаемость, которые, я полагаю, получаю с помощью внедрения зависимостей» - DI не о читабельности. Речь идет о разъединении зависимости между объектами.
Похоже, у вас есть класс A, создающий класс B, класс B, создающий класс C, и класс C, создающий класс D.
То, что вы должны иметь, это класс B, вводимый в класс A. Класс C, вводимый в класс B. Класс D, вводимый в класс C.
источник
Я не уверен, почему ты не хочешь сделать свое время фабричным синглтоном. Если в вашем приложении есть только один экземпляр, то это де-факто синглтон.
Это, как говорится, очень опасно совместно использовать изменяемый объект, за исключением случаев, когда он должным образом охраняется с помощью блоков синхронизации, и в этом случае нет никаких причин для того, чтобы он не был одиночным.
Если вы хотите сделать внедрение зависимостей, вы можете взглянуть на Spring или другие структуры внедрения зависимостей, которые позволят вам автоматически назначать параметры с помощью аннотации.
источник
Это делается для того, чтобы служить дополнительным ответом Доку Брауну, а также отвечать на оставшиеся без ответа комментарии Динаиза, которые все еще имеют отношение к Вопросу.
То, что вам, вероятно, нужно, это рамки для DI. Наличие сложных иерархий не обязательно означает плохой дизайн, но если вам нужно внедрить восходящую TimeFactory (от A до D) вместо прямой инъекции в D, то, вероятно, что-то не так с тем, как вы делаете Dependency Injection.
Синглтон? Нет, спасибо. Если вам нужен только один istance, сделайте его общим для контекста вашего приложения (использование контейнера IoC для DI, такого как Infector ++, требует просто привязать TimeFactory как один istance), вот пример (кстати, C ++ 11, но так и C ++. Возможно, переместим на C ++ 11? Вы получаете приложение без утечек бесплатно):
Хорошая особенность контейнера IoC заключается в том, что вам не нужно передавать фабрику времени до D. Если вашему классу "D" нужна фабрика времени, просто укажите фабрику времени в качестве параметра конструктора для класса D.
как вы видите, вы вводите TimeFactory только один раз. Как использовать «А»? Очень просто, каждый класс вводится, строится в основном или заводится с завода.
каждый раз, когда вы создаете класс A, он будет автоматически (ленивый istantiation) вводиться со всеми зависимостями вплоть до D, а D будет добавляться с TimeFactory, поэтому, вызывая только 1 метод, вы получаете готовую иерархию (и даже сложные иерархии решаются таким образом). удаление БОЛЬШОГО кода пластины котельной): вам не нужно называть «новый / удалить», и это очень важно, потому что вы можете отделить логику приложения от кода клея.
Это просто, у вашей TimeFactory есть метод «создать», затем просто используйте другую подпись «создать (параметры)», и все готово. Параметры, которые не являются зависимостями, часто разрешаются таким образом. Это также избавляет от необходимости вводить такие вещи, как «строки» или «целые числа», потому что это просто добавляет дополнительную пластину котла.
Кто кого создает? Контейнер IoC создает экземпляры и фабрики, фабрики создают остальное (фабрики могут создавать разные объекты с произвольными параметрами, поэтому вам не нужно состояние для фабрики). Вы по-прежнему можете использовать фабрики как оболочки для IoC-контейнера: вообще говоря, Injectin для IoC-контейнера очень плох и аналогичен использованию сервисного локатора. Некоторые люди решили проблему, упаковав Контейнер IoC с фабрикой (это не является строго необходимым, но имеет преимущество в том, что Контейнер решает иерархию, и все ваши фабрики становятся еще проще в обслуживании).
Также не злоупотребляйте внедрением зависимостей, простые типы могут быть просто членами класса или локальными переменными области видимости. Это кажется очевидным, но я видел, как люди вводили «std :: vector» только потому, что была структура DI, которая позволяла это делать. Всегда помните закон Деметры: «Вводите только то, что вам действительно нужно ввести»
источник
Ваши классы A, B и C также должны создавать экземпляры Time или только класс D? Если это только класс D, то A и B не должны ничего знать о TimeFactory. Создайте экземпляр TimeFactory внутри класса C и передайте его классу D. Обратите внимание, что под «созданием экземпляра» я не обязательно имею в виду, что класс C должен отвечать за создание экземпляра TimeFactory. Он может получить DClassFactory из класса B, а DClassFactory знает, как создать экземпляр Time.
Техника, которую я также часто использую, когда у меня нет DI-фреймворка, предоставляет два конструктора, один из которых принимает фабрику, а другой - фабрику по умолчанию. Второй обычно имеет защищенный / пакетный доступ и используется в основном для модульных тестов.
источник
Я реализовал еще одну инфраструктуру внедрения зависимостей C ++, которая недавно была предложена для повышения - https://github.com/krzysztof-jusiak/di - библиотека не содержит макросов (бесплатно), только заголовки, C ++ 03 / C ++ 11 / C ++ 14 библиотека, обеспечивающая безопасность типов, время компиляции, внедрение макрос-зависимых конструкторов.
источник