Últimamente he tenido la costumbre de "enmascarar" colecciones de Java con nombres de clase amigables para los humanos. Algunos ejemplos simples:
// Facade class that makes code more readable and understandable.
public class WidgetCache extends Map<String, Widget> {
}
O:
// If you saw a ArrayList<ArrayList<?>> being passed around in the code, would you
// run away screaming, or would you actually understand what it is and what
// it represents?
public class Changelist extends ArrayList<ArrayList<SomePOJO>> {
}
Un colega me señaló que esta es una mala práctica e introduce retraso / latencia, además de ser un anti-patrón OO. Puedo entender que introduce un nivel muy pequeño de sobrecarga de rendimiento, pero no puedo imaginar que sea significativo. Entonces pregunto: ¿es bueno o malo hacerlo y por qué?
java
anti-patterns
generics
collections
herpylderp
fuente
fuente
ChangeList
la compilación se romperáextends
, porque List es una interfaz, requiereimplements
. @randomA lo que estás imaginando pierde el punto debido a este errorimplementation
ieHashMap
oTreeMap
y lo que tenía allí era un error tipográfico.Respuestas:
Retraso / Latencia? Llamo a BS por eso. Debe haber exactamente cero sobrecarga de esta práctica. ( Editar: se ha señalado en los comentarios que esto puede, de hecho, inhibir las optimizaciones realizadas por la VM de HotSpot. No sé lo suficiente sobre la implementación de VM para confirmar o negar esto. Estaba basando mi comentario en C ++ implementación de funciones virtuales).
Hay algo de código sobrecarga. Debe crear todos los constructores de la clase base que desee, reenviando sus parámetros.
Tampoco lo veo como un antipatrón, per se. Sin embargo, lo veo como una oportunidad perdida. En lugar de crear una clase que derive la clase base solo por cambiar el nombre, ¿qué tal si crea una clase que contenga la colección y ofrezca una interfaz mejorada específica para cada caso? ¿Debe su caché de widgets realmente ofrecer la interfaz completa de un mapa? ¿O debería ofrecer una interfaz especializada?
Además, en el caso de las colecciones, el patrón simplemente no funciona junto con la regla general de usar interfaces, no implementaciones, es decir, en un código de colección simple, crearía un
HashMap<String, Widget>
y luego lo asignaría a una variable de tipoMap<String, Widget>
. No seWidgetCache
puede extenderMap<String, Widget>
, porque esa es una interfaz. No puede ser una interfaz que extienda la interfaz base, porqueHashMap<String, Widget>
no implementa esa interfaz, y tampoco lo hace ninguna otra colección estándar. Y si bien puede convertirlo en una clase que se extiendaHashMap<String, Widget>
, debe declarar las variables comoWidgetCache
oMap<String, Widget>
, y el primero le pierde la flexibilidad para sustituir una colección diferente (tal vez una colección de carga diferida de ORM), mientras que el segundo tipo de derrota el punto de tener la clase.Algunos de estos contrapuntos también se aplican a mi clase especializada propuesta.
Estos son todos los puntos a considerar. Puede o no ser la elección correcta. En cualquier caso, los argumentos ofrecidos por su colega no son válidos. Si cree que es un antipatrón, debería nombrarlo.
fuente
public class Properties extends Hashtable<Object,Object>
injava.util
dice "Debido a que las propiedades heredan de Hashtable, los métodos put y putAll se pueden aplicar a un objeto Properties. Su uso se desaconseja en gran medida ya que permiten al llamante insertar entradas cuyas claves o los valores no son cadenas ". La composición habría sido mucho más limpia.Según IBM, esto en realidad es un antipatrón. Estas clases tipo 'typedef' se llaman tipos psuedo.
El artículo lo explica mucho mejor que yo, pero intentaré resumirlo en caso de que el enlace se caiga:
WidgetCache
no puede manejar unMap<String, Widget>
En el artículo proponen el siguiente truco para hacer la vida más fácil sin usar pseudo tipos:
Que funciona debido a la inferencia automática de tipos.
(Llegué a esta respuesta a través de esta pregunta de desbordamiento de pila relacionada)
fuente
newHashMap
resuelta ahora por el operador de diamantes?WidgetCache
pudiera asignar un valor de tipo a una variable de tipoWidgetCache
oMap<String,Widget>
sin una conversión, pero hay era un medio por el cual un método estáticoWidgetCache
podría hacer tomar una referenciaMap<String,Widget>
y devolverlo como tipoWidgetCache
después de realizar la validación deseada. Tal característica podría ser especialmente útil con los genéricos, que no tendrían que ser borrados (desde ...El éxito en el rendimiento se limitaría a lo sumo a una búsqueda de vtable, en la que probablemente ya esté incurriendo. Esa no es una razón válida para oponerse.
La situación es lo suficientemente común como para que la mayoría de los lenguajes de programación de tipo estático tengan una sintaxis especial para los tipos de alias, generalmente llamados a
typedef
. Java probablemente no los copió porque originalmente no tenía tipos parametrizados. Ampliar una clase no es ideal, debido a las razones que Sebastian cubrió tan bien en su respuesta, pero puede ser una solución razonable para la sintaxis limitada de Java.Typedefs tiene una serie de ventajas. Expresan la intención del programador más claramente, con un mejor nombre, a un nivel de abstracción más apropiado. Son más fáciles de buscar para propósitos de depuración o refactorización. Considere buscar en todas partes donde
WidgetCache
se usa a versus encontrar esos usos específicos de aMap
. Son más fáciles de cambiar, por ejemplo, si luego encuentra que necesita unLinkedHashMap
lugar, o incluso su propio contenedor personalizado.fuente
Le sugiero, como ya lo mencionaron otros, que use la composición sobre la herencia, para que pueda exponer solo los métodos que realmente se necesitan, con nombres que coincidan con el caso de uso previsto. ¿Los usuarios de tu clase realmente necesitan saber que
WidgetCache
es un mapa? ¿Y poder hacer con él lo que quieran? ¿O simplemente necesitan saber que es un caché para widgets?Clase de ejemplo de mi base de código, con solución a un problema similar que describió:
Puede ver que internamente es "solo un mapa", pero la interfaz pública no muestra esto. Y tiene métodos "amigables para el programador", como
appendMessage(Locale locale, String code, String message)
una forma más fácil y significativa de insertar nuevas entradas. Y los usuarios de clase no pueden hacerlo, por ejemplotranslations.clear()
, porqueTranslations
no se está extendiendoMap
.Opcionalmente, siempre puede delegar algunos de los métodos necesarios en el mapa utilizado internamente.
fuente
Veo esto como un ejemplo de una abstracción significativa. Una buena abstracción tiene un par de rasgos:
Oculta detalles de implementación que son irrelevantes para el código que lo consume.
Es tan complejo como debe ser.
Al extender, está exponiendo toda la interfaz del padre, pero en muchos casos es posible que gran parte de eso esté mejor oculto, por lo que querrá hacer lo que Sebastian Redl sugiere y favorecer la composición sobre la herencia y agregar una instancia del padre como un miembro privado de tu clase personalizada. Cualquiera de los métodos de interfaz que tengan sentido para su abstracción puede delegarse fácilmente (en su caso) en la colección interna.
En cuanto a un impacto en el rendimiento, siempre es una buena idea optimizar primero el código para facilitar la lectura, y si se sospecha un impacto en el rendimiento, perfile el código para comparar las dos implementaciones.
fuente
+1 a las otras respuestas aquí. También agregaré que en realidad la comunidad DDD (Domain Driven Design) considera que es una práctica muy buena. Defienden que su dominio y las interacciones con él deben tener un significado de dominio semántico en lugar de la estructura de datos subyacente. A
Map<String, Widget>
podría ser un caché, pero también podría ser otra cosa, lo que has hecho correctamente en My Not So Humble Opinion (IMNSHO) es modelar lo que representa la colección, en este caso un caché.Agregaré una edición importante en la que el contenedor de clase de dominio alrededor de la estructura de datos subyacente probablemente también debería tener otras variables o funciones miembro que realmente la conviertan en una clase de dominio con interacciones en lugar de solo una estructura de datos (si solo Java tuviera tipos de valor , los conseguiremos en Java 10 - ¡lo prometo!)
Será interesante ver qué impacto tendrán las secuencias de Java 8 en todo esto, me imagino que quizás algunas interfaces públicas prefieran tratar con una secuencia de (insertar primitiva o cadena común de Java) en lugar de un objeto de Java.
fuente