Как использовать Inpendency Injection в сочетании с шаблоном Factory


10

Рассмотрим модуль, который отвечает за синтаксический анализ файлов любого типа. Я думаю об использовании паттерна стратегии для решения этой проблемы, как я уже объяснил здесь . Пожалуйста, обратитесь к связанному посту, прежде чем продолжить этот вопрос.

Рассмотрим класс B, которому нужно содержимое файла product.xml. Этот класс должен будет создать экземпляр соответствующего конкретного разработчика интерфейса Parser для анализа XML-файла. Я могу делегировать создание экземпляра соответствующего конкретного реализатора на Фабрику так, чтобы у класса В была Фабрика. Тем не менее, класс B будет «зависеть» от Factory для создания конкретного реализатора. Это означает, что конструктор или метод установки в классе B должны быть переданы Factory.

Следовательно, Factory и класс B, которым необходимо проанализировать файл, будут тесно связаны друг с другом. Я понимаю, что могу ошибаться в том, что объяснил до сих пор. Я хотел бы знать, могу ли я использовать внедрение зависимостей в сценарии, где вводимая зависимость - это Фабрика, и каков будет правильный способ реализовать это, чтобы я мог использовать преимущества таких областей, как насмешка над Фабрикой в ​​моих модульных тестах.

Ответы:


8

Правильный способ сделать это - зависеть от интерфейса, а затем внедрить реализацию этого интерфейса в класс B.

Интерфейс - это самая тонкая вещь, от которой вы можете зависеть - я сравниваю его с попыткой получить пучок дыма. Код должен соединяться с чем-то, иначе он ничего не будет делать, но связь с интерфейсом настолько разобщена, насколько это возможно, но интерфейсы могут предоставить все функции, которые вы могли бы пожелать.

Поэтому пусть конструктор класса B берет интерфейс с нужным ему классом, а фабрика создает этот класс как средство реализации интерфейса. Не зависит от фабрики, зависит от интерфейса, и пусть фабрика обеспечивает реализацию этой фабрики.

Так что да, вы будете использовать внедрение зависимостей, но в этом нет ничего плохого. Внедрение зависимостей - в частности, простое внедрение в конструктор - должно быть «нормальным» способом ведения дел. Просто отложите новые вещи как можно дальше назад в своем приложении (и как можно ближе к первой строке main) и скройте новый вызов в классе, предназначенном специально для создания вещей.

Итог: не стесняйтесь вводить зависимости. Это должно быть нормальным способом ведения дел.


Внедряет ли конструктор откладывание новых вещей как можно дольше?
Джеффо

Это было плохо - я исправил это так, чтобы сказать «как можно глубже в вашем приложении».
Ник Ходжес

Да. Я думал о зависимости от интерфейса Parser вместо фабрики. Теперь, если другой класс использует класс B, он должен будет зависеть от интерфейса, от которого зависит класс B. Это будет продолжаться вплоть до класса высшего уровня. Этот класс верхнего уровня должен будет наконец использовать фабрику для создания экземпляра соответствующего конкретного экземпляра Parser. Должен ли класс верхнего уровня зависеть от фабрики или он должен использовать фабрику непосредственно внутри своих методов?
CKing

1
Хорошо сказано: не
стесняйтесь

9

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

Тем не менее, позвольте мне немного рассказать о внедрении зависимостей и о том, как вы обычно их используете.

Существует множество способов внедрения зависимостей, но наиболее распространенными являются внедрение конструкторов, внедрение свойств и прямой DIContainer. Я буду говорить о внедрении конструктора, так как внедрение свойства является неправильным подходом в большинстве случаев (правильный подход иногда), и доступ к DIContainer не является предпочтительным, за исключением случаев, когда вы абсолютно не можете использовать ни один из других подходов.

Внедрение в конструктор - это то, где у вас есть интерфейс для зависимости и DIContainer (или фабрика), который знает конкретную реализацию для этой зависимости, и всякий раз, когда вам нужен объект, который зависит от этого интерфейса, во время создания вы передаете реализацию из фабрики в Это.

