Внедрение зависимости через конструкторы или установщики свойств?

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

Это не похоже на шаблон с существующим здесь кодом, поэтому я хочу выяснить, каков общий консенсус, плюсы и минусы конструкторов по сравнению со свойствами. Лучше ли использовать установщики свойств?


person Niall Connaughton    schedule 01.10.2009    source источник


Ответы (14)


Смотря как :-).

Если класс не может выполнять свою работу без зависимости, добавьте его в конструктор. Классу нужна новая зависимость, поэтому вы хотите, чтобы ваше изменение сломало ситуацию. Кроме того, создание не полностью инициализированного класса («двухэтапная конструкция») - это антипаттерн (ИМХО).

Если класс может работать без зависимости, сеттер в порядке.

person sleske    schedule 01.10.2009
comment
Я думаю, что во многих случаях предпочтительнее использовать шаблон Null Object и придерживаться требования ссылок на конструктор. Это позволяет избежать проверки на нуль и повышенной цикломатической сложности. - person Mark Lindell; 30.03.2010
comment
@Mark: Хороший момент. Однако вопрос был о добавлении зависимости к существующему классу. Тогда сохранение конструктора без аргументов обеспечивает обратную совместимость. - person sleske; 30.03.2010
comment
А что насчет того, когда зависимость необходима для работы, но обычно достаточно внедрения этой зависимости по умолчанию. Тогда должна ли эта зависимость быть переопределена свойством или перегрузкой конструктора? - person Patrick Szalapski; 26.08.2010
comment
@Patrick: Под тем, что класс не может выполнять свою работу без зависимости, я имел в виду, что разумного значения по умолчанию не существует (например, для класса требуется соединение с БД). В вашей ситуации оба будут работать. Я по-прежнему обычно выбираю конструктор, потому что он снижает сложность (что, если, например, сеттер вызывается дважды)? - person sleske; 27.08.2010
comment
Предостережение: конструктор форм Visual Studio, кажется, плачет и умирает, если вы используете внедрение конструктора в пользовательские элементы управления. - person Maslow; 09.02.2011
comment
@Maslow: Да, довольно много фреймворков на самом деле не предназначены для внедрения зависимостей; Думаю, когда они создавались, это не было обычным явлением. Например, Java Beans и классы, предназначенные для сохранения с использованием Hibernate, требуют установщиков для всех полей :-(. - person sleske; 09.02.2011
comment
Хорошая запись об этом здесь explorejava.com/different-types-of -боб-инъекция-весной - person Eldhose Abraham; 27.12.2017

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

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

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

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

person Doctor Blue    schedule 01.10.2009
comment
Конечно, цепочка конструкторов работает только при наличии разумного значения по умолчанию для нового параметра. Но иначе все равно не избежать поломки ... - person sleske; 01.10.2009
comment
Обычно в качестве параметра по умолчанию вы должны использовать все, что вы использовали в методе до внедрения зависимостей. В идеале это сделало бы добавление нового конструктора чистым рефакторингом, поскольку поведение класса не изменилось бы. - person Doctor Blue; 01.10.2009
comment
Я понимаю вашу точку зрения о зависимостях управления ресурсами, таких как соединения с базой данных. Я думаю, что проблема в моем случае заключается в том, что класс, для которого я добавляю зависимость, имеет несколько подклассов. В мире контейнеров IOC, где свойство будет устанавливаться контейнером, использование установщика по крайней мере уменьшит давление на дублирование интерфейса конструктора между всеми подклассами. - person Niall Connaughton; 01.10.2009

Конечно, установка конструктора означает, что вы можете проверить все сразу. Если вы назначаете вещи в поля, доступные только для чтения, у вас есть некоторые гарантии относительно зависимостей вашего объекта прямо во время создания.

Добавление новых зависимостей - настоящая боль, но, по крайней мере, так компилятор продолжает жаловаться, пока не станет исправным. Думаю, это хорошо.

person Joe    schedule 01.10.2009
comment
Плюс один за это. Вдобавок это значительно снижает опасность циклических зависимостей ... - person gilgamash; 12.09.2017

Если у вас есть большое количество необязательных зависимостей (что уже является запахом), то, вероятно, инъекция сеттера - это путь. Однако внедрение конструктора лучше раскрывает ваши зависимости.

person epitka    schedule 01.10.2009

Общий предпочтительный подход - как можно чаще использовать внедрение конструктора.

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

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

Вы можете использовать внедрение свойств, когда у вас есть то, что Марк Симанн называет локальным значением по умолчанию в своей книге «Внедрение зависимостей в .NET»: зависимость не является обязательной, потому что вы можете предоставить хорошо работающую реализацию, но хотите разрешить вызывающий, чтобы указать другой, если необходимо.

(Предыдущий ответ ниже)


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

Инъекция сеттера хороша, если инъекция является необязательной или если вы хотите изменить ее на полпути. Я вообще не люблю сеттеров, но это дело вкуса.

person Philippe    schedule 01.10.2009
comment
Я бы сказал, что обычно изменение инъекции на полпути - это плохой стиль (потому что вы добавляете скрытое состояние к своему объекту). Но, конечно, никаких правил без исключения ... - person sleske; 01.10.2009
comment
Да, вот почему я сказал, что мне не очень нравится сеттер ... Мне нравится конструкторский подход, поскольку тогда его нельзя изменить. - person Philippe; 01.10.2009
comment
Если при этом добавляется слишком много конструкторов, рассмотрите возможность использования фабрик вместо конструкторов. Вы в основном откладываете любые исключения времени выполнения и можете даже ошибиться и в конечном итоге застрять с реализацией локатора служб. - person MeTitus; 09.09.2016
comment
@ Марко, это мой прежний ответ, и вы правы. Если конструкторов много, я бы сказал, что класс делает слишком много вещей :-) Или рассмотрим абстрактную фабрику. - person Philippe; 12.09.2016

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

