Mejores prácticas sobre mapeo de tipos y métodos de extensión


15

Quiero hacer algunas preguntas sobre las mejores prácticas con respecto a los tipos de mapeo y el uso de métodos de extensión en C #. Sé que este tema se ha discutido varias veces en los últimos años, pero he leído muchas publicaciones y todavía tengo dudas.

El problema que encontré fue extender la clase que poseo con la funcionalidad "convertir". Digamos que tengo la clase "Persona" que representa un objeto que será utilizado por alguna lógica. También tengo una clase "Cliente" que representa una respuesta de una API externa (en realidad habrá más de una API, por lo que necesito asignar la respuesta de cada API a un tipo común: Persona). Tengo acceso al código fuente de ambas clases y, en teoría, puedo implementar mis propios métodos allí. Necesito convertir Cliente a Persona para poder guardarlo en la base de datos. El proyecto no utiliza ningún mapeador automático.

Tengo 4 posibles soluciones en mente:

  1. Método .ToPerson () en la clase Consumer. Es simple, pero parece romper el patrón de responsabilidad única para mí, especialmente porque la clase Consumer está asignada a otras clases (algunas requeridas por otra API externa) también, por lo que necesitaría contener múltiples métodos de mapeo.

  2. El constructor de mapeo en la clase Person toma Consumer como argumento También es fácil y también parece romper el patrón de responsabilidad única. Necesitaría tener múltiples constructores de mapeo (ya que habrá clases de otra API, proporcionando los mismos datos que Consumer pero en un formato ligeramente diferente)

  3. Clase de convertidores con métodos de extensión. De esta manera puedo escribir el método .ToPerson () para la clase Consumer y cuando se introduce otra API con su propia clase NewConsumer, puedo escribir otro método de extensión y mantenerlo todo en el mismo archivo. He escuchado una opinión de que los métodos de extensión son malos en general y deben usarse solo si es absolutamente necesario, así que eso es lo que me está frenando. De lo contrario, me gusta esta solución

  4. Clase Convertidor / Mapeador. Creo una clase separada que manejará las conversiones e implementará métodos que tomarán la instancia de la clase de origen como argumento y devolverán la instancia de la clase de destino.

En resumen, mi problema puede reducirse a varias preguntas (todo en contexto a lo que describí anteriormente):

  1. ¿Se considera que romper el método de conversión dentro del objeto (POCO?) (Como el método .ToPerson () en la clase Consumer) rompe el patrón de responsabilidad única?

  2. ¿El uso de constructores de conversión en clase (tipo DTO) se considera romper el patrón de responsabilidad única? ¿Especialmente si tal clase se puede convertir de múltiples tipos de fuente, entonces se requerirían múltiples constructores de conversión?

  3. ¿Usar métodos de extensión mientras se tiene acceso al código fuente original de la clase se considera una mala práctica? ¿Se puede usar este comportamiento como patrón viable para separar la lógica o es un antipatrón?


¿Es la Personclase un DTO? ¿contiene algún comportamiento?
Yacoub Massad

Hasta donde yo sé, técnicamente es un DTO. Contiene cierta lógica "inyectada" como métodos de extensión, pero esta lógica se limita al tipo de métodos "ConvertToThatClass". Todos estos son métodos heredados. Mi trabajo es implementar una nueva funcionalidad que use algunas de estas clases, pero me dijeron que no debería seguir con el enfoque actual si es malo solo para mantenerlo consistente. Así que me pregunto si este enfoque existente con la conversión utilizando métodos de extensión es bueno y si debería seguir con él o intentar otra cosa
emsi el

Respuestas:


11

¿Se considera que romper el método de conversión dentro del objeto (POCO?) (Como el método .ToPerson () en la clase Consumer) rompe el patrón de responsabilidad única?

Sí, porque la conversión es otra responsabilidad.

¿El uso de constructores de conversión en clase (tipo DTO) se considera romper el patrón de responsabilidad única? ¿Especialmente si tal clase se puede convertir de múltiples tipos de fuente, entonces se requerirían múltiples constructores de conversión?

Sí, la conversión es otra responsabilidad. No importa si lo hace a través de constructores o métodos de conversión (por ejemplo ToPerson).