т.е.

IDbConnectionProvider connProvider = DIContainer.Get<IDbConnectionProvider>();
IUserRepository userRepo = new UserRepository(connProvider);
User currentUser = userRepo.GetCurrentUser();

Многие инфраструктуры DI могут значительно упростить это, когда ваш DIContainer будет проверять конструктор UserRepository на наличие интерфейсов, для которых он знает конкретные реализации, и автоматически передает их вам; этот метод часто называют инверсией контроля, хотя DI и IoC - термины, которые сильно взаимозаменяемы и имеют неопределенные (если таковые имеются) различия.

Теперь, если вам интересно, как всеобъемлющий код обращается к DIContainer, у вас может быть статический класс для доступа к нему, или что более уместно, так это то, что большинство сред DI позволяют вам создавать DIContainer, в котором он фактически будет вести себя как Оболочка во внутренний одноэлементный словарь для типов, которые, как она знает, являются конкретными для данных интерфейсов.

Это означает, что вы можете обновлять DIContainer где угодно в коде и получать тот же DIContainer, который вы уже настроили, чтобы знать ваши связи между интерфейсом и бетоном. Обычный способ скрыть DIContainer от частей кода, которые не должны напрямую с ним взаимодействовать, состоит в том, чтобы просто гарантировать, что только необходимые проекты имеют ссылку на структуру DI.


Я не полностью согласен с первым абзацем. Есть еще сценарии, в которых вы зависите от фабрики (реализующей интерфейс), которая вводится контейнером DI.
Петр Перак

Я думаю, что вы упустили момент, так как OP не говорил о платформах DI или контейнерах DI.
Док Браун

@Jimmy Hoffa Хотя вы сделали несколько очень интересных замечаний, мне известны такие контейнеры IoC, как Spring и концепция внедрения зависимостей. Меня беспокоит сценарий, в котором вы не используете инфраструктуру IoC, в этом случае вам нужно написать класс, который будет создавать для вас ваши зависимости. Если класс A зависит от интерфейса B, а класс C использует класс A, то класс C зависит от интерфейса B. Кто-то должен предоставить классу C класс B ce. Должен ли класс C зависеть от кла
CKing

Если класс A зависит от интерфейса B, а класс C использует класс A, то класс C зависит от интерфейса B. Кто-то должен предоставить классу C интерфейс B. Должен ли класс C зависеть от интерфейса B или он должен зависеть от класса, который создал экземпляр зависимости заводские. Вы, кажется, ответили на этот вопрос в своем ответе, но с информационной перегрузкой :)
CKing

@bot, как я уже говорил в предисловии, по большей части вы можете рассматривать фабрики как подмножество функциональности контейнера DI, это не верно для всех сценариев, как упоминал Док Браун, но посмотрите на мой пример кода и замените DIContainerна DbConnectionFactoryи концепцию все еще стоит, что вы извлечете конкретную реализацию из вашего DI / Factory / etc и передадите ее потребителям типа во время их создания.
Джимми Хоффа

5

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

Я использую DI, чтобы вводить фабрики довольно регулярно.


1
Если класс зависит от Фабрики, вам нужно будет смоделировать Фабрику при модульном тестировании этого класса. Как ты это делаешь? Пусть класс зависит от фабричного интерфейса?
CKing

4

В инъекционных фабриках нет ничего плохого. Это стандартный способ, если вы не можете решить во время сборки родительского объекта, какая зависимость вам понадобится. Я думаю, что пример объяснит это лучше всего. Я буду использовать c #, потому что я не знаю Java.


class Parent
{
    private IParserFactory parserFactory;
    public Parent(IParserFactory factory)
    {
        parserFactory = factory
    }

    public ParsedObject ParseFrom(string filename, FileType fileType)
    {
        var parser = parserFactory.CreateFor(fileType);
        return parser.Parse(filename);
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.