Лучшие практики относительно отображения типов и методов расширения


15

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

Проблема, с которой я столкнулся, заключалась в расширении принадлежащего мне класса с помощью функции «convert». Допустим, у меня есть класс «Person», который представляет объект, который будет использоваться некоторой логикой. У меня также есть класс «Клиент», который представляет ответ от внешнего API (на самом деле будет более одного API, поэтому мне нужно сопоставить ответ каждого API с общим типом: Person). У меня есть доступ к исходному коду обоих классов, и теоретически я могу реализовать свои собственные методы там. Мне нужно преобразовать Customer в Person, чтобы я мог сохранить его в базе данных. Проект не использует автоматических картографов.

Я имею в виду 4 возможных решения:

  1. Метод .ToPerson () в классе Consumer. Это просто, но мне кажется, что я нарушаю шаблон Single Responsibility, особенно в том, что класс Consumer сопоставлен с другими классами (некоторые требуются другим внешним API), поэтому он должен содержать несколько методов отображения.

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

  3. Класс преобразователей с методами расширения. Таким образом, я могу написать метод .ToPerson () для класса Consumer, и когда другой API-интерфейс представлен с собственным классом NewConsumer, я могу просто написать другой метод расширения и сохранить его в одном файле. Я слышал мнение, что методы расширения в целом являются злыми и должны использоваться только в случае крайней необходимости, так что это сдерживает меня. В противном случае мне нравится это решение

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

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

  1. Считается ли помещение метода преобразования внутри (POCO?) Объекта (например, метода .ToPerson () в классе Consumer) нарушением единого шаблона ответственности?

  2. Считается ли использование конвертирующих конструкторов в классе (DTO-подобном) нарушением единого шаблона ответственности? Особенно, если такой класс может быть преобразован из нескольких типов источников, поэтому потребуется несколько конструкторов преобразования?

  3. Считается ли использование методов расширения при доступе к исходному коду исходного кода плохой практикой? Может ли такое поведение использоваться в качестве жизнеспособного шаблона для разделения логики или это анти-шаблон?


Является ли Personкласс DTO? это содержит какое-либо поведение?
Якуб Массад

Насколько я знаю, это технически DTO. Он содержит некоторую логику, «внедренную» как методы расширения, но эта логика ограничена методами типа «ConvertToThatClass». Это все устаревшие методы. Моя работа состоит в том, чтобы реализовать новые функциональные возможности, использующие некоторые из этих классов, но мне сказали, что я не должен придерживаться текущего подхода, если он плох, просто поддерживать его согласованность. Поэтому мне интересно, является ли этот существующий подход с преобразованием с использованием методов расширения хорошим, и если я должен придерживаться его, или попробовать что-то еще
emsi

Ответы:


11

Считается ли помещение метода преобразования внутри (POCO?) Объекта (например, метода .ToPerson () в классе Consumer) нарушением единого шаблона ответственности?

Да, потому что обращение - другая ответственность.

Считается ли использование конвертирующих конструкторов в классе (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) нарушением единого шаблона ответственности?

Да. ConsumerКласс отвечает за проведение данных , связанные с потребителем (и , возможно , выполняя некоторые действия) и не должно нести ответственности за превращение себя в другой, не связанный тип.

Считается ли использование конвертирующих конструкторов в классе (DTO-подобном) нарушением единого шаблона ответственности? Особенно, если такой класс может быть преобразован из нескольких типов источников, поэтому потребуется несколько конструкторов преобразования?

Наверное. У меня обычно есть методы вне обоих доменов и объектов DTO, чтобы сделать преобразование. Я часто использую репозиторий, который принимает объекты домена и (де) сериализует их, либо в базу данных, в память, в файл, что угодно. Если я поместил эту логику в классы своего домена, то теперь я привязал их к одной конкретной форме сериализации, которая плохо подходит для тестирования (и других вещей). Если я добавлю логику в мои классы DTO, то я привяжу их к своему домену, снова ограничивая тестирование.

Считается ли использование методов расширения при доступе к исходному коду исходного кода плохой практикой?

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

В вашем случае класс конвертера или преобразователя кажется самым простым и простым подходом, хотя я не думаю, что вы делаете что-то неправильно , используя методы расширения.


2

Как насчет использования AutoMapper или можно использовать пользовательский картограф

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.