У меня есть потребность в объекте возразить картопостроителю в моем приложении. Я испытал некоторых, но не смог найти что-либо, что соответствует моим потребностям, таким образом, я пишу свое собственное. В настоящее время у меня есть интерфейс как ниже:
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> {
}
где Картопостроитель содержит:
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));
}
}
Вам придется использовать свой первый интерфейс и реализовать его несколько раз на вашем объекте:
public class BillHistoryMapper : IMapper<Account, Invoice>,
IMapper<Account, Payment> {
...
}
Я бы серьезно подумал о том, чтобы взглянуть на AutoMapper вместо того, чтобы писать свой собственный. Есть много нюансов в маппинге, которые он уже решил, не говоря уже о том, что он прошел через множество тестов производительности, исправлений ошибок и т.д.
Ваш второй пример будет работать только с несколькими изменениями:
// 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
}
}
Я не уверен, что это действительно принесет вам что-нибудь по сравнению с существующий шаблон, тем не менее.
Если существует ограниченное количество типов, из которого вы хотите сопоставить, то я бы использовал первый метод объявления типов ввода и вывода в определении интерфейса. Затем картограф может реализовать интерфейсы для каждого типа ввода, который он поддерживает, поэтому ваш BillHistoryMapper
будет объявлен как:
public class BillHistoryMapper : IMapper<BillHistory, Invoice>, IMapper<BillHistory, Payment>
{
...
}
Что касается вашего абстрактного класса, подумайте о том, чтобы избавиться от него и заменить его методом расширения. Это позволит вам использовать функцию 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
делегат, и вы можете сделать что-то вроде:
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);
}
}
Заключительное слово
Также помните, что не всегда ваша стратегия отображения будет работать во всех случаях, поэтому не пытайтесь бороться с вашим доменом и заставлять его соответствовать вашей стратегии отображения. Один из конкретных примеров - вам может потребоваться создать карту из двух входных элементов в один. Вы, конечно, можете сделать так, чтобы это соответствовало вашей стратегии, но вы можете обнаружить, что это становится беспорядочным. В этом конкретном примере рассмотрим слияние.