Объект основанный на С# объекте для объекта 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>
{
...
}