Używać wstrzykiwania zależności dla obiektów danych?

11

Uczę się tylko o wstrzykiwaniu zależności i utknąłem na czymś. Dependency Injection zaleca wysyłanie klas zależnych przez konstruktor, ale zastanawiam się, czy jest to konieczne w przypadku obiektów danych. Ponieważ testowanie jednostkowe jest jedną z głównych zalet DI, czy obiekt danych, który przechowuje tylko dane, a nie jakiekolwiek procedury, kiedykolwiek byłby testowany jednostkowo, co czyni DI niepotrzebną warstwą złożoności, czy też pomaga w pokazywaniu zależności nawet z obiektami danych?

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
źródło
Co dokładnie rozumiesz przez „obiekt danych”? To nie jest standardowy termin. Czy mówisz o DTO, czy odnosisz się do dowolnej klasy bez metod (np. Szczególnie nudnej części modelu domeny)? Istnieje ogromna różnica między tymi dwoma.
Aaronaught,
Jasne, mam na myśli tylko klasę bez metod, klasę, która po prostu przechowuje dane. Jeśli obiekt danych nie jest odpowiednim terminem, czy istnieje, czy jest to po prostu klasa bez metod?
sooprise
@ sooprise To dobre pytanie. Dobre myślenie.
Matthew Rodatus,
@ sooprise Być może moja odpowiedź jest poza bazą. Gdzie umieścisz metody CRUD? W osobnej klasie dostępu do danych, która zabrałaby obiekty danych i utrwaliła je w tabeli bazy danych? Czyli DataAccess.Create (<DataObject>)?
Matthew Rodatus,
1
@Matthew, byłby to obiekt dostępu do danych - jeśli tak właśnie mówi OP, to wcale nie jest jasne. Nowoczesne wdrożenia i tak odchodzą od tego schematu, opierając się na repozytoriach i / lub jednostkach pracy.
Aaronaught

Odpowiedzi:

7

Chciałbym zasugerować wyjaśnienie niektórych terminów, których tu używasz, w szczególności „zależności” i „wstrzykiwania zależności”.

Zależność:

„Zależność” jest zazwyczaj złożonym obiektem, który wykonuje pewne funkcje, od których może zależeć inna klasa. Klasycznymi przykładami mogą być rejestrator lub moduł dostępu do bazy danych lub jakiś komponent przetwarzający określoną logikę biznesową.

Obiekt zawierający tylko dane, taki jak DTO lub obiekt wartości, nie jest zwykle określany jako „zależność”, ponieważ nie pełni on niektórych potrzebnych funkcji.

Gdy spojrzeć na to w ten sposób, co robisz w swoim przykładzie ( komponując ten DOobiekt z listy D02obiektów przez konstruktora) nie powinny być uważane za „zastrzyk dependecy” w ogóle. To tylko ustawienie nieruchomości. Od Ciebie zależy, czy podasz je w konstruktorze, czy w inny sposób, ale przekazanie go przez konstruktor nie spowoduje wstrzyknięcia zależności.

Wstrzykiwanie zależności:

Gdyby twoja DO2klasa faktycznie zapewniała dodatkowe funkcje, których DOpotrzebuje klasa, to naprawdę byłaby to zależność. W takim przypadku klasa zależna DOpowinna zależeć od interfejsu (takiego jak ILogger lub IDataAccessor), a z kolei polegać na kodzie wywołującym, który zapewnia ten interfejs (innymi słowy, „wstrzykuje” go do DOinstancji).

Wstrzyknięcie zależności w taki sposób sprawia, że DOobiekt jest bardziej elastyczny, ponieważ każdy inny kontekst może zapewnić własną implementację interfejsu do DOobiektu. (Pomyśl o testowaniu jednostkowym.)

Eric King
źródło
7

Zrobię co w mojej mocy, aby przezwyciężyć zamieszanie w pytaniu.

Przede wszystkim „obiekt danych” nie jest znaczącym terminem. Jeśli jedyną cechą charakterystyczną tego obiektu jest to, że nie ma on metod, to w ogóle nie powinien istnieć . Przydatny obiekt bez zachowania powinien pasować do co najmniej jednej z następujących podkategorii:

  • Obiekty wartości lub „rekordy” nie mają żadnej tożsamości. Powinny być typami wartości , z semantyką kopiowania na podstawie odniesienia, zakładając, że środowisko to obsługuje. Ponieważ są to stałe struktury, VO powinien być tylko typem pierwotnym lub ustaloną sekwencją elementów pierwotnych. Dlatego VO nie powinien mieć żadnych zależności ani powiązań; każdy inny niż domyślny konstruktor istniałby wyłącznie w celu zainicjowania wartości, tzn. ponieważ nie można jej wyrazić dosłownie.

  • Obiekty przesyłania danych są często mylnie mylone z obiektami wartości. DTOs zrobić mieć tożsamość, lub przynajmniej może . Jedynym celem DTO jest ułatwienie przepływu informacji z jednej domeny do drugiej. Nigdy nie mają „zależności”. Oni mogą mieć skojarzenia (czyli do tablicy lub kolekcji), ale większość ludzi woli, aby ich mieszkania. Zasadniczo są one analogiczne do wierszy danych wyjściowych zapytania do bazy danych; są to obiekty przejściowe, które zwykle wymagają utrwalenia lub serializacji i dlatego nie mogą odwoływać się do żadnych abstrakcyjnych typów, ponieważ uniemożliwiłoby to ich użycie.

  • Wreszcie obiekty dostępu do danych zapewniają opakowanie lub fasadę do bazy danych pewnego rodzaju. Te oczywiście mają zależności - zależą od połączenia z bazą danych i / lub składników trwałości. Jednak ich zależności są prawie zawsze zarządzane zewnętrznie i całkowicie niewidoczne dla dzwoniących. We wzorze Active Record jest to struktura, która zarządza wszystkim poprzez konfigurację; w starszych (dawnych jak na dzisiejsze standardy) modelach DAO można było je kiedykolwiek zbudować tylko za pomocą kontenera. Gdybym widział jeden z nich z zastrzykiem konstruktora, byłbym bardzo, bardzo zmartwiony.

