При проектировании системы я часто сталкиваюсь с проблемой использования множества модулей (журналирование, доступ к базе данных и т. Д.) Другими модулями. Вопрос в том, как мне предоставить эти компоненты другим компонентам. Два ответа кажутся возможными для внедрения зависимости или использования фабричного шаблона. Однако оба кажутся неправильными:
- Фабрики затрудняют тестирование и не позволяют легко менять реализации. Они также не делают зависимости очевидными (например, вы изучаете метод, не обращая внимания на тот факт, что он вызывает метод, который вызывает метод, который вызывает метод, использующий базу данных).
- Внедрение зависимостей массово расширяет списки аргументов конструктора и размазывает некоторые аспекты по всему вашему коду. Типичная ситуация, когда конструкторы более половины классов выглядят так
(....., LoggingProvider l, DbSessionProvider db, ExceptionFactory d, UserSession sess, Descriptions d)
Вот типичная ситуация, с которой у меня проблема: у меня есть классы исключений, которые используют описания ошибок, загруженные из базы данных, используя запрос, который имеет параметр настройки языка пользователя, который находится в объекте сеанса пользователя. Поэтому для создания нового исключения мне нужно описание, которое требует сеанса базы данных и сеанса пользователя. Поэтому я обречен на перетаскивание всех этих объектов по всем моим методам на тот случай, если мне может понадобиться сгенерировать исключение.
Как мне решить такую проблему ??
Ответы:
Используйте внедрение зависимостей, но всякий раз, когда ваши списки аргументов конструктора становятся слишком большими, реорганизуйте их, используя Facade Service . Идея состоит в том, чтобы сгруппировать некоторые аргументы конструктора вместе, вводя новую абстракцию.
Например, вы можете ввести новый тип,
SessionEnvironment
инкапсулирующий aDBSessionProvider
, theUserSession
и загруженныйDescriptions
. Однако, чтобы знать, какие абстракции имеют смысл больше всего, нужно знать детали вашей программы.Подобный вопрос уже задавался здесь на SO .
источник
Исходя из этого, не похоже, что вы правильно понимаете DI - идея состоит в том, чтобы инвертировать шаблон создания объекта внутри фабрики.
Ваша конкретная проблема кажется более общей проблемой ООП. Почему объекты не могут просто генерировать нормальные, нечитаемые человеку исключения во время их выполнения, а затем иметь что-то перед финальной попыткой / перехватом, которая перехватывает это исключение, и в этот момент использует информацию сеанса для создания нового, более симпатичного исключения ?
Другой подход заключается в создании фабрики исключений, которая передается объектам через их конструкторы. Вместо того , чтобы бросать новое исключение, класс может бросить на метод завода (например
throw PrettyExceptionFactory.createException(data)
.Имейте в виду, что ваши объекты, кроме ваших заводских объектов, никогда не должны использовать
new
оператор. Исключения - это, как правило, один особый случай, но в вашем случае они могут быть исключением!источник
Builder
шаблон) диктует это. Если вы передаете параметры в конструктор, потому что ваш объект создает экземпляры других объектов, это очевидный случай для IoC.Вы уже достаточно хорошо перечислили недостатки статического шаблона фабрики, но я не совсем согласен с недостатками шаблона внедрения зависимостей:
Это внедрение зависимостей требует от вас написания кода для каждой зависимости, это не ошибка, а особенность: она заставляет вас задуматься о том, действительно ли вам нужны эти зависимости, тем самым способствуя слабой связи. В вашем примере:
Нет, ты не обречен. Почему бизнес-логика отвечает за локализацию ваших сообщений об ошибках для определенного сеанса пользователя? Что, если когда-нибудь в будущем вы захотите использовать этот бизнес-сервис из пакетной программы (в которой нет сеанса пользователя ...)? Или что, если сообщение об ошибке должно отображаться не зарегистрированному в данный момент пользователю, а его руководителю (который может предпочесть другой язык)? Или что, если вы хотите повторно использовать бизнес-логику на клиенте (у которого нет доступа к базе данных ...)?
Ясно, что локализация сообщений зависит от того, кто просматривает эти сообщения, т.е. это ответственность уровня представления. Поэтому я бы выбросил обычные исключения из бизнес-службы, которые содержат идентификатор сообщения, который затем можно найти в обработчике исключений на уровне представления в любом используемом источнике сообщений.
Таким образом, вы можете удалить 3 ненужных зависимости (UserSession, ExceptionFactory и, вероятно, описания), что сделает ваш код более простым и универсальным.
Вообще говоря, я бы использовал статические фабрики только для тех вещей, к которым вам нужен повсеместный доступ, и которые гарантированно будут доступны во всех средах, в которых мы когда-либо захотим запустить код (например, в журналах). Для всего остального я бы использовал обычную инъекцию зависимостей.
источник
Используйте внедрение зависимостей. Использование статических фабрик - это занятие
Service Locator
антипаттерна. Смотрите оригинальную работу Мартина Фаулера здесь - http://martinfowler.com/articles/injection.htmlЕсли ваши аргументы конструктора становятся слишком большими, и вы не используете контейнер DI, во что бы то ни стало, напишите свои собственные фабрики для создания экземпляров, позволяя настраивать его либо с помощью XML, либо связывая реализацию с интерфейсом.
источник
Я бы тоже пошел с инъекцией зависимостей. Помните, что DI выполняется не только через конструкторы, но и через установщики свойств. Например, регистратор может быть введен как свойство.
Кроме того, вы можете захотеть использовать контейнер IoC, который может снять с вас часть бремени, например, путем сохранения параметров конструктора для вещей, которые необходимы во время выполнения вашей доменной логикой (сохранение конструктора таким образом, чтобы раскрыть намерение класс и реальные доменные зависимости) и, возможно, внедрить другие вспомогательные классы через свойства.
Еще одним шагом, который вы можете сделать, является Aspect-Oriented Programmnig, которая реализована во многих основных средах. Это может позволить вам перехватить (или «посоветовать» использовать терминологию AspectJ) конструктор класса и внедрить соответствующие свойства, возможно, с помощью специального атрибута.
источник
Я не совсем согласен. По крайней мере, не в целом.
Простая фабрика:
Простая инъекция:
Оба фрагмента служат одной и той же цели, они устанавливают связь между
IFoo
иFoo
. Все остальное - просто синтаксис.Переход
Foo
кADifferentFoo
такому же примеру кода требует столько же усилий.Я слышал, что люди утверждают, что внедрение зависимости позволяет использовать разные привязки, но один и тот же аргумент может быть сделан о создании разных фабрик. Выбор правильной привязки так же сложен, как и выбор правильной фабрики.
Фабричные методы позволяют, например, использовать
Foo
в некоторых местах иADifferentFoo
в других местах. Кто-то может назвать это хорошим (полезно, если оно вам нужно), кто-то может назвать это плохим (вы могли бы сделать недоделанную работу по замене всего).Однако не так сложно избежать этой неоднозначности, если придерживаться одного метода, который возвращает,
IFoo
чтобы у вас всегда был один источник. Если вы не хотите стрелять себе в ногу, не держите заряженный пистолет или не направляйте его на ногу.Вот почему некоторые люди предпочитают явно получать зависимости в конструкторе, например так:
Я слышал аргументы "за" (без раздувания конструктора), я слышал аргументы "против" (использование конструктора позволяет больше автоматизации DI).
Лично, хотя я уступил нашему старшему, который хочет использовать аргументы конструктора, я заметил проблему с выпадающим списком в VS (вверху справа, чтобы просмотреть методы текущего класса), когда имена методов исчезают, когда один из них сигнатур метода длиннее, чем мой экран (=> раздутый конструктор).
На техническом уровне мне все равно. Любой вариант требует примерно столько же усилий, чтобы набрать. А поскольку вы используете DI, вы все равно обычно не будете вызывать конструктор вручную. Но ошибка пользовательского интерфейса Visual Studio заставляет меня предпочитать не раздувать аргумент конструктора.
Как примечание, введение зависимости и фабрики не являются взаимоисключающими . У меня были случаи, когда вместо вставки зависимости я вставлял фабрику, которая генерирует зависимости (к счастью, NInject позволяет вам использовать,
Func<IFoo>
поэтому вам не нужно создавать фактический класс фабрики).Варианты использования для этого редки, но они существуют.
источник
В этом фиктивном примере фабричный класс используется во время выполнения, чтобы определить, какой тип объекта входящего HTTP-запроса создавать, на основе метода HTTP-запроса. Сама фабрика внедряется с экземпляром контейнера внедрения зависимости. Это позволяет фабрике определять время выполнения и позволяет контейнеру внедрения зависимостей обрабатывать зависимости. Каждый входящий объект HTTP-запроса имеет как минимум четыре зависимости (суперглобальные и другие объекты).
Клиентский код для установки типа MVC в централизованном виде
index.php
может выглядеть следующим образом (проверки пропущены).Кроме того, вы можете удалить статическую природу (и ключевое слово) фабрики и позволить инжектору зависимостей управлять всем этим (следовательно, закомментированным конструктором). Однако вам придется изменить некоторые (не константы) ссылок
self
на члены класса ( ) на члены экземпляра ($this
).источник