tienen - que es una clase abstracta en java
¿Cuándo debo devolver la interfaz y cuándo la clase concreta? (12)
Por ejemplo, si sé que accederé principalmente a los datos de la lista de forma aleatoria, una lista enlazada sería mala. Pero si mi función de biblioteca solo devuelve la interfaz, simplemente no lo sé. Para estar seguro, incluso podría necesitar copiar la lista explícitamente a un ArrayList.
Como todos los demás han mencionado, no debe preocuparse por cómo la biblioteca ha implementado la funcionalidad, para reducir el acoplamiento y aumentar la capacidad de mantenimiento de la biblioteca.
Si usted, como cliente de la biblioteca, puede demostrar que la implementación está teniendo un mal desempeño en su caso de uso, puede comunicarse con la persona a cargo y discutir sobre el mejor camino a seguir (un nuevo método para este caso o simplemente cambiando la implementación) .
Dicho esto, su ejemplo apesta a una optimización prematura.
Si el método es o puede ser crítico, podría mencionar los detalles de la implementación en la documentación.
Cuando programo en Java, prácticamente siempre, solo por costumbre, escribo algo como esto:
public List<String> foo() {
return new ArrayList<String>();
}
La mayor parte del tiempo sin siquiera pensarlo. Ahora, la pregunta es: ¿ siempre debo especificar la interfaz como el tipo de retorno? ¿O es aconsejable utilizar la implementación real de la interfaz y, en caso afirmativo, en qué circunstancias?
Es obvio que el uso de la interfaz tiene muchas ventajas (por eso está ahí). En la mayoría de los casos, no importa qué implementación concreta utilice una función de biblioteca. Pero tal vez hay casos en los que sí importa. Por ejemplo, si sé que accederé principalmente a los datos de la lista de forma aleatoria, una lista LinkedList
sería mala. Pero si mi función de biblioteca solo devuelve la interfaz, simplemente no lo sé. Para estar seguro, incluso podría necesitar copiar la lista explícitamente a un ArrayList
:
List bar = foo();
List myList = bar instanceof LinkedList ? new ArrayList(bar) : bar;
pero eso parece horrible y mis compañeros de trabajo probablemente me lincharán en la cafetería. Y con razón.
¿Qué piensan ustedes? ¿Cuáles son sus pautas, cuándo tiende a la solución abstracta y cuándo revela los detalles de su implementación para posibles ganancias de rendimiento?
Como regla general, solo devuelvo las implementaciones internas si estoy en algún funcionamiento interno privado de una biblioteca, y aún así solo con moderación. Para todo lo que es público y es probable que se llame desde el exterior de mi módulo, uso interfaces y también el patrón Factory.
El uso de interfaces de tal manera ha demostrado ser una forma muy confiable de escribir código reutilizable.
Devuelva la interfaz apropiada para ocultar los detalles de la implementación. Sus clientes solo deben preocuparse por lo que ofrece su objeto, no cómo lo implementó. Si comienza con un ArrayList privado y decide más adelante que otra cosa (por ejemplo, LinkedLisk, lista de omisión, etc.) es más apropiada, puede cambiar la implementación sin afectar a los clientes si devuelve la interfaz. En el momento en que devuelves un tipo concreto se pierde la oportunidad.
En general, para una interfaz de cara al público como las API, sería mejor devolver la interfaz (como List
) sobre la implementación concreta (como ArrayList
).
El uso de un ArrayList
o LinkedList
es un detalle de implementación de la biblioteca que debe considerarse para el caso de uso más común de esa biblioteca. Y, por supuesto, internamente, tener métodos private
que entreguen a LinkedList
no sería necesariamente algo malo, si proporciona instalaciones que faciliten el procesamiento.
No hay razón para que no se use una clase concreta en la implementación, a menos que haya una buena razón para creer que alguna otra clase de List
se usará más adelante. Pero, una vez más, cambiar los detalles de la implementación no debería ser tan doloroso siempre que la parte que mira al público esté bien diseñada.
La biblioteca en sí misma debería ser una caja negra para sus consumidores, de modo que no tengan que preocuparse realmente por lo que está sucediendo internamente. Eso también significa que la biblioteca debe diseñarse de modo que se diseñe para que se use de la forma prevista.
En general, use la interfaz en todos los casos si no necesita la funcionalidad de la clase concreta. Tenga en cuenta que para las listas, Java ha agregado una clase de marcador RandomAccess principalmente para distinguir un caso común en el que un algoritmo puede necesitar saber si get (i) es tiempo constante o no.
Para los usos del código, Michael arriba es correcto que ser tan genérico como sea posible en los parámetros del método es a menudo incluso más importante. Esto es especialmente cierto cuando se prueba un método de este tipo.
En la programación OO, queremos encapsular lo más posible los datos. Oculte lo más posible la implementación real, abstrayendo los tipos lo más alto posible.
En este contexto, solo respondería devolviendo lo que sea significativo . ¿Tiene sentido en absoluto que el valor de retorno sea la clase concreta? En su ejemplo, pregúntese: ¿utilizará alguien un método específico de LinkedList en el valor de retorno de foo?
- Si no, solo usa la interfaz de nivel superior. Es mucho más flexible y te permite cambiar el backend.
- En caso afirmativo, pregúntese: ¿no puedo refactorizar mi código para devolver la interfaz de nivel superior? :)
Cuanto más abstracto sea su código, menos cambios tendrá que hacer cuando cambie un backend. Es tan simple como eso.
Si, por otro lado, terminas echando los valores de retorno a la clase concreta, bueno, eso es una señal fuerte de que probablemente deberías devolver la clase concreta. Sus usuarios / compañeros de equipo no deberían tener que conocer más o menos contratos implícitos: si necesita usar los métodos concretos, simplemente devuelva la clase concreta, para mayor claridad.
En pocas palabras: código abstracto , pero explícitamente :)
Encontrará (o habrá encontrado) que a medida que devuelve las interfaces, éstas penetran a través de su código. por ejemplo, devuelve una interfaz desde el método A y luego tiene que pasar una interfaz al método B.
Lo que estás haciendo es programar por contrato, aunque de manera limitada.
Esto le da un enorme margen para cambiar las implementaciones bajo las coberturas (siempre que estos nuevos objetos cumplan con los contratos / comportamientos esperados existentes).
Dado todo esto, tiene beneficios en términos de elegir su implementación y cómo puede sustituir los comportamientos (incluidas las pruebas, por ejemplo, el uso de burla). En caso de que no lo hayas adivinado, estoy a favor de esto y tratar de reducir (o introducir) las interfaces siempre que sea posible.
La pregunta principal ya ha sido respondida y siempre debe utilizar la interfaz. Sin embargo me gustaría comentar
Es obvio que el uso de la interfaz tiene muchas ventajas (por eso está ahí). En la mayoría de los casos, no importa qué implementación concreta utilice una función de biblioteca. Pero tal vez hay casos en los que sí importa. Por ejemplo, si sé que accederé principalmente a los datos de la lista de forma aleatoria, una lista enlazada sería mala. Pero si mi función de biblioteca solo devuelve la interfaz, simplemente no lo sé. Para estar seguro, incluso podría necesitar copiar la lista explícitamente a un ArrayList.
Si está devolviendo una estructura de datos que sabe que tiene un rendimiento de acceso aleatorio deficiente (O (n) y, por lo general, MUCHA información), hay otras interfaces que debe especificar en lugar de Lista, como Iterable, para que cualquier persona que use la biblioteca tenga en cuenta que solo está disponible el acceso secuencial.
Elegir el tipo correcto para devolver no se trata solo de una interfaz en lugar de una implementación concreta, sino también de seleccionar la interfaz correcta.
Lamento no estar de acuerdo, pero creo que la regla básica es la siguiente:
- Para los argumentos de entrada utilice el más genérico .
- Para los valores de salida , los más específicos .
Entonces, en este caso, desea declarar la implementación como:
public ArrayList<String> foo() {
return new ArrayList<String>();
}
Fundamento : El caso de entrada ya es conocido y explicado por todos: use la interfaz, punto. Sin embargo, el caso de salida puede parecer contrario a la intuición. Desea devolver la implementación porque desea que el cliente tenga más información sobre lo que está recibiendo. En este caso, más conocimiento es más poder .
Ejemplo 1: el cliente quiere obtener el quinto elemento:
- Devolución de la colección: debe iterar hasta el 5º elemento contra la lista de devolución:
- Lista de retorno:
list.get(4)
Ejemplo 2: el cliente quiere eliminar el quinto elemento:
- Lista de retorno: debe crear una nueva lista sin el elemento especificado (
list.remove()
es opcional). - volver ArrayList:
arrayList.remove(4)
Entonces, es una gran verdad que el uso de interfaces es excelente porque promueve la reutilización, reduce el acoplamiento, mejora la capacidad de mantenimiento y hace que la gente sea feliz ... pero solo cuando se usa como entrada .
Entonces, una vez más, la regla se puede establecer como:
- Sé flexible para lo que ofreces.
- Sé informativo con lo que entregas.
Entonces, la próxima vez, por favor devuelva la implementación.
No importa mucho si un método API devuelve una interfaz o una clase concreta; a pesar de lo que todos dicen aquí, casi nunca cambia la clase de implementación una vez que se escribe el código.
Lo que es mucho más importante: ¡siempre use interfaces de alcance mínimo para los parámetros de su método! De esa manera, los clientes tienen máxima libertad y pueden usar clases que su código ni siquiera conoce.
Cuando un método API devuelve ArrayList
, no tengo absolutamente ningún reparo en eso, pero cuando exige un parámetro ArrayList
(o, todos en común, Vector
), considero buscar al programador y hacerle daño, porque significa que no puedo use Arrays.asList()
, Collections.singletonList()
o Collections.EMPTY_LIST
.
Se utiliza la interfaz para abstraer de la implementación real. La interfaz es básicamente solo un plano para lo que puede hacer su implementación.
Las interfaces son un buen diseño porque le permiten cambiar los detalles de la implementación sin tener que temer que alguno de sus consumidores se vean directamente afectados, siempre y cuando la implementación siga haciendo lo que su interfaz dice.
Para trabajar con interfaces, las instanciarías así:
IParser parser = new Parser();
Ahora IParser sería su interfaz, y Parser sería su implementación. Ahora, cuando trabaje con el objeto analizador desde arriba, trabajará contra la interfaz (IParser), que a su vez funcionará contra su implementación (Parser).
Eso significa que puede cambiar el funcionamiento interno de Parser todo lo que quiera, nunca afectará el código que funciona en contra de la interfaz del analizador de IParser.
Sin poder justificarlo con resmas de citas de CS (soy autodidacta), siempre he seguido el mantra de "Acepte lo menos derivado, devuelva lo más derivado" al diseñar las clases y me ha superado. los años.
Supongo que eso significa que en términos de interfaz frente a retorno concreto, si está intentando reducir las dependencias y / o desacoplar, devolver la interfaz generalmente es más útil. Sin embargo, si la clase concreta implementa más que esa interfaz, usualmente es más útil para los llamadores de su método recuperar la clase concreta (es decir, la "más derivada") en lugar de restringirla arbitrariamente a un subconjunto de la funcionalidad de ese objeto devuelto. - A menos que realmente necesites restringirlos. Por otra parte, también podría aumentar la cobertura de la interfaz. Restricciones innecesarias como esta comparadas con el sellado irreflexivo de clases; nunca sabes. Solo para hablar un poco sobre la parte anterior de ese mantra (para otros lectores), aceptar la menos derivada también brinda la máxima flexibilidad para quienes llaman a su método.
-Oisin