C # generics основанный на объекте объект маппер вопрос - PullRequest
13 голосов
/ 13 мая 2010

У меня есть потребность в сопоставлении объектов в моем приложении. Я опробовал несколько, но не смог найти ничего, что соответствует моим потребностям, поэтому я пишу свои собственные. В настоящее время у меня есть интерфейс, как показано ниже:

public interface IMapper<T, R> {
    T Map(R obj);
}

Затем я реализую AccountMapper, который сопоставляет Клиента с Учетной записью как:

public class AccountMapper : IMapper<Account, Customer> {
    Account Map(Customer obj) {
        // mapping code
    }
}

Пока это работает нормально, однако у меня есть несколько исходных объектов, которые отображаются на один и тот же объект назначения. Например, у меня есть Платеж и Счет, которые оба сопоставляются с BillHistory. Чтобы вышеупомянутое поддержало это, мне нужно сделать два отдельных преобразователя (то есть. BillHistoryPaymentMapper и BillHistoryInvoiceMapper), что хорошо. Тем не менее, я хотел бы иметь возможность реализовать это немного по-другому, как показано ниже. Единственная проблема в том, что я не знаю, возможно ли это, и если да, я не знаю правильный синтаксис.

public interface IMapper<T> {
    T Map<R>(R obj);
}

public class BillHistoryMapper : IMapper<Account> {
    public BillHistory Map<Invoice>(Invoice obj) {
        // mapping code
    }
    public BillHistory Map<Payment>(Payment obj) {
        // mapping code
    }
}

Хотя первая реализация работает нормально, вторая будет немного более элегантной. Возможно ли это, и если да, то как будет выглядеть правильный синтаксис?

редактировать -------

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

public class BillHistoryMapper : Mapper<BillHistory, Invoice> {
}

где Mapper содержит:

public abstract class Mapper<T, R> : IMapper<T, R> {
    public IList<T> Map(IList<R> objList) {
        return objList.ToList<R>().ConvertAll<T>(new Converter<T, R>(Map));
    }
}

Ответы [ 4 ]

4 голосов
/ 13 мая 2010

Вы должны будете использовать свой первый интерфейс и реализовать интерфейс несколько раз на вашем объекте:

public class BillHistoryMapper : IMapper<Account, Invoice>, 
                                 IMapper<Account, Payment> {     
   ...
}

Я бы серьезно подумал о том, чтобы взглянуть на AutoMapper вместо того, чтобы писать свой собственный. Есть много нюансов в отображении, которые он уже решил, не говоря уже о том, что он прошел через множество тестов производительности, исправлений ошибок и т. Д.

1 голос
/ 29 июня 2010

Что касается вашего абстрактного класса, рассмотрите возможность избавиться от него и заменить его на метод расширения.Это позволит вам использовать функцию MapAll независимо от того, реализуете ли вы интерфейс или используете какую-то цепочку наследования.

public static class MapperExtensions
{
    public static IEnumerable<TOutput> MapAll<TInput, TOutput>
        (this IMapper<TInput, TOutput> mapper, IEnumerable<TInput> input)
    {
        return input.Select(x => mapper.Map(x));
    }
}

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

public class BillHistoryMapper :
    IMapper<Invoice, BillHistory>, IMapper<Payment, BillHistory>
{
    public BillHistory Map<Invoice>(Invoice obj) {}
    public BillHistory Map<Payment>(Payment obj) {}
}

Также рассмотрите возможность изменения общих параметров IMapper, чтобы они были наоборот (Iвзял на себя смелость в предыдущих примерах):

public interface IMapper<in TInput, out TOutput>
{
    TOutput Map(TInput input);
}

Причина этого в том, что он напрямую отображается на делегат System.Converter<T>, и вы можете сделать что-то вроде:

IMapper<ObjectA, ObjectB> myAToBMapper = new MyAToBMapper();

ObjectA[] aArray = { new ObjectA(), new ObjectA() };
ObjectB[] bArray = Array.ConvertAll<ObjectA, ObjectB>(aArray, myAToBMapper.Map);

List<ObjectA> aList = new List<ObjectA> { new ObjectA(), new ObjectA() };
List<ObjectB> bList = aList.ConvertAll<ObjectB>(myAToBMapper.Map);

// Or

var aToBConverter = new Converter<ObjectA, ObjectB>(myAToBMapper.Map);
bArray = Array.ConvertAll(aArray, aToBConverter);
bList = aList.ConvertAll(aToBConverter);

AutoMapper также было предложено, что сделает вашу жизнь проще.Однако, если вы хотите сохранить абстракцию сопоставления и иметь независимый от кода код для вашей стратегии сопоставления, очень легко использовать вышеуказанный интерфейс для внедрения оболочки вокруг AutoMapper.Это также будет означать, что вы можете продолжать использовать метод расширения MapAll, описанный выше.

public class AutoMapperWrapper<in TInput, out TOutput> : IMapper<TInput, TOutput>
{
    public TOutput Map(TInput input)
    {
        return Mapper.Map<TOutput>(input);
    }
}

Последнее слово

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

0 голосов
/ 13 мая 2010

Если существует ограниченное число типов, из которых вы хотите отобразить, то я бы использовал первый метод объявления типов ввода и вывода в определении интерфейса. Тогда картограф может реализовать интерфейсы для каждого поддерживаемого им типа ввода, поэтому ваш BillHistoryMapper будет объявлен как:

public class BillHistoryMapper : IMapper<BillHistory, Invoice>, IMapper<BillHistory, Payment>
{
   ...
}
0 голосов
/ 13 мая 2010

Ваш второй пример будет работать только с несколькими изменениями:

// you have to include the R type in the declaration of the Mapper interface
public interface IMapper<T, R> {
    T Map<R>(R obj);
}

// You have to specify both IMapper implementations in the declaration
public class BillHistoryMapper : IMapper<Account, Invoice>, IMapper<Account, Payment> {
    public BillHistory Map<Invoice>(Invoice obj) {
        // mapping code
    }
    public BillHistory Map<Payment>(Payment obj) {
        // mapping code
    }
}

Я не уверен, что это на самом деле выигрывает вас по сравнению с существующим паттерном.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...