Estoy desarrollando un código que utiliza genéricos, y uno de mis principios rectores fue hacerlo utilizable para escenarios futuros, y no solo para los de hoy. Sin embargo, varios compañeros de trabajo han expresado que podría haber cambiado la legibilidad en aras de la extensibilidad. Quería reunir algunos comentarios sobre posibles formas de resolver esto.
Para ser específicos, aquí hay una interfaz que define una transformación: comienza con una colección de elementos de origen y aplica la transformación a cada elemento, almacenando los resultados en una colección de destino. Además, quiero poder devolver la colección de destino a la persona que llama, y en lugar de obligarlos a usar una referencia de Colección, quiero que puedan usar el tipo de colección que realmente proporcionaron para la colección de destino.
Finalmente, hago posible que el tipo de elementos en la colección de destino sea diferente del tipo de elementos en la colección de origen, porque tal vez eso es lo que hace la transformación. En mi código, por ejemplo, varios elementos de origen forman un elemento de destino después de la transformación.
Esto produce la siguiente interfaz:
interface Transform<Src, Dst> {
<DstColl extends Collection<? super Dst>> DstColl transform(
Collection<? extends Src> sourceCollection,
DstColl destinationCollection);
}
Traté de ser amable y aplicar el principio PECS de Josh Bloch (productor se extiende, consumidor súper) para asegurarme de que la interfaz sea utilizable con súper y subtipos cuando sea apropiado. El resultado final es algo monstruoso.
Ahora, hubiera sido bueno si pudiera extender esta interfaz y especializarla de alguna manera. Por ejemplo, si realmente no me importa jugar bien con los subtipos de los elementos de origen y los supertipos de los elementos de destino, podría tener:
interface SimpleTransform<Src, Dst> {
<DstColl extends Collection<Dst>> DstColl transform(
Collection<Src> sourceCollection,
DstColl destinationCollection);
}
Pero no hay forma de hacerlo en Java. Quiero hacer que las implementaciones de esta interfaz sean algo que otros realmente considerarían hacer, en lugar de correr con miedo. He considerado varias opciones:
- No devuelva la colección de destino. Parece extraño dado que haces una transformación pero no recuperas nada.
- Tenga una clase abstracta que implemente esta interfaz, pero luego traduzca los parámetros a algo más fácil de usar y llame a otro método "translateImpl ()" que tenga la firma más simple y, por lo tanto, presente una carga cognitiva menor para los implementadores. Pero entonces es extraño tener que escribir una clase abstracta solo para hacer que una interfaz sea fácil de usar.
- Olvídese de la extensibilidad y solo tenga la interfaz más simple. Posiblemente junte eso con no devolver la colección de destino. Pero eso limita mis opciones en el futuro.
¿Qué piensas? ¿Me estoy perdiendo un enfoque que podría usar?
fuente
Collection<? extends Src> srcCollection
, deberías usarIterable<? extends Src>
Respuestas:
Guava Collections ya ofrece esta funcionalidad y si puede esperar un poco más, Java 8 también lo hará :-). FWIW Creo que tu uso de
? extends T
es el idioma correcto, no hay mucho que puedas hacer dado que Java usa Type Erasurefuente
Collection<? super Dst>
sería ideal aquí, si no también devolviera el mismo tipo (presumiblemente la misma referencia, lo cual no es realmente una gran idea (creo que Java SE 8 lo hará para algunos métodos (into
))). Es interesante notar que los gustos deCollections.fill
usar comodines innecesariamente para ser más expresivos. / IIRC, Java SE 6 finalmente deja de ser compatible este mes.Personalmente, no veo muchos problemas con la forma en que ha definido su interfaz, sin embargo, estoy acostumbrado a jugar con los genéricos y puedo entender que sus compañeros de trabajo encontrarán que su código es un poco bocado si ese no es su caso.
Si yo fuera usted, elegiría la tercera solución que establezca: simplificar las cosas a expensas de tener que volver más adelante. Porque después de todo, tal vez no tengas que hacerlo al final; o terminaría siendo capaz de usarlo completamente en algún momento, pero no lo suficiente para compensar el esfuerzo que ha puesto en hacer que esta interfaz sea tan genérica, así como el esfuerzo que sus compañeros de trabajo pueden haber hecho para entenderlo. eso.
Otra cosa a tener en cuenta es la frecuencia con la que utiliza esta interfaz y en qué nivel de complejidad. Además, cuán útil resulta esta interfaz en sus proyectos. Si permite una alta reutilización de algunos componentes (y no solo potenciales, sino reales), es algo bueno. Si una interfaz mucho más simple funciona tan bien el 90% del tiempo, podría preguntarse si el 10% restante sería útil o no con una interfaz compleja.
No creo que sea una muy buena idea no usarlo
super
yextend
, aunque por el momento puede prescindir de ellos, estoy seguro de que a sus colegas no les importará verlos desaparecer. Sin embargo, ¿realmente tiene tantas situaciones en las que también necesita cambiar el tipoCollection
?Algunos consejos ya dados son realmente buenos, y estoy de acuerdo con Frank en seguir el principio de YAGNI a menos que tenga muy buenas razones para no hacerlo. Todavía puede cambiar el código cuando surja la necesidad de una mayor complejidad, pero no es de mucha utilidad desarrollar cosas que no está seguro de que se usarán pronto. Además, el consejo de Martijn de usar guayaba es algo a considerar en serio. Todavía no he tenido la oportunidad de usarlo, pero he escuchado muchas cosas buenas al respecto, incluso en una presentación en la que se discutió el patrón Transformer (puede verlo en línea en InfoQ si está interesado).
Por otro lado, ¿no sería mejor en su interfaz actual tener
destinationCollection
un tipoClass<DstColl>
?fuente
Me gusta envolver colecciones de Java. La encapsulación adecuada realmente puede ayudar. Este patrón necesita estar relacionado según el tipo, pero deja en gran medida cada línea de código sin saber el tipo de colección.
Por ejemplo, suponga que hay una lista de productos. La clase de producto sería inmutable y tendría algunos métodos de utilidad. La clase de lista de productos puede agregarle un producto u otra lista. Si desea ejecutar un método en toda la lista, sería en la clase de lista de productos. Hay un método que tomaría una clase de lista de filtros y daría una nueva lista de productos con los filtrados. El filtrado se eliminaría del original.
Hay una interfaz de filtro que decide si UN producto pasa el filtro. Habría una clase de lista de filtros que tiene una lista de filtros e implementa la interfaz al requerir que TODOS los filtros pasen un producto para que el producto pase.
Lo curioso es que puedo apilar y cambiarlo a una lista vinculada sin cambios notables como este. Puede hacer bucles y hacer condicionales muy notorios y simples. Por lo tanto, viste un código imperativo y agrega flexibilidad. Y está bien orgonizado.
fuente