He visto la discusión en esta pregunta sobre cómo se instanciaría una clase que se implementa desde una interfaz. En mi caso, estoy escribiendo un programa muy pequeño en Java que usa una instancia de TreeMap
, y de acuerdo con la opinión de todos allí, debería ser instanciado como:
Map<X> map = new TreeMap<X>();
En mi programa, llamo a la función map.pollFirstEntry()
, que no se declara en la Map
interfaz (y a un par de otras personas que también están presentes en la Map
interfaz). Me las arreglé para hacer esto enviando a un sitio TreeMap<X>
donde llamo este método como:
someEntry = ((TreeMap<X>) map).pollFirstEntry();
Entiendo las ventajas de las pautas de inicialización descritas anteriormente para programas grandes, sin embargo, para un programa muy pequeño donde este objeto no se pasaría a otros métodos, creo que es innecesario. Aún así, estoy escribiendo este código de muestra como parte de una solicitud de empleo, y no quiero que mi código se vea mal ni esté desordenado. ¿Cuál sería la solución más elegante?
EDITAR: Me gustaría señalar que estoy más interesado en las buenas prácticas generales de codificación en lugar de la aplicación de la función específica TreeMap
. Como algunas de las respuestas ya han señalado (y he marcado como contestado el primero en hacerlo), se debe usar el nivel de abstracción más alto posible, sin perder la funcionalidad.
fuente
Respuestas:
"Programar en una interfaz" no significa "usar la versión más abstracta posible". En ese caso, todos lo usarían
Object
.Lo que significa es que debe definir su programa contra la abstracción más baja posible sin perder funcionalidad . Si necesita una
TreeMap
, deberá definir un contrato con aTreeMap
.fuente
TreeMap
como ejemplo. "Programa para una interfaz" no debe tomarse literalmente como una interfaz o una clase abstracta: una implementación también puede considerarse una interfaz.public interface
lugar de 'los métodos públicos de una implementación' .TreeMap
habría tenidoSortedMap
oNavigableMap
Si todavía quieres usar una interfaz, puedes usar
no es necesario usar siempre una interfaz, pero a menudo hay un punto en el que desea tener una vista más general que le permita reemplazar la implementación (quizás para probar) y esto es fácil si todas las referencias al objeto se abstraen como Tipo de interfaz.
fuente
El punto detrás de la codificación de una interfaz en lugar de una implementación es evitar la filtración de detalles de implementación que de otro modo limitarían su programa.
Considere la situación en la que la versión original de su código usó
HashMap
ay lo expuso.Esto significa que cualquier cambio
getFoo()
es un cambio de API de última hora y haría que las personas que lo usan sean infelices. Si todo lo que está garantizando es quefoo
es un Mapa, debe devolverlo en su lugar.Esto le brinda la flexibilidad de cambiar cómo funcionan las cosas dentro de su código. Te das cuenta de que en
foo
realidad debe ser un Mapa que devuelva las cosas en un orden particular.Y eso no rompe nada para el resto del código.
Más tarde, puede fortalecer el contrato que da la clase sin romper nada tampoco.
Esto profundiza en el principio de sustitución de Liskov
Dado que NavigableMap es un subtipo de Mapa, esta sustitución se puede hacer sin alterar el programa.
Exponer los tipos de implementación hace que sea difícil cambiar la forma en que el programa funciona internamente cuando se necesita hacer un cambio. Este es un proceso doloroso y muchas veces crea soluciones desagradables que solo sirven para infligir más dolor al codificador más tarde (te estoy mirando a un codificador anterior que seguía barajando datos entre un LinkedHashMap y un TreeMap por alguna razón - confía en mí, siempre que yo veo tu nombre en svn la culpa me preocupa).
Se podría todavía quieren evitar fugas tipos de implementación. Por ejemplo, es posible que desee implementar ConcurrentSkipListMap en su lugar debido a algunas características de rendimiento o simplemente en
java.util.concurrent.ConcurrentSkipListMap
lugar dejava.util.TreeMap
en las declaraciones de importación o lo que sea.fuente
De acuerdo con las otras respuestas, debe usar la clase (o interfaz) más genérica de la que realmente necesita algo, en este caso TreeMap (o como alguien sugirió NavigableMap). Pero me gustaría agregar que esto es ciertamente mejor que lanzarlo a todas partes, lo que sería un olor mucho más grande en cualquier caso. Consulte /programming/4167304/why-should-casting-be-avoided por algunos motivos.
fuente
Se trata de comunicar su intención de cómo se debe usar el objeto. Por ejemplo, si su método espera un
Map
objeto con un orden de iteración predecible :Luego, si absolutamente necesita decirles a las personas que llaman sobre el método anterior que también devuelve un
Map
objeto con un orden de iteración predecible , porque existe tal expectativa por alguna razón:Por supuesto, las personas que llaman aún pueden tratar el objeto de retorno
Map
como tal, pero eso está más allá del alcance de su método:Dando un paso atrás
El consejo general de codificación a una interfaz es (generalmente) aplicable, porque generalmente es la interfaz la que proporciona la garantía de lo que el objeto debería poder realizar, también conocido como el contrato . Muchos principiantes comienzan
HashMap<K, V> map = new HashMap<>()
y se les aconseja declarar eso como aMap
, porque aHashMap
no ofrece nada más de lo queMap
se supone que debe hacer. A partir de esto, podrán comprender (con suerte) por qué sus métodos deberían tomar enMap
lugar de aHashMap
, y esto les permite darse cuenta de la característica de herencia en OOP.Para citar solo una línea de la entrada de Wikipedia del principio favorito de todos relacionado con este tema:
En otras palabras, usar una
Map
declaración no es porque tenga sentido 'sintácticamente', sino que las invocaciones sobre el objeto solo deberían importarle que sea un tipo deMap
.Código más limpio
Encuentro que esto me permite escribir código más limpio a veces también, especialmente cuando se trata de pruebas unitarias. Crear una
HashMap
entrada de prueba de solo lectura requiere más de una línea (excluyendo el uso de la inicialización de doble llave), cuando puedo reemplazarla fácilmente conCollections.singletonMap()
.fuente