Пока интерфейс классов (API) ясно понимает, что ему нужно для выполнения своей задачи, все в порядке.

person nkr1pt    schedule 01.10.2009
comment
пожалуйста, укажите, почему вы голосуете против? - person nkr1pt; 01.10.2009
comment
Да, конструкторы с большим количеством аргументов - это плохо. Вот почему вы реорганизуете классы с большим количеством параметров конструктора :-). - person sleske; 01.10.2009
comment
@ nkr1pt: Большинство людей (включая меня) согласны с тем, что внедрение сеттера - это плохо, если оно позволяет вам создать класс, который не работает во время выполнения, если инъекция не выполняется. Я полагаю, что кто-то поэтому возражал против вашего утверждения о том, что это личный вкус. - person sleske; 01.10.2009

Я лично предпочитаю "шаблон" Извлечь и переопределить, а не внедрять зависимости в конструктор, в основном по причине, изложенной в вашем вопросе. Вы можете установить свойства как virtual, а затем переопределить реализацию в производном тестируемом классе.

person Russ Cam    schedule 01.10.2009
comment
Я думаю, что официальное название паттерна - Template Method. - person davidjnelson; 24.07.2012

Я предпочитаю внедрение конструктора, потому что оно помогает «обеспечить» требования к зависимости класса. Если он находится в c'tor, потребитель должен установить объекты для компиляции приложения. Если вы используете внедрение сеттера, они могут не знать, что у них есть проблема, до времени выполнения - и в зависимости от объекта, это может быть поздно во время выполнения.

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

person ctacke    schedule 01.10.2009

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

Я также использую внедрение свойств для установки вещей, на которые контейнер не ссылается, например, на представление ASP.NET на презентаторе, созданном с помощью контейнера.

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