¿Usar métodos de extensión mientras se tiene acceso al código fuente original de la clase se considera una mala práctica?

No necesariamente. Puede crear métodos de extensión incluso si tiene el código fuente de la clase que desea extender. Creo que si creas o no un método de extensión debe determinarse por la naturaleza de dicho método. Por ejemplo, ¿contiene mucha lógica? ¿Depende de algo más que los miembros del objeto mismo? Diría que no debería tener un método de extensión que requiera dependencias para funcionar o que contenga una lógica compleja. Solo la lógica más simple debe estar contenida en un método de extensión.

¿Se puede usar este comportamiento como patrón viable para separar la lógica o es un antipatrón?

Si la lógica es compleja, creo que no deberías usar un método de extensión. Como señalé anteriormente, debe usar métodos de extensión solo para las cosas más simples. No consideraría la conversión simple.

Le sugiero que cree servicios de conversión. Puede tener una única interfaz genérica para esto como esta:

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

Y puedes tener convertidores 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
    }
}

Y puede usar la Inyección de dependencias para inyectar un convertidor (por ejemplo IConverter<Person,Customer>) a cualquier clase que requiera la capacidad de convertir entre Persony Customer.


Si no me equivoco, ya hay un IConverteren el marco, esperando a ser implementado.
RubberDuck

@RubberDuck, ¿dónde existe?
Yacoub Massad el

Estaba pensando IConvertable, que no es lo que estamos buscando aquí. Mi error.
RubberDuck

Ah ja! Encontré lo que estaba pensando en @YacoubMassad. Converterse usa Listcuando se llama ConvertAll. msdn.microsoft.com/en-us/library/kt456a2y(v=vs.110).aspx Sin embargo, no sé lo útil que es para OP.
RubberDuck

También relevante Alguien más ha tomado el enfoque que propones aquí. codereview.stackexchange.com/q/51889/41243
RubberDuck

5

¿Se .ToPerson()considera que romper el método de conversión dentro del objeto (POCO?) (Como el método en la clase Consumer) rompe el patrón de responsabilidad única?

Si. Una Consumerclase es responsable de mantener los datos relacionados con un consumidor (y posiblemente realizar algunas acciones) y no debe ser responsable de convertir la misma en otro, el tipo de relación.

¿El uso de constructores de conversión en clase (tipo DTO) se considera romper el patrón de responsabilidad única? ¿Especialmente si tal clase se puede convertir de múltiples tipos de fuente, entonces se requerirían múltiples constructores de conversión?

Probablemente. Normalmente tengo métodos fuera del dominio y los objetos DTO para hacer la conversión. A menudo uso un repositorio que toma objetos de dominio y los (des) serializa, ya sea en una base de datos, memoria, archivo, lo que sea. Si pongo esa lógica en mis clases de dominio, ahora las he vinculado a una forma particular de serialización, que es mala para probar (y otras cosas). Si pongo la lógica en mis clases de DTO, las ato a mi dominio, limitando nuevamente las pruebas.

¿Usar métodos de extensión mientras se tiene acceso al código fuente original de la clase se considera una mala práctica?

No en general, aunque pueden ser utilizados en exceso. Con los métodos de extensión, crea extensiones opcionales que hacen que el código sea más fácil de leer. Tienen inconvenientes: es más fácil crear ambigüedades que el compilador tiene que resolver (a veces en silencio) y puede hacer que el código sea más difícil de depurar, ya que no es del todo obvio de dónde proviene el método de extensión.

En su caso, una clase de convertidor o mapeador parece ser el enfoque más simple y directo, aunque no creo que esté haciendo nada mal al usar métodos de extensión.


2

¿Qué tal usar AutoMapper o un mapeador personalizado?

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

del dominio

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

Debajo del capó puedes abstraer AutoMapper o rodar tu propio mapeador


2

Sé que esto es viejo, pero aún es reverente. Esto te permitirá convertir en ambos sentidos. Esto me resulta útil cuando trabajo con Entity Framework y al crear modelos de vista (DTO).

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

Me parece que AutoMapper es demasiado para hacer una operación de mapeo realmente simple.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.