2009-10-01 4 views
136

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

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

ответ

117

Ну, это зависит :-).

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

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

+9

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

+3

@Mark: Хорошая точка. Однако речь шла о добавлении зависимости к существующему классу. Тогда сохранение конструктора no-arg допускает обратную совместимость. – sleske

+0

Как насчет того, когда зависимость должна функционировать, но стандартная инъекция этой зависимости обычно будет достаточной. Тогда должна ли эта зависимость «переопределяться» по свойству или перегрузке конструктора? –

6

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

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

+0

, пожалуйста, укажите, почему вы сбрасываете воду? – nkr1pt

+3

Да, конструкторы с большим количеством аргументов плохи. Вот почему вы рефакторируете классы с множеством параметров конструктора :-). – sleske

+10

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

6

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

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

11

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

17

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

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

+0

Плюс к этому. Кроме того, это чрезвычайно снижает опасность круговых зависимостей ... – gilgamash

6

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

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

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

+0

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

+0

Просто написал хороший длинный ответьте и потеряли его из-за исключения на этом сайте! :(Таким образом, базовый класс обычно используется для повторного использования логики. Эта логика вполне может попасть в подкласс ... так что вы можете думать о базовом классе и подклассе = одна проблема, которая зависит от нескольких внешних объектов, что –

7

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

+1

Я думаю, что официальное название шаблона - «Метод шаблона». – davidjnelson

11

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

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

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

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

(бывший ответ ниже)


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

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

+0

Я бы сказал, что обычно меняя инъекцию наполовину, я бы сказал, что я не знаю, как это сделать. через плохой стиль (потому что вы добавляете скрытое состояние к своему объекту). Но ни одно правило без исключения конечно ... – sleske

+0

Да, почему я сказал, что мне не очень нравится сеттер ... Мне нравится конструктор потому что тогда его нельзя изменить. – Philippe

+0

«Если это добавляет слишком много конструкторов, подумайте о том, чтобы использовать фабрики вместо конструкторов». Вы в основном откладываете исключения во время выполнения и даже можете ошибиться и завершить застревание с реализацией локатора сервиса. – Marco

19

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

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

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. 
    } 
} 

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

+0

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

+0

Как правило, вы использовали бы то, что вы использовали в методе до инъекции зависимостей, в качестве параметра по умолчанию. В идеальном случае это добавит новый конструктор к чистому рефакторингу, поскольку поведение класса не изменится. – Scott

+1

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

3

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

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

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

3

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

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

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

0

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

+1

Даже при кодировании внутри, я всегда кодирую с точки зрения, что я независимый подрядчик, разрабатывающий код, предназначенный для распространения. Я предполагаю, что это будет открытый исходный код. Таким образом, я уверен, что код является модульным и подключаемым и следует принципам SOLID. – Fred

0

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

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

0

Это старый пост, но если это необходимо, в будущем, может быть, это никакой пользы:

https://github.com/omegamit6zeichen/prinject

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

Смежные вопросы