Implementación de múltiples interfaces genéricas en java

10

Necesito una interfaz que me asegure que un cierto método, incluida la firma específica, esté disponible. Hasta ahora lo suyo es lo que tengo:

public interface Mappable<M> {
    M mapTo(M mappableEntity);
}

El problema surge cuando una clase debe ser mapeable a varias otras entidades. El caso ideal sería este (no java):

public class Something implements Mappable<A>, Mappable<B> {
    public A mapTo(A someObject) {...}
    public B mapTo(B someOtherObject) {...}
}

¿Cuál sería la mejor manera de lograr que esto permanezca lo más "genérico" posible?

estani
fuente

Respuestas:

10

Esto, por supuesto, no es algo que pueda hacer debido a Type Erasure . En tiempo de ejecución, tiene dos métodos public Object mapTo(Object), que obviamente no pueden coexistir.

Desafortunadamente, lo que está tratando de hacer es simplemente más allá del sistema de tipos de Java.

Asumiendo que su tipo genérico es siempre un tipo de primera clase, y no genérico en sí mismo, podría lograr un comportamiento similar al exterior al tener el método mapTo(Object, Class), que le permitiría hacer una inspección en tiempo de ejecución de la clase dada y decidir qué comportamiento usar. Obviamente, esto es bastante poco elegante, y requerirá la conversión manual del valor de retorno, pero creo que es lo mejor que puede hacer. Si sus tipos genéricos son genéricos, sus parámetros genéricos también se borrarán y sus clases serán iguales, por lo que este método no funcionará.

Sin embargo, también señalaría la respuesta de @Joachim, este puede ser un caso en el que puede dividir el comportamiento en componentes separados y eludir todo el problema.

Phoshi
fuente
3

Como viste, no puedes implementar la misma interfaz dos veces con diferentes parámetros de tipo (debido a la eliminación: en tiempo de ejecución son las mismas interfaces).

Además, este enfoque viola el principio de responsabilidad única: su clase debe enfocarse en ser un Something(lo que sea que eso signifique) y no debe hacer el mapeo Ao B además de esa tarea.

Parece que realmente deberías tener a Mapper<Something,A>y a Mapper<Something,B>. De esta forma, cada clase tiene una única responsabilidad claramente definida y no se encuentra con el problema de implementar la misma interfaz dos veces.

Joachim Sauer
fuente
Bueno, la idea es dejar que la clase sea responsable de "transformar" su contenido a otros objetos. Luego hay un despachador que los maneja de manera agnóstica de clase, por lo tanto, el requisito genérico. Pensaré un poco en extraer la lógica, aunque eso en realidad significa que estoy dividiendo la clase en dos, pero permanecen estrechamente acoplados (se debe otorgar acceso al campo, modificar uno la mayoría de las veces implica modificar el otro, etc.)
2013
@estani: sí, están algo estrechamente unidos, pero tienen responsabilidades distintas. También piense en esto: cuando introduce una nueva clase Cy desea Somethingser mapeable a esa, entonces necesitaría modificar Something, lo que es demasiado acoplamiento. Solo agregar una nueva SoemthingToCMapperes menos intrusivo.
Joachim Sauer
1
+1 a esto: en términos generales, debe favorecer la composición sobre la herencia si está tratando de lograr lo que quiere el OP (en Java). Java 8 con métodos predeterminados hace que esto sea aún más fácil, pero aún no todos pueden saltar al límite :-).
Martijn Verburg el
0

Dado que no está permitido implementar múltiples interfaces, puede considerar el uso de encapsulación. (ejemplo usando java8 +)

// Mappable.java
public interface Mappable<M> {
    M mapTo(M mappableEntity);
}

// TwoMappables.java
public interface TwoMappables {
    default Mappable<A> mapableA() {
         return new MappableA();
    }

    default Mappable<B> mapableB() {
         return new MappableB();
    }

    class MappableA implements Mappable<A> {}
    class MappableB implements Mappable<B> {}
}

// Something.java
public class Something implements TwoMappables {
    // ... business logic ...
    mapableA().mapTo(A);
    mapableB().mapTo(B);
}

Consulte aquí para obtener más información y más ejemplos: ¿Cómo hacer una clase Java que implemente una interfaz con dos tipos genéricos? .

invierno
fuente
-1
public interface IMappable<S, T> {
    T MapFrom(S source);
}

// T - target
// S - source

Si desea asignar Usuario a UserDTO y asignar Usuario a UserViewModel, necesitará dos implementaciones separadas. No acumule toda esta lógica en una sola clase, no tiene sentido hacerlo.

Actualización para mantener a Joachim feliz

public interface ITypeConverter<TSource, TDestination>
{
    TDestination Convert(TSource source);
}

Pero ahora estamos en el ámbito de Automapper ( http://automapper.codeplex.com/wikipage?title=Custom%20Type%20Converters )

CodeART
fuente
No creo que IMappablesea ​​un buen nombre para algo que mapee otras cosas. Mapper(o IMapper, si es necesario ;-)) es probablemente más correcto. (Por cierto: no, ese no fue mi voto negativo).
Joachim Sauer
He tomado lo que estaba en la pregunta y con el prefijo I para resaltar el hecho de que es una interfaz. Estoy resolviendo un problema de diseño según la pregunta, en lugar de un problema de nomenclatura.
CodeART
1
lo siento, pero en mi opinión, uno realmente no puede "resolver" un problema de diseño e ignorar la nomenclatura. Diseño significa estructuras comprensibles. Nombrar incorrectamente es un problema de comprensión.
Joachim Sauer
La actualización debería tranquilizarlo ;-)
CodeART
1
@CodeART Si entendí su respuesta correctamente, implica "MapFrom" (que debe estar en minúsculas ;-) crea el objeto. En mi caso, solo se trata de llenar información sobre un objeto ya creado.
estani