Використовувати введення залежностей для об'єктів даних?


11

Я просто вчуся про ін'єкцію залежності, і я зациклююся на чомусь. Dependency Injection рекомендує надсилати залежні класи через конструктор, але мені цікаво, чи це необхідно для об'єктів даних. Оскільки Unit-Testability є однією з головних переваг 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;
}

Що саме ви маєте на увазі під «об’єктом даних»? Це не стандартний термін. Ви говорите про DTO або маєте на увазі будь-який клас без методів (наприклад, особливо нудна частина доменної моделі)? Існує величезна різниця між ними.
Aaronaught

Звичайно, я маю на увазі просто клас без методів, клас, який просто зберігає дані. Якщо Data Object не є правильним терміном для цього, чи є такий, або його просто називають класом без методів?
приспів

@sooprise Це гарне запитання. Гарне мислення.
Матвій Родатус

@sooprise Можливо, моя відповідь не є базовою. Куди ви поставите методи CRUD? У окремому класі доступу до даних, який би приймав Об'єкти даних і зберігав їх у таблиці бази даних? Тобто DataAccess.Create (<DataObject>)?
Матвій Родатус

1
@Matthew, це буде Data Access Object - якщо це насправді то , що ОП , про яку говорить, то , що не ясно , на всіх. Сучасні реалізації в будь-якому випадку мають тенденцію відходити від цієї структури, спираючись на сховища та / або одиниці роботи.
Aaronaught

Відповіді:


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 чи ні. Неможливо приймати будь-які змістовні дизайнерські рішення щодо об'єкта, який не повинен існувати в першу чергу.

Плавник.


0

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

Дійсно, яку залежність ви б тут ввели?


На інший його питання, на який я відповів, я думаю, що це питання стосується об'єкта даних із вбудованими операціями CRUD. Як / де в залежності від бази даних вводиться об'єкт даних? У методах? У конструкторі? Якимось іншим способом? Припущення полягає в тому, що залежність не слід приховувати в тілі методів - це відключає відокремлення залежності бази даних від об'єкта даних, щоб об'єкт даних міг бути перевірений одиницею.
Матвій Родатус

@Matthew Rodatus - бачу. Так, у цьому випадку у вас є два варіанти: ввести послугу стійкості або створити інший клас, який називається, DOPersisterякий вміє зберігатись, DOі залишити його як суворо лише об'єкт даних (краще, на мою думку). В останньому випадку DOPersisterвводиться залежність від бази даних.
Скотт Вітлок

Перечитавши його запитання, я менш впевнений. Мій аналіз може бути неправильним. Він сказав у своєму запитанні, що його ДО не буде мати жодних процедур. Це означало б, що наполегливість НЕ відбувається в 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())
    {
        ...
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.