Объект основанный на С# объекте для объекта mapper question

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

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));
    }
}

Ответ 1

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

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

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

Ответ 2

Что касается вашего абстрактного класса, подумайте о том, чтобы избавиться от него и заменить его на метод расширения. Это позволит вам использовать функцию 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 в обратном порядке (я взял на себя смелость в предыдущих примерах):

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);
    }
}

Заключительное слово

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

Ответ 3

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

// 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
    }
}

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

Ответ 4

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

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