Możesz być także myślenie o obiekcie podmiotu lub „obiektu biznesowego” , aw tym przypadku nie chcą wstrzykiwania zależności wsparcie, ale nie w taki sposób, że uważasz lub z powodów myślisz. Nie służy to kodowi użytkownika , lecz menedżerowi jednostki lub ORM, który po cichu wstrzykuje serwer proxy, który przechwytuje, aby robić wymyślne rzeczy, takie jak rozumienie zapytań lub leniwe ładowanie.

W takich przypadkach zwykle nie udostępnia się konstruktora do wstrzykiwania; zamiast tego wystarczy uczynić właściwość wirtualną i użyć typu abstrakcyjnego (np. IList<T>zamiast List<T>). Reszta dzieje się za kulisami i nikt nie jest mądrzejszy.

Podsumowując, powiedziałbym, że widoczny wzorzec DI zastosowany do „obiektu danych” jest niepotrzebny i prawdopodobnie nawet czerwona flaga; ale w dużej mierze wynika to z faktu, że samo istnienie obiektu jest czerwoną flagą, z wyjątkiem przypadku, gdy jest on konkretnie używany do reprezentowania danych z bazy danych. W prawie każdym innym przypadku jest to zapach kodu, zazwyczaj początki Anemicznego Modelu Domeny lub przynajmniej Poltergeist .

Powtarzać:

  1. Nie twórz „obiektów danych”.
  2. Jeśli musisz utworzyć „obiekt danych”, upewnij się, że ma on jasno określony cel . Ten cel powie ci, czy DI jest odpowiedni, czy nie. Niemożliwe jest podejmowanie znaczących decyzji projektowych dotyczących obiektu, który nie powinien istnieć w ogóle.

Płetwa.

Aaronaught
źródło
0

W twoim przykładzie DOnie ma żadnych zależności funkcjonalnych (zasadniczo dlatego, że nic nie robi). Jest zależny od konkretnego typu DO2, więc możesz chcieć wprowadzić interfejs do abstraktu DO2, aby konsument mógł zaimplementować własną konkretną implementację klasy potomnej.

Naprawdę, jaką zależność tu zastrzygłbyś?

Scott Whitlock
źródło
Na inne pytanie, na które odpowiedziałem, myślę, że pytanie dotyczy obiektu danych z wbudowanymi operacjami CRUD. Jak / gdzie wstrzykiwana jest zależność bazy danych do obiektu danych? W metodach? W konstruktorze? W jakiś inny sposób? Zakłada się, że zależności nie należy ukrywać w treści metod - co uniemożliwia oddzielenie zależności bazy danych od obiektu danych, aby obiekt danych mógł być testowany jednostkowo.
Matthew Rodatus,
@Mathew Rodatus - Rozumiem. Tak, w takim przypadku masz dwie możliwości: wstrzyknąć usługę trwałości lub utworzyć inną klasę o nazwie, DOPersisterktóra wie, jak się DOzachować, i pozostawić ją jako obiekt wyłącznie danych (moim zdaniem lepiej). W tym drugim przypadku DOPersisterzostanie wstrzyknięta zależność od bazy danych.
Scott Whitlock,
Po ponownym przeczytaniu jego pytania jestem mniej pewien. Moja analiza może być błędna. W swoim pytaniu powiedział, że jego DO nie będzie miał żadnych procedur. Oznaczałoby to, że uporczywość NIE występuje w DO. W takim przypadku Twoja odpowiedź jest prawidłowa - nie ma żadnych zależności do wstrzyknięcia.
Matthew Rodatus
0

Ponieważ jest to obiekt danych w warstwie dostępu do danych, powinien zależeć bezpośrednio od usługi bazy danych. Możesz podać Konstruktor bazy danych:

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

Ale zastrzyk nie musi być w konstruktorze. Alternatywnie można podać zależność za pomocą każdej metody CRUD. Wolę tę metodę od poprzedniej, ponieważ Twój obiekt danych nie musi wiedzieć, gdzie będzie trwał, dopóki nie będziesz musiał go utrwalić.

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

Na pewno nie chcesz ukryć konstrukcji w metodach CRUD!

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

Alternatywną opcją byłoby zbudowanie usługi DatabaseService za pomocą metody klasy nadpisywalnej.

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())
    {
        ...
    }
}

Ostatnią alternatywą jest użycie ServiceLocator w stylu singleton. Chociaż nie podoba mi się ta opcja, można ją testować jednostkowo.

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())
    {
        ...
    }
}
Matthew Rodatus
źródło