Pergunta

Eu tenho a necessidade de um objeto para objetar mapear no meu aplicativo. Eu experimentei alguns, mas não consegui encontrar nada que atenda às minhas necessidades, então estou escrevendo o meu. Atualmente, tenho uma interface como abaixo:

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

Em seguida, implemento um AccountMapper que mapeia um cliente para uma conta como:

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

Isso funciona bem até agora, no entanto, tenho várias entidades de origem que mapeiam para a mesma entidade de destino. Por exemplo, tenho um pagamento e uma fatura que os dois mapeiam para o BillHistory. Para o exposto para apoiar isso, preciso fazer dois mapeadores separados (por exemplo, BillHistoryPayMentMapper e BillHistoryInvoicemapper), o que é bom. No entanto, eu adoraria poder implementá -lo um pouco diferente, como abaixo. O único problema é que não sei se é possível e, se sim, não conheço a sintaxe correta.

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

Enquanto a primeira implementação funciona bem, o segundo seria um pouco mais elegante. Isso é possível e, se sim, como seria a sintaxe correta?

editar-------

Eu odeio quando as pessoas fazem isso, mas é claro que esqueci de mencionar um pequeno detalhe. Temos uma classe abstrata entre o mapeador e a interface para implementar uma lógica comum em todos os mapeadores. Então, minha assinatura do mapeador é realmente:

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

Onde o Mapper contém:

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));
    }
}
Foi útil?

Solução

Você terá que usar sua primeira interface e implementar a interface várias vezes em seu objeto:

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

Eu seria sério, considere dar uma olhada em Automapper em vez de escrever o seu próprio. Há muitas nuances no mapeamento que ele já resolveu, sem mencionar que foi através de muitos testes de desempenho, correções de bugs etc.

Outras dicas

No que diz respeito à sua classe abstrata, considere se livrar dela e substituí -la por um método de extensão. Isso permitirá que você use o MapAll função, independentemente de você implementar a interface ou usar algum tipo de cadeia de herança.

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

Agora isso facilitará a tentativa de resolver seu problema acima, porque você não precisa mais herdar de uma classe base Você pode implementar a interface de mapeamento para os tipos que deseja mapear.

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

Considere também mudar seu IMapper Parâmetros genéricos para ser o contrário (peguei a liberdade nos exemplos anteriores):

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

A razão para isso é que ele mapeia diretamente para o System.Converter<T> delegado e você pode fazer algo como:

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 Também foi sugerido que facilitará sua vida. No entanto, se você deseja manter sua abstração de mapeamento e ter seu código agnóstico à sua estratégia de mapeamento, é muito fácil usar a interface acima para injetar um invólucro no Automappper. Isso também significa que você pode continuar a usar o MapAll Método de extensão explicado acima.

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

Palavra final

Além disso, lembre -se de que você nem sempre descobrirá que sua estratégia de mapeamento funcionará em geral, para que não tente combater seu domínio e forçá -lo a se ajustar à sua estratégia de mapeamento. Um exemplo em particular é que você pode ter que mapear de dois itens de entrada em um. Obviamente, você pode fazer isso se encaixar na sua estratégia, mas pode achar que ela se torna confusa. Neste exemplo em particular, considere isso uma mesclagem.

Seu segundo exemplo funcionará com apenas algumas alterações:

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

Não tenho certeza se isso realmente lhe ganha alguma coisa sobre o padrão existente.

Se houver um número limitado de tipos dos quais você deseja mapear, eu usaria o primeiro método de declarar os tipos de entrada e saída na definição da interface. Em seguida, um mapeador pode implementar interfaces para cada tipo de entrada que ele suporta; portanto, seu BillHistoryMapper seria declarado como:

public class BillHistoryMapper : IMapper<BillHistory, Invoice>, IMapper<BillHistory, Payment>
{
   ...
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top