Кращі практики щодо методів відображення та розширення типів


15

Я хочу задати кілька питань щодо найкращих практик щодо типів відображення та використання методів розширення в C #. Я знаю, що ця тема обговорювалася неодноразово за останні кілька років, але я прочитав багато публікацій і все ще сумніваюся.

Проблема, з якою я зіткнулася, - це розширення класу, яким я володію, з функцією "конвертувати". Скажімо, у мене є клас "Person", який представляє об'єкт, який буде використовуватися певною логікою. У мене також є клас "Клієнт", який представляє відповідь із зовнішнього API (насправді буде більше одного API, тому мені потрібно зіставити відповіді кожного API на загальний тип: Person). Я маю доступ до вихідного коду обох класів і теоретично можу реалізувати там свої власні методи. Мені потрібно конвертувати Клієнта в Особу, щоб я міг зберегти його в базі даних. У проекті не використовуються автоматичні картографи.

Маю на увазі 4 можливих рішення:

  1. Метод .ToPerson () у класі споживача. Це просто, але здається, що для мене порушується схема єдиної відповідальності, тим більше, що клас споживача також відображається в інших класах (деяких вимагає інший зовнішній API), тому він повинен містити кілька методів відображення.

  2. Конструктор картографування у класі Person, що сприймає споживача як аргумент. Також легко, а також здається, що порушується схема єдиної відповідальності. Мені потрібно мати декілька конструкторів відображення (оскільки буде клас з іншого API, який надає ті самі дані, що і споживач, але у дещо іншому форматі)

  3. Клас перетворювачів із методами розширення. Таким чином я можу написати .ToPerson () метод для споживчого класу, і коли інший API вводиться з його власним класом NewConsumer, я можу просто написати інший метод розширення і зберегти все в одному файлі. Я чув думку, що методи розширення загалом є злими, і їх слід застосовувати лише в разі крайньої необхідності, тому це мене стримує. Інакше мені подобається це рішення

  4. Конвертор / клас Mapper. Я створюю окремий клас, який буде обробляти перетворення та реалізовувати методи, які будуть приймати екземпляр класу джерела як аргумент і повертати екземпляр класу призначення.

Підводячи підсумок, моя проблема може бути зведена до кількості питань (все в контексті того, що я описав вище):

  1. Чи вважається розміщення методу перетворення всередині об'єкта (POCO?) (Наприклад, методу .ToPerson () у споживчому класі) порушенням однієї структури відповідальності?

  2. Чи вважається використання конструкторів перетворення в (схожий на DTO) розрив єдиної моделі відповідальності? Особливо, якщо такий клас можна перетворити з декількох типів джерела, тож знадобиться кілька конструкторів, що перетворюють?

  3. Чи вважається використання методів розширення під час доступу до вихідного вихідного коду класу поганою практикою? Чи може така поведінка використовуватись як життєздатний зразок для розділення логіки чи це антидіаграма?


Чи Personклас DTO? чи містить вона якусь поведінку?
Якуб Массад

Наскільки я знаю, це технічно DTO. Він містить деяку логіку, "введену" як методи розширення, але ця логіка обмежена типом методів "ConvertToThatClass". Це всі застарілі методи. Моя робота полягає у впровадженні нової функціональності, яка використовує деякі з цих класів, але мені сказали, що я не повинен дотримуватися поточного підходу, якщо це погано, просто підтримувати його послідовно. Тож мені цікаво, чи підходить цей існуючий підхід до перетворення методів розширення і чи варто дотримуватися цього, або спробувати щось інше
emsi

Відповіді:


11

Чи вважається розміщення методу перетворення всередині об'єкта (POCO?) (Наприклад, методу .ToPerson () у споживчому класі) порушенням однієї структури відповідальності?

Так, тому що конверсія - це інша відповідальність.

Чи вважається використання конструкторів перетворення в (схожий на DTO) розрив єдиної моделі відповідальності? Особливо, якщо такий клас можна перетворити з декількох типів джерела, тож знадобиться кілька конструкторів, що перетворюють?

Так, конверсія - це ще одна відповідальність. Це не має значення, якщо ви робите це за допомогою конструкторів або методів перетворення (наприклад ToPerson).

Чи вважається використання методів розширення під час доступу до вихідного вихідного коду класу поганою практикою?

