Я только учусь внедрению зависимости, и застрял на чем-то. Внедрение зависимостей рекомендует отправлять зависимые классы через конструктор, но мне интересно, нужно ли это для объектов данных. Поскольку модульная тестируемость является одним из основных преимуществ DI, будет ли объект данных, который хранит только данные, а не какие-либо процедуры, когда-либо подвергаться модульному тестированию, что делает DI ненужным слоем сложности или же помогает показывать зависимости даже с объектами данных?
Class DO{
DO(){
DataObject2List = new List<DO2>();
}
public string Field1;
public string Field2;
public List<DO2> DataObject2List;
}
Class DO2{
public DateTime Date;
public double Value;
}
c#
dependency-injection
class-design
sooprise
источник
источник
Ответы:
Я хотел бы предложить разъяснение некоторых терминов, которые вы используете здесь, в частности, «зависимость» и «внедрение зависимости».
Зависимость:
«Зависимость» обычно представляет собой сложный объект, который выполняет некоторые функции, от которых может потребоваться другой класс. Некоторыми классическими примерами могут быть регистратор или средство доступа к базе данных или какой-либо компонент, который обрабатывает определенный фрагмент бизнес-логики.
Объект только для данных, такой как DTO или объект значения, обычно не называют «зависимостью», поскольку они не выполняют какой-либо необходимой функции.
После того, как вы смотрите на это таким образом, что вы делаете в вашем примере ( составляя в
DO
объект со спискомD02
объектов через конструктор) не следует рассматривать как «инъекции зависимых пакетов » на всех. Это просто установка свойства. Вам решать, предоставите ли вы его в конструкторе или каким-либо другим способом, но простая передача его через конструктор не сделает это внедрением зависимости.Внедрение зависимости:
Если бы ваш
DO2
класс на самом деле предоставлял некоторую дополнительную функциональность, которая емуDO
необходима, то это действительно была бы зависимость. В этом случае зависимый классDO
должен зависеть от интерфейса (например, ILogger или IDataAccessor) и, в свою очередь, полагаться на вызывающий код для предоставления этого интерфейса (другими словами, для «внедрения» его вDO
экземпляр).Внедрение зависимости таким образом делает
DO
объект более гибким, поскольку каждый отдельный контекст может обеспечить свою собственную реализацию интерфейса дляDO
объекта. (Подумайте, юнит-тестирование.)источник
Я сделаю все возможное, чтобы преодолеть путаницу в вопросе.
Прежде всего, «Объект данных» не является значимым термином. Если единственной определяющей характеристикой этого объекта является то, что у него нет методов, то он вообще не должен существовать . Полезный объект без поведения должен входить хотя бы в одну из следующих подкатегорий:
Объекты значений или «записи» вообще не имеют идентичности. Они должны быть типами значений с семантикой копирования по ссылке, предполагая, что среда поддерживает это. Поскольку это фиксированные структуры, VO должен быть только примитивного типа или фиксированной последовательности примитивов. Следовательно, у ВО не должно быть никаких зависимостей или ассоциаций; любой конструктор не по умолчанию будет существовать исключительно с целью инициализации значения, то есть потому, что оно не может быть выражено как литерал.
Объекты передачи данных часто ошибочно путают с объектами значений. DTOS делать есть тождества, или , по крайней мере , они могут . Единственная цель DTO - облегчить поток информации из одного домена в другой. У них никогда не бывает «зависимостей». Они могут иметь ассоциации (то есть с массивом или коллекцией), но большинство людей предпочитают делать их плоскими. По сути, они аналогичны строкам в выводе запроса к базе данных; это временные объекты, которые обычно необходимо сохранять или сериализовать, и поэтому они не могут ссылаться на какие-либо абстрактные типы, так как это сделает их непригодными для использования.
Наконец, объекты доступа к данным предоставляют оболочку или фасад для какой-либо базы данных. Очевидно, что у них есть зависимости - они зависят от соединения с базой данных и / или постоянных компонентов. Тем не менее, их зависимости почти всегда управляются извне и абсолютно невидимы для вызывающих. В паттерне Active Record это фреймворк, который управляет всем через конфигурацию; в более старых (древних по сегодняшним меркам) моделях DAO их можно было создать только через контейнер. Если бы я видел один из них с помощью инжектора конструктора, я бы очень, очень переживал.
Вы также можете думать о объекте сущности или «бизнес - объекте» , и в этом случае вы действительно хотите инъекцию поддержки зависимостей, но не в том смысле , что вы думаете , или по причинам вы думаете. Это не в пользу пользовательского кода , а в интересах менеджера сущностей или ORM, который будет молча вводить прокси-сервер, который он перехватывает, чтобы делать такие причудливые вещи, как понимание запросов или ленивая загрузка.
В них вы обычно не предоставляете конструктор для внедрения; вместо этого вам просто нужно сделать свойство виртуальным и использовать абстрактный тип (например,
IList<T>
вместоList<T>
). Остальное происходит за кулисами, и никто не мудрее.В общем, я бы сказал, что видимый шаблон DI, применяемый к «объекту данных», не нужен и, возможно, даже красный флаг; но в значительной степени это происходит потому, что само существование объекта является красным флагом, за исключением случая, когда он специально используется для представления данных из базы данных. Почти во всех других случаях это запах кода, как правило, начало модели анемичной области или, по крайней мере, полтергейста .
Чтобы повторить:
Плавник.
источник
В вашем примере
DO
не имеет никаких функциональных зависимостей (в основном потому, что он ничего не делает). Он зависит от конкретного типаDO2
, поэтому вы можете захотеть ввести интерфейс для абстрагированияDO2
, чтобы потребитель мог реализовать собственную конкретную реализацию дочернего класса.Действительно, какую зависимость вы бы здесь вводили?
источник
DOPersister
который знает, как сохранитьDO
и оставить его как объект, исключительно для данных (на мой взгляд, лучше). В последнем случаеDOPersister
будет введена зависимость от базы данных.Поскольку это объект данных на уровне доступа к данным, он должен напрямую зависеть от службы базы данных. Вы можете указать DatabaseService для конструктора:
Но инъекция не должна быть в конструкторе. Кроме того, вы можете предоставить зависимость через каждый метод CRUD. Я предпочитаю этот метод предыдущему, потому что ваш объект данных не должен знать, где он будет сохраняться, пока вам действительно не нужно его сохранить.
Вы определенно не хотите скрывать конструкцию в методах CRUD!
Альтернативным вариантом будет создание DatabaseService с помощью переопределенного метода класса.
Последней альтернативой является использование ServiceLocator в стиле синглтона. Хотя мне не нравится эта опция, она тестируется на единицу.
источник