¿Por qué definir un objeto Java utilizando la interfaz (por ejemplo, Mapa) en lugar de la implementación (HashMap)

17

En la mayoría de los códigos Java, veo que la gente declara objetos Java como este:

Map<String, String> hashMap = new HashMap<>();
List<String> list = new ArrayList<>();

en lugar de:

HashMap<String, String> hashMap = new HashMap<>();
ArrayList<String> list = new ArrayList<>();

¿Por qué existe una preferencia para definir el objeto Java utilizando la interfaz en lugar de la implementación que realmente se va a utilizar?

Suman
fuente

Respuestas:

26

La razón es que la implementación de estas interfaces generalmente no es relevante al manejarlas, por lo tanto, si obliga a la persona que llama a pasar HashMapun método, entonces esencialmente está obligando a qué implementación usar. Por lo tanto, como regla general, se supone que debe manejar su interfaz en lugar de la implementación real y evitar el dolor y el sufrimiento que podrían tener que cambiar todas las firmas de métodos HashMapcuando decida que necesita usarlas LinkedHashMap.

Debe decirse que hay excepciones a esto cuando la implementación es relevante. Si necesita un mapa cuando el orden es importante, entonces se puede requerir una TreeMapo una LinkedHashMapque se pasa, o mejor aún, SortedMapque no especifica una implementación específica. Esto obliga a la persona que llama a pasar necesariamente un cierto tipo de implementación de Map y sugiere que ese orden es importante. Dicho esto, ¿podrías anular SortedMapy pasar uno sin clasificar? Sí, por supuesto, sin embargo, espera que sucedan cosas malas como resultado.

Sin embargo, la mejor práctica aún dicta que si no es importante, no debe usar implementaciones específicas. Esto es cierto en general. Si está lidiando Dogy Catcuáles se derivan Animal, para hacer un mejor uso de la herencia, generalmente debe evitar tener métodos específicos para Dogo Cat. En su lugar, todos los métodos deben Dogo Catdeben anular los métodos Animaly esto le ahorrará problemas a largo plazo.

Neil
fuente
Cuando necesita un mapa ordenado, el tipo de parámetro debe ser SortedMap, no TreeMap.
Cefalópodo
@Arian SortedMapes una de varias implementaciones que se ocupan de ordenar. Eso está fuera del punto. TreeMaptambién ordena artículos de acuerdo con la implementación de la clave Comparableo con una Comparatorinterfaz dada .
Neil
No, SortedMap no es una implementación, ese es exactamente el punto. Es la interfaz para mapas que se ordenan por clave.
Cefalópodo
1
@Arian Ah, entiendo lo que quieres decir. Es cierto, mejor SortedMap ya que eso no obliga a una implementación. Haré los ajustes adecuados.
Neil
En realidad, a LinkedHashMapno se implementa SortedMap. Las únicas subclases de SortedMapson ConcurrentSkipListMapy TreeMap.
bcorso
10

En palabras de Layman:

La misma razón por la que los fabricantes de electrodomésticos construyeron sus productos con enchufes eléctricos en lugar de simplemente cables pelados, y las casas vienen con enchufes de pared en lugar de cables desprendibles que sobresalen de la pared.

Al utilizar enchufes estándar en su lugar, permiten enchufar los mismos aparatos en cualquier enchufe compatible de la casa.

Desde el punto de vista de la toma de corriente, no importa si conecta un televisor o un estéreo.

Eso hace que tanto el dispositivo como el enchufe sean más útiles.

Tomemos, por ejemplo, un método que acepte un Mapa como argumento.

El método funcionará independientemente de que le pase un HashMap o un LinkedHashMap, siempre que sea una subclase de Map.

Ese es el principio de sustitución de Liskov .

En el código de muestra que proporcionó, significa que más tarde, por alguna razón, puede cambiar la implementación concreta de Hash y no necesitará cambiar el resto del código.

El problema con el software es que, dado que es relativamente fácil cambiar las cosas más tarde sin desperdicio de ladrillos o mortero, la gente asume que ese tipo de previsión no vale la pena. Pero la realidad nos ha demostrado que el mantenimiento del software es muy costoso.

Tulains Córdova
fuente
4

Es seguir el principio de segregación de interfaz (la 'I' en SÓLIDO ). Evita que el código que usa esos objetos dependa de los métodos de esos objetos que no necesita, lo que hace que el código esté menos acoplado y, por lo tanto, sea más fácil de cambiar.

Por ejemplo, si luego descubre que realmente necesita un LinkedHashMap, puede hacer ese cambio de manera segura sin afectar ningún otro código.

Sin embargo, hay una compensación, porque está limitando artificialmente el código que puede tomar su objeto como parámetro. Digamos que hay una función en algún lugar que requiere un HashMappor alguna razón. Si devuelve a Map, no puede pasar su objeto a esa función. Debe equilibrar la probabilidad de que en el futuro necesite la funcionalidad adicional que se encuentra en la clase más concreta con el deseo de limitar el acoplamiento y mantener su interfaz pública lo más pequeña posible.

Karl Bielefeldt
fuente
3

Tener la variable restringida a una interfaz garantiza que ninguno de los usos de esa variable utilizará HashMapuna funcionalidad específica que puede no existir en la interfaz, por lo que la instancia puede cambiarse sin preocupación más adelante a una implementación diferente siempre que la nueva instancia también implemente interfaz.

Por esta razón, cada vez que desee usar una interfaz de objetos, siempre es una buena práctica declarar sus variables como la interfaz y no la implementación particular, esto se aplica a todos los tipos de objetos que puede usar que tienen una interfaz. La razón por la que lo ve a menudo es porque muchas personas lo han incorporado como un hábito.

Dicho esto, a veces no es dañino omitir el uso de interfaces, y la mayoría de nosotros descuidadamente no siempre seguimos esta regla, sin daño real. Es una buena práctica seguir cuando tiene la sensación de que el código puede cambiar y necesita mantenimiento / crecimiento en el futuro. Es menos preocupante cuando pirateas código que no sospechas que tendrá una larga vida o tiene mucha importancia. Además, romper esta regla generalmente tiene una pequeña consecuencia: cambiar la implementación a otra puede requerir un poco de refactorización, por lo que si no siempre la sigue, no se lastimará mucho, aunque tampoco hay ningún daño real en seguirla. .

Jimmy Hoffa
fuente