Не обов'язково. Ви можете створити методи розширення, навіть якщо у вас є вихідний код класу, який ви хочете розширити. Я думаю, що ви створюєте метод розширення чи ні, слід визначати характер такого методу. Наприклад, чи містить вона багато логіки? Чи залежить від чого іншого, ніж самі члени об’єкта? Я б сказав, що у вас не повинно бути методу розширення, який вимагає роботи залежностей або містить складну логіку. У методі розширення має міститися лише найпростіша логіка.

Чи може така поведінка використовуватись як життєздатний зразок для розділення логіки чи це антидіаграма?

Якщо логіка складна, то я думаю, що ви не повинні використовувати метод розширення. Як я вже зазначав, слід використовувати методи розширення лише для найпростіших речей. Я б не вважав перетворення простим.

Я пропоную вам створити служби перетворення. Ви можете мати єдиний загальний інтерфейс для нього так:

public interface IConverter<TSource,TDestination>
{
    TDestination Convert(TSource source_object);
}

І ви можете мати такі перетворювачі:

public class PersonToCustomerConverter : IConverter<Person,Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

І ви можете використовувати Dependency Injection для введення конвертора (наприклад IConverter<Person,Customer>) у будь-який клас, який потребує можливості перетворення між Personі Customer.


Якщо я не помиляюсь, IConverterв рамках вже є рамки, які просто чекають їх впровадження.
RubberDuck

@RubberDuck, де він існує?
Якуб Массад

Я думав IConvertable, що це НЕ то , що ми шукаємо тут. Моя помилка.
RubberDuck

А-а-а! Я знайшов те, що думав про @YacoubMassad. Converterвикористовується Listпри дзвінку ConvertAll. msdn.microsoft.com/en-us/library/kt456a2y(v=vs.110).aspx Я не знаю, наскільки корисно це для OP.
RubberDuck

Також актуально. Хтось ще скористався запропонованим тут підходом. codereview.stackexchange.com/q/51889/41243
RubberDuck

5

Чи .ToPerson()вважається розміщення методу перетворення всередині об'єкта (POCO?) (Як метод у класі споживачів) порушенням єдиної моделі відповідальності?

Так. ConsumerКлас відповідає за проведення даних , що стосуються замовників (і , можливо , виконуючи деякі дії) і не несе відповідальності за перетворення себе в інший, не пов'язаний тип.

Чи вважається використання конструкторів перетворення в (схожий на DTO) розрив єдиної моделі відповідальності? Особливо, якщо такий клас можна перетворити з декількох типів джерела, тож знадобиться кілька конструкторів, що перетворюють?

Ймовірно. Як правило, у мене є методи, що знаходяться поза об'єктами домену та DTO для перетворення. Я часто використовую сховище, яке займає доменні об’єкти і (де) їх серіалізує, або до бази даних, пам'яті, файлів і будь-чого іншого. Якщо я вклав цю логіку в свої доменні класи, тепер я прив’язав їх до однієї конкретної форми серіалізації, яка погана для тестування (та інших речей). Якщо я вкладу логіку в мої класи DTO, то я прив’язав їх до свого домену, знову обмежуючи тестування.

Чи вважається використання методів розширення під час доступу до вихідного вихідного коду класу поганою практикою?

Не взагалі, хоча ними можна зловживати. За допомогою методів розширення ви створюєте необов'язкові розширення, які полегшують читання коду. У них є недоліки - простіше створити неоднозначності, які компілятор повинен вирішувати (іноді беззвучно), і може ускладнити код налагодження, оскільки не зовсім очевидно, звідки походить метод розширення.

У вашому випадку клас перетворювача або картографування здається найпростішим, найпростішим підходом, хоча я не думаю, що ви робите щось не так , використовуючи методи розширення.


2

Як щодо використання AutoMapper або спеціального картографування можна використовувати Like

MyMapper
   .CreateMap<Person>()
   .To<PersonViewModel>()
   .Map(p => p.Name, vm => vm.FirstName)
   .To<SomeDTO>()
   .Map(...);

з домену

 db.Persons
   .ToListAsync()
   .Map<PersonViewModel>();

Під капотом ви можете абстрагувати AutoMapper або згорнути власний картограф


2

Я знаю, що це по-старому, але все ще шанобливо. Це дозволить конвертувати обидва способи. Я вважаю це корисним під час роботи з Entity Framework та створення моделей перегляду (DTO).

public interface IConverter<TSource, TDestination>
{
    TDestination Convert(TSource source_object);
    TSource Convert(TDestination source_object);
}

public class PersonCustomerConverter : IConverter<Person, Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
    public Person Convert(Customer source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

Я вважаю, що AutoMapper трохи допомагає робити дійсно прості операції з картографуванням.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.