person David Kiff    schedule 01.10.2009
comment
Спасибо за Ваш ответ. Конечно, популярным ответом является конструктор. Однако я думаю, что это каким-то образом нарушает инкапсуляцию. Внедрение предзависимости, класс будет объявлять и создавать экземпляры любых конкретных типов, необходимых для выполнения своей работы. Благодаря DI подклассы (и любые созданные вручную экземпляры) теперь знают, какие инструменты использует базовый класс. Вы добавляете новую зависимость, и теперь вам нужно связать экземпляр со всеми подклассами, даже если они сами не нуждаются в зависимости. - person Niall Connaughton; 01.10.2009
comment
Просто написал хороший длинный ответ и потерял его из-за исключения на этом сайте !! :( Таким образом, базовый класс обычно используется для повторного использования логики. Эта логика может довольно легко перейти в подкласс ... так что вы можете подумать о базовом классе и подклассе = одна проблема, которая зависит от нескольких внешних объектов, которые выполнять разные работы.Факт наличия зависимостей не означает, что вам нужно раскрывать все, что вы ранее хранили в секрете. - person David Kiff; 01.10.2009

Один из вариантов, который, возможно, стоит рассмотреть, - это составление сложных множественных зависимостей из простых одиночных зависимостей. То есть определите дополнительные классы для составных зависимостей. Это немного упрощает внедрение конструктора WRT - меньше параметров на вызов - при сохранении принципа обязательного предоставления всех зависимостей для создания экземпляра.

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

Лично я, однако, больше поклонник использования методов / средств установки свойств для указания зависимостей, параметров и т. Д. Имена вызовов помогают описать, что происходит. Тем не менее, неплохо было бы предоставить пример фрагментов кода, как это сделать, и убедиться, что зависимый класс выполняет достаточное количество проверок ошибок. Возможно, вы захотите использовать модель с конечным числом состояний для настройки.

person Steve314    schedule 01.10.2009

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

Использование необязательных параметров возможно только в поддерживающих их платформах, таких как .NET 4 (как для C #, так и для VB.NET, хотя они всегда были в VB.NET). Конечно, вы можете реализовать аналогичную функциональность, просто используя свойство, которое может быть переназначено потребителем вашего класса, но вы не получите преимущества неизменности, обеспечиваемой назначением объекта частного интерфейса параметру конструктора.

При всем вышесказанном, если вы вводите новую зависимость, которая должна предоставляться каждым потребителем, вам придется реорганизовать свой конструктор и весь код, который потребляет ваш класс. Мои предложения, приведенные выше, действительно применимы только в том случае, если у вас есть возможность предоставить реализацию по умолчанию для всего вашего текущего кода, но при этом при необходимости вы можете переопределить реализацию по умолчанию.

person Ben McCormack    schedule 03.08.2010

Внедрение конструктора явно выявляет зависимости, делая код более читаемым и менее подверженным необработанным ошибкам времени выполнения, если аргументы проверяются в конструкторе, но это действительно сводится к личному мнению, и чем больше вы используете DI, тем больше вы имеют тенденцию раскачиваться в ту или иную сторону в зависимости от проекта. Лично у меня проблемы с кодом, который пахнет конструкторами с длинным списком аргументов, и я чувствую, что потребитель объекта должен знать зависимости, чтобы в любом случае использовать объект, поэтому это дает повод использовать инъекцию свойств. Мне не нравится неявный характер внедрения свойств, но я считаю его более элегантным, в результате чего код выглядит более чистым. Но с другой стороны, внедрение конструктора предлагает более высокую степень инкапсуляции, и, по моему опыту, я стараюсь избегать конструкторов по умолчанию, поскольку они могут плохо повлиять на целостность инкапсулированных данных, если вы не будете осторожны.

Выбирайте инъекцию по конструктору или по свойству с умом, в зависимости от вашего конкретного сценария. И не думайте, что вам нужно использовать DI только потому, что это кажется необходимым, и это предотвратит плохой дизайн и запах кода. Иногда не стоит прилагать усилия к использованию шаблона, если усилия и сложность перевешивают пользу. Будь проще.

person David Spenard    schedule 08.07.2016

Это старый пост, но, если он понадобится в будущем, возможно, он пригодится:

https://github.com/omegamit6zeichen/prinject

У меня была похожая идея, и я придумал этот фреймворк. Вероятно, он далек от завершения, но это идея фреймворка, ориентированного на внедрение свойств.

person Markus    schedule 28.10.2016

Это зависит от того, как вы хотите реализовать. Я предпочитаю внедрение конструктора везде, где мне кажется, что значения, входящие в реализацию, меняются нечасто. Например: если стратегия compnay идет с сервером oracle, я настрою свои значения источника данных для bean-компонентов, обеспечивающих соединения, через инъекцию конструктора. В противном случае, если мое приложение является продуктом и может подключаться к любой базе данных клиента, я бы реализовал такую ​​конфигурацию базы данных и реализацию нескольких брендов с помощью инъекции установщика. Я только что привел пример, но есть более эффективные способы реализации сценариев, о которых я упоминал выше.

person ramarao Gangina    schedule 06.06.2014
comment
Даже когда кодирую внутри компании, я всегда кодирую с точки зрения того, что я независимый подрядчик, разрабатывающий код, предназначенный для распространения. Я предполагаю, что это будет открытый исходный код. Таким образом, я уверен, что код является модульным и подключаемым и следует принципам SOLID. - person Fred; 25.03.2016