Использовать внедрение зависимостей для объектов данных?

11

Я только учусь внедрению зависимости, и застрял на чем-то. Внедрение зависимостей рекомендует отправлять зависимые классы через конструктор, но мне интересно, нужно ли это для объектов данных. Поскольку модульная тестируемость является одним из основных преимуществ 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;
}
sooprise
источник
Что именно вы подразумеваете под «объектом данных»? Это не стандартный термин. Вы говорите о DTO или имеете в виду какой-то класс без методов (например, особенно скучную часть модели предметной области)? Существует огромная разница между ними.
Aaronaught
Конечно, я имею в виду просто класс без методов, класс, который просто хранит данные. Если Data Object не подходит для этого, существует ли он или просто называется классом без методов?
сюрприз
@sooprise Это хороший вопрос. Хорошая мысль.
Мэтью Родат
@sooprise Возможно, мой ответ не на базе. Где вы положите методы CRUD? В отдельном классе доступа к данным, который будет принимать объекты данных и сохранять их в таблице базы данных? Т.е. DataAccess.Create (<DataObject>)?
Мэтью Родат
1
@Matthew, это будет Data Access Object - если это на самом деле то , что ОП , о которой говорит, то , что не ясно , на всех. Современные реализации в любом случае имеют тенденцию отходить от этого шаблона, полагаясь на репозитории и / или единицы работы.
Ааронаут

Ответы:

7

Я хотел бы предложить разъяснение некоторых терминов, которые вы используете здесь, в частности, «зависимость» и «внедрение зависимости».

Зависимость:

«Зависимость» обычно представляет собой сложный объект, который выполняет некоторые функции, от которых может потребоваться другой класс. Некоторыми классическими примерами могут быть регистратор или средство доступа к базе данных или какой-либо компонент, который обрабатывает определенный фрагмент бизнес-логики.

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

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

Внедрение зависимости:

Если бы ваш DO2класс на самом деле предоставлял некоторую дополнительную функциональность, которая ему DOнеобходима, то это действительно была бы зависимость. В этом случае зависимый класс DOдолжен зависеть от интерфейса (например, ILogger или IDataAccessor) и, в свою очередь, полагаться на вызывающий код для предоставления этого интерфейса (другими словами, для «внедрения» его в DOэкземпляр).

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

Эрик Кинг
источник
7

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

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

  • Объекты значений или «записи» вообще не имеют идентичности. Они должны быть типами значений с семантикой копирования по ссылке, предполагая, что среда поддерживает это. Поскольку это фиксированные структуры, VO должен быть только примитивного типа или фиксированной последовательности примитивов. Следовательно, у ВО не должно быть никаких зависимостей или ассоциаций; любой конструктор не по умолчанию будет существовать исключительно с целью инициализации значения, то есть потому, что оно не может быть выражено как литерал.

  • Объекты передачи данных часто ошибочно путают с объектами значений. DTOS делать есть тождества, или , по крайней мере , они могут . Единственная цель DTO - облегчить поток информации из одного домена в другой. У них никогда не бывает «зависимостей». Они могут иметь ассоциации (то есть с массивом или коллекцией), но большинство людей предпочитают делать их плоскими. По сути, они аналогичны строкам в выводе запроса к базе данных; это временные объекты, которые обычно необходимо сохранять или сериализовать, и поэтому они не могут ссылаться на какие-либо абстрактные типы, так как это сделает их непригодными для использования.

  • Наконец, объекты доступа к данным предоставляют оболочку или фасад для какой-либо базы данных. Очевидно, что у них есть зависимости - они зависят от соединения с базой данных и / или постоянных компонентов. Тем не менее, их зависимости почти всегда управляются извне и абсолютно невидимы для вызывающих. В паттерне Active Record это фреймворк, который управляет всем через конфигурацию; в более старых (древних по сегодняшним меркам) моделях DAO их можно было создать только через контейнер. Если бы я видел один из них с помощью инжектора конструктора, я бы очень, очень переживал.

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

В них вы обычно не предоставляете конструктор для внедрения; вместо этого вам просто нужно сделать свойство виртуальным и использовать абстрактный тип (например, IList<T>вместо List<T>). Остальное происходит за кулисами, и никто не мудрее.

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

Чтобы повторить:

  1. Не создавайте «объекты данных».
  2. Если вам необходимо создать «объект данных», убедитесь, что он имеет четко определенную цель . Эта цель скажет вам, подходит ли DI. Невозможно принимать какие-либо конструктивные решения относительно объекта, который не должен существовать в первую очередь.

Плавник.

Aaronaught
источник
0

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

Действительно, какую зависимость вы бы здесь вводили?

Скотт Уитлок
источник
На другой его вопрос, на который я ответил, я думаю, что этот вопрос относится к объекту данных со встроенными операциями CRUD. Как / где зависимость от базы данных вводится в объект данных? В методах? В конструкторе? Как-то иначе? Предполагается, что зависимость не должна быть скрыта в теле методов - это отключает отделение зависимости базы данных от объекта данных, чтобы объект данных мог быть подвергнут модульному тестированию.
Мэтью Родат
@ Мэтью Родатус - понятно. Да, в этом случае у вас есть два варианта: внедрить службу персистентности или создать другой класс с именем, DOPersisterкоторый знает, как сохранить DOи оставить его как объект, исключительно для данных (на мой взгляд, лучше). В последнем случае DOPersisterбудет введена зависимость от базы данных.
Скотт Уитлок
Перечитав его вопрос, я менее уверен. Мой анализ может быть неверным. В своем вопросе он сказал, что в его DO не будет никаких процедур. Это будет означать, что настойчивость не происходит в DO. В этом случае ваш ответ правильный - нет никаких зависимостей для внедрения.
Мэтью Родатус
0

Поскольку это объект данных на уровне доступа к данным, он должен напрямую зависеть от службы базы данных. Вы можете указать DatabaseService для конструктора:

DataObject dataObject = new DataObject(new DatabaseService());
dataObject.Update();

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

DataObject dataObject = new DataObject();
dataObject.Update(new DatabaseService());

Вы определенно не хотите скрывать конструкцию в методах CRUD!

public void Update()
{
    // DON'T DO THIS!
    using (DatabaseService dbService = new DatabaseService())
    {
        ...
    }
}

Альтернативным вариантом будет создание DatabaseService с помощью переопределенного метода класса.

public void Update()
{
    // GetDatabaseService() is protected virtual, so in unit testing
    // you can subclass the Data Object and return your own
    // MockDatabaseService.
    using (DatabaseService dbService = GetDatabaseService())
    {
        ...
    }
}

Последней альтернативой является использование ServiceLocator в стиле синглтона. Хотя мне не нравится эта опция, она тестируется на единицу.

public void Update()
{
    // The ServiceLocator would not be a real singleton. It would have a setter
    // property so that unit tests can swap it out with a mock implementation
    // for unit tests.
    using (DatabaseService dbService = ServiceLocator.GetDatabaseService())
    {
        ...
    }
}
Мэтью Родатус
источник