Chcę zadać kilka pytań na temat najlepszych praktyk dotyczących typów mapowania i korzystania z metod rozszerzenia w języku C #. Wiem, że ten temat był omawiany wiele razy w ciągu ostatnich kilku lat, ale przeczytałem wiele postów i nadal mam wątpliwości.
Problemem, który napotkałem, było rozszerzenie posiadanej przeze mnie klasy o funkcję „konwersji”. Powiedzmy, że mam klasę „Osoba” reprezentującą obiekt, który będzie używany przez jakąś logikę. Mam również klasę „Klient”, która reprezentuje odpowiedź zewnętrznego interfejsu API (w rzeczywistości będzie więcej niż jeden interfejs API, więc muszę mapować odpowiedź każdego interfejsu API na typ typowy: Osoba). Mam dostęp do kodu źródłowego obu klas i teoretycznie mogę tam implementować własne metody. Muszę przekonwertować klienta na osobę, aby zapisać go w bazie danych. Projekt nie korzysta z żadnych automatycznych mapowań.
Mam na myśli 4 możliwe rozwiązania:
Metoda .ToPerson () w klasie Consumer. To proste, ale wydaje mi się, że przełamujesz wzorzec pojedynczej odpowiedzialności, zwłaszcza że klasa Consumer jest również odwzorowana na inne klasy (niektóre wymagane przez inny zewnętrzny interfejs API), więc musiałaby zawierać wiele metod mapowania.
Konstruktor odwzorowań w klasie Person traktuje Konsumenta jako argument. Również łatwe i wydaje się, że łamanie wzorca pojedynczej odpowiedzialności. Musiałbym mieć wiele konstruktorów odwzorowywania (ponieważ będzie klasa z innego interfejsu API, zapewniająca te same dane co konsument, ale w nieco innym formacie)
Klasa konwerterów z metodami rozszerzeń. W ten sposób mogę napisać metodę .ToPerson () dla klasy Consumer, a kiedy inny API zostanie wprowadzony z własną klasą NewConsumer, mogę po prostu napisać inną metodę rozszerzenia i zachować wszystko w tym samym pliku. Słyszałem opinię, że metody rozszerzenia są ogólnie złe i powinny być stosowane tylko wtedy, gdy jest to absolutnie konieczne, więc to mnie powstrzymuje. W przeciwnym razie podoba mi się to rozwiązanie
Klasa konwertera / mapowania. Tworzę osobną klasę, która będzie obsługiwać konwersje i implementuję metody, które przyjmą instancję klasy źródłowej jako argument i zwrócą instancję klasy docelowej.
Podsumowując, mój problem można sprowadzić do liczby pytań (wszystko w kontekście do tego, co opisałem powyżej):
Czy umieszczenie metody konwersji w obiekcie (POCO?) (Jak metoda .ToPerson () w klasie Consumer) jest rozważane jako przełamanie wzorca pojedynczej odpowiedzialności?
Czy używanie konstruktorów konwertujących w klasie (podobnej do DTO) jest uważane za przełamanie wzorca pojedynczej odpowiedzialności? Zwłaszcza jeśli taką klasę można przekonwertować z wielu typów źródeł, więc byłoby wymaganych wiele konstruktorów konwertujących?
Czy stosowanie metod rozszerzenia przy jednoczesnym dostępie do oryginalnego kodu źródłowego klasy jest uważane za złą praktykę? Czy takie zachowanie może być wykorzystane jako realny wzór do rozdzielenia logiki, czy też jest to anty-wzór?
źródło
Person
klasa jest DTO? czy zawiera jakieś zachowanie?Odpowiedzi:
Tak, ponieważ konwersja to kolejna odpowiedzialność.
Tak, konwersja to kolejna odpowiedzialność. Nie ma znaczenia, czy robisz to za pomocą konstruktorów, czy metod konwersji (np
ToPerson
.).Niekoniecznie. Możesz tworzyć metody rozszerzenia, nawet jeśli masz kod źródłowy klasy, którą chcesz rozszerzyć. Myślę, że to, czy utworzysz metodę rozszerzenia, czy nie, powinno zależeć od charakteru takiej metody. Na przykład, czy zawiera dużo logiki? Czy to zależy od czegoś innego niż członków samego obiektu? Powiedziałbym, że nie powinieneś mieć metody rozszerzenia wymagającej zależności do działania lub zawierającej złożoną logikę. W metodzie rozszerzenia powinna znajdować się tylko najprostsza logika.
Jeśli logika jest złożona, myślę, że nie powinieneś używać metody rozszerzenia. Jak zauważyłem wcześniej, powinieneś używać metod rozszerzenia tylko do najprostszych rzeczy. Nie uważałbym konwersji za prostą.
Sugeruję utworzenie usług konwersji. Możesz mieć do tego celu jeden ogólny interfejs:
I możesz mieć takie konwertery:
I możesz użyć Dependency Injection, aby wstrzyknąć konwerter (np.
IConverter<Person,Customer>
) Do dowolnej klasy, która wymaga możliwości konwersji pomiędzyPerson
iCustomer
.źródło
IConverter
w frameworku, który tylko czeka na wdrożenie.IConvertable
, czego nie szukamy tutaj. Mój błąd.Converter
jest używany przezList
podczas rozmowyConvertAll
. msdn.microsoft.com/en-us/library/kt456a2y(v=vs.110).aspx Nie wiem jednak, jak użyteczne jest to dla OP.Tak.
Consumer
Klasa jest odpowiedzialny za przechowywanie danych związanych z konsumentem (i ewentualnie wykonując pewne działania) i nie powinien być odpowiedzialny za przekształcanie się do innego, niezwiązanego typu.Prawdopodobnie. Zazwyczaj mam metody poza domeną i obiektami DTO do przeprowadzenia konwersji. Często używam repozytorium, które przyjmuje obiekty domeny i (de) serializuje je, albo do bazy danych, pamięci, pliku, cokolwiek. Jeśli zastosuję tę logikę w swoich klasach domen, teraz powiązałem je z jedną konkretną formą serializacji, która jest nieodpowiednia do testowania (i innych rzeczy). Jeśli zastosuję logikę w moich klasach DTO, przywiążę je do mojej domeny, ponownie ograniczając testowanie.
Nie ogólnie, choć można je nadużywać. Metodami rozszerzeń tworzysz opcjonalne rozszerzenia, które ułatwiają czytanie kodu. Mają wady - łatwiej jest tworzyć niejednoznaczności, które kompilator musi rozwiązać (czasem po cichu) i może utrudnić debugowanie kodu, ponieważ nie jest całkowicie oczywiste, skąd pochodzi metoda rozszerzenia.
W twoim przypadku klasa konwertera lub programu mapującego wydaje się najprostszym i najprostszym podejściem, chociaż nie sądzę, abyś robił coś złego , używając metod rozszerzenia.
źródło
Co powiesz na użycie AutoMappera lub niestandardowego mapera
z domeny
Pod maską można streścić AutoMapper lub rzucić własnego mapera
źródło
Wiem, że to stare, ale wciąż rozkoszne. Umożliwi to konwersję w obie strony. Uważam to za pomocne podczas pracy z Entity Framework i tworzeniem modeli widoków (DTO).
Uważam, że AutoMapper jest trochę za bardzo do robienia naprawdę prostych operacji mapowania.
źródło