Práticas recomendadas para mapeamento de tipos e métodos de extensão


15

Quero fazer algumas perguntas sobre as práticas recomendadas em relação aos tipos de mapeamento e ao uso de métodos de extensão em C #. Sei que esse tópico foi discutido várias vezes nos últimos anos, mas li muitos posts e ainda tenho dúvidas.

O problema que encontrei foi estender a classe que possuo com a funcionalidade "converter". Digamos que eu tenha a classe "Person" que representa um objeto que será usado por alguma lógica. Eu também tenho uma classe "Customer" que representa uma resposta da API externa (na verdade, haverá mais de uma API, portanto, preciso mapear a resposta de cada API para o tipo comum: Person). Eu tenho acesso ao código fonte de ambas as classes e, teoricamente, posso implementar meus próprios métodos lá. Preciso converter Customer em Person para salvá-lo no banco de dados. O projeto não usa nenhum mapeador automático.

Tenho 4 soluções possíveis em mente:

  1. .ToPerson () na classe Consumer. É simples, mas parece que quebra o padrão de Responsabilidade Única para mim, especialmente porque a classe Consumer é mapeada para outras classes (algumas exigidas por outra API externa), portanto, seria necessário conter vários métodos de mapeamento.

  2. Construtor de mapeamento na classe Person, tendo Consumer como argumento. Também é fácil e também parece quebrar o padrão de responsabilidade única. Eu precisaria ter vários construtores de mapeamento (já que haverá classe de outra API, fornecendo os mesmos dados que o Consumidor, mas em um formato ligeiramente diferente)

  3. Classe de conversores com métodos de extensão. Dessa forma, eu posso escrever o método .ToPerson () para a classe Consumer e quando outra API é introduzida com sua própria classe NewConsumer, eu posso apenas escrever outro método de extensão e manter tudo no mesmo arquivo. Ouvi uma opinião de que os métodos de extensão são maus em geral e devem ser usados ​​apenas se for absolutamente necessário, e é isso que está me impedindo. Caso contrário, eu gosto desta solução

  4. Classe Conversor / Mapeador. Crio uma classe separada que manipulará conversões e implemente métodos que terão a instância da classe de origem como argumento e retornarão a instância da classe de destino.

Para resumir, meu problema pode ser reduzido a um número de perguntas (tudo no contexto do que descrevi acima):

  1. Colocar o método de conversão dentro do objeto (POCO?) (Como o método .ToPerson () na classe Consumer) considera quebrar o padrão de responsabilidade única?

  2. O uso de construtores de conversão na classe (semelhante ao DTO) é considerado uma quebra do padrão de responsabilidade única? Especialmente se essa classe puder ser convertida a partir de vários tipos de fontes, seriam necessários vários construtores de conversão?

  3. O uso de métodos de extensão ao ter acesso ao código fonte da classe original é considerado uma má prática? Esse comportamento pode ser usado como padrão viável para separar a lógica ou é um anti-padrão?


A Personclasse é um DTO? contém algum comportamento?
Yacoub Massad

Tanto quanto eu sei, é tecnicamente um DTO. Ele contém alguma lógica "injetada" como métodos de extensão, mas essa lógica é limitada ao tipo de métodos "ConvertToThatClass". Todos esses são métodos herdados. Meu trabalho é implementar novas funcionalidades que usem algumas dessas classes, mas me disseram que não devo seguir a abordagem atual se for ruim apenas para mantê-la consistente. Então, eu estou querendo saber se esta abordagem existente com a conversão usando métodos de extensão é um bom e se eu deveria ficar com ela, ou tentar outra coisa
emsi

Respostas:


11

Colocar o método de conversão dentro do objeto (POCO?) (Como o método .ToPerson () na classe Consumer) considera quebrar o padrão de responsabilidade única?

Sim, porque a conversão é outra responsabilidade.

O uso de construtores de conversão na classe (semelhante ao DTO) é considerado uma quebra do padrão de responsabilidade única? Especialmente se essa classe puder ser convertida a partir de vários tipos de fontes, seriam necessários vários construtores de conversão?

Sim, a conversão é outra responsabilidade. Não faz diferença se você fizer isso através de construtores ou métodos de conversão (por exemplo ToPerson).

O uso de métodos de extensão ao ter acesso ao código fonte da classe original é considerado uma má prática?

Não necessariamente. Você pode criar métodos de extensão, mesmo que possua o código fonte da classe que deseja estender. Eu acho que se você cria ou não um método de extensão deve ser determinado pela natureza desse método. Por exemplo, ele contém muita lógica? Depende de outra coisa que os membros do próprio objeto? Eu diria que você não deve ter um método de extensão que exija dependências para funcionar ou que contenha lógica complexa. Somente a lógica mais simples deve estar contida em um método de extensão.

Esse comportamento pode ser usado como padrão viável para separar a lógica ou é um anti-padrão?

Se a lógica for complexa, acho que você não deve usar um método de extensão. Como observei anteriormente, você deve usar métodos de extensão apenas para as coisas mais simples. Eu não consideraria a conversão simples.

Sugiro que você crie serviços de conversão. Você pode ter uma única interface genérica assim:

public interface IConverter<TSource,TDestination>
{
    TDestination Convert(TSource source_object);
}

E você pode ter conversores como este:

public class PersonToCustomerConverter : IConverter<Person,Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

E você pode usar a injeção de dependência para injetar um conversor (por exemplo IConverter<Person,Customer>) em qualquer classe que exija a capacidade de conversão entre Persone Customer.


Se não me engano, já existe uma IConverterestrutura, apenas esperando para ser implementada.
precisa

@RubberDuck, onde existe?
Yacoub Massad

Eu estava pensando IConvertable, o que não é o que estamos procurando aqui. Meu erro.
precisa

Ah ha! Encontrei o que estava pensando @YacoubMassad. Converteré usado Listao chamar ConvertAll. msdn.microsoft.com/en-us/library/kt456a2y(v=vs.110).aspx Não sei o quão útil isso é para o OP.
precisa

Também relevante. Outra pessoa adotou a abordagem que você propõe aqui. codereview.stackexchange.com/q/51889/41243
RubberDuck

5

A colocação do método de conversão dentro do objeto (POCO?) (Como o .ToPerson()método na classe Consumer) é considerada uma quebra do padrão de responsabilidade única?

Sim. A Consumerclasse é responsável por manter os dados relacionados a um consumidor (e possivelmente executar algumas ações) e não deve ser responsável por converter -se em outro, tipo não relacionado.

O uso de construtores de conversão na classe (semelhante ao DTO) é considerado uma quebra do padrão de responsabilidade única? Especialmente se essa classe puder ser convertida a partir de vários tipos de fontes, seriam necessários vários construtores de conversão?

Provavelmente. Normalmente, tenho métodos fora dos objetos domínio e DTO para fazer a conversão. Costumo usar um repositório que recebe objetos de domínio e os des serializa, em um banco de dados, memória, arquivo, qualquer que seja. Se eu colocar essa lógica em minhas classes de domínio, agora as vinculei a uma forma específica de serialização, que é ruim para testes (e outras coisas). Se eu colocar a lógica nas minhas classes de DTO, vinculei-as ao meu domínio, limitando novamente os testes.

O uso de métodos de extensão ao ter acesso ao código fonte da classe original é considerado uma má prática?

Não em geral, embora possam ser usados ​​em excesso. Com os métodos de extensão, você cria extensões opcionais que facilitam a leitura do código. Eles têm desvantagens - é mais fácil criar ambiguidades que o compilador precisa resolver (às vezes silenciosamente) e pode tornar o código mais difícil de depurar, pois não é totalmente óbvio de onde vem o método de extensão.

No seu caso, uma classe de conversor ou mapeador parece ser a abordagem mais simples e direta, embora eu não ache que você esteja fazendo algo errado usando métodos de extensão.


2

Que tal usar o AutoMapper ou o mapeador personalizado?

MyMapper
   .CreateMap<Person>()
   .To<PersonViewModel>()
   .Map(p => p.Name, vm => vm.FirstName)
   .To<SomeDTO>()
   .Map(...);

do domínio

 db.Persons
   .ToListAsync()
   .Map<PersonViewModel>();

Sob o capô, você pode abstrair o AutoMapper ou rodar seu próprio mapeador


2

Eu sei que isso é antigo, mas ainda é revelador. Isso permitirá que você converta nos dois sentidos. Acho isso útil ao trabalhar com o Entity Framework e ao criar modelos de exibição (DTOs).

public interface IConverter<TSource, TDestination>
{
    TDestination Convert(TSource source_object);
    TSource Convert(TDestination source_object);
}

public class PersonCustomerConverter : IConverter<Person, Customer>
{
    public Customer Convert(Person source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
    public Person Convert(Customer source_object)
    {
        //Do the conversion here. Note that you can have dependencies injected to this class
    }
}

Acho que o AutoMapper é um pouco demais para fazer uma operação de mapeamento realmente simples.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.