Java: declarando desde Tipo de interfaz en lugar de Clase

En mi búsqueda para comprender correctamente las mejores prácticas de la interfaz, he notado declaraciones como:

List myList = new ArrayList(); 

en lugar de

 ArrayList myList = new ArrayList(); 

– A mi entender, la razón es porque permite flexibilidad en caso de que algún día no desee implementar una ArrayList, pero tal vez otro tipo de lista.

Con esta lógica, configuré un ejemplo:

 public class InterfaceTest { public static void main(String[] args) { PetInterface p = new Cat(); p.talk(); } } interface PetInterface { public void talk(); } class Dog implements PetInterface { @Override public void talk() { System.out.println("Bark!"); } } class Cat implements PetInterface { @Override public void talk() { System.out.println("Meow!"); } public void batheSelf() { System.out.println("Cat bathing"); } } 

Mi pregunta es: no puedo acceder al método batheSelf () porque solo existe para Cat. Eso me lleva a creer que solo debería declarar desde una interfaz si solo voy a usar los métodos declarados en la interfaz (y no los métodos adicionales de la subclase); de lo contrario, debería declarar directamente desde la clase (en este caso, Cat). ¿Estoy en lo correcto en esta suposición?

Cuando hay una opción entre referirse a un objeto por su interface o una class , se debe preferir el primero, pero solo si existe un tipo apropiado .

Considere que String implements CharSequence como ejemplo. No debería usar CharSequence a CharSequence en String para todos los casos, porque eso le negaría operaciones simples como trim() , toUpperCase() , etc.

Sin embargo, un método que toma una String solo para preocuparse por su secuencia de valores de char debe usar CharSequence en CharSequence lugar, porque ese es el tipo apropiado en este caso. De hecho, este es el caso con replace(CharSequence target, CharSequence replacement) de la String replace(CharSequence target, CharSequence replacement) en la clase String .

Otro ejemplo es java.util.regex.Pattern y su Matcher matcher(CharSequence) . Esto permite crear un Matcher a partir de Pattern para no solo String , sino también para todas las demás CharSequence que existen.

Un buen ejemplo en la biblioteca de dónde se debería haber usado una interface , pero desafortunadamente no, también se puede encontrar en Matcher : sus métodos appendReplacement y appendTail solo aceptan StringBuffer . Esta clase ha sido reemplazada en gran medida por su primo más rápido StringBuilder desde 1.5.

Un StringBuilder no es un StringBuffer , por lo que no podemos usar el primero con los métodos de append… en Matcher . Sin embargo, ambos implements Appendable (también introducido en 1.5). Idealmente, el método de append… Matcher debería aceptar cualquier Appendable , y entonces podríamos usar StringBuilder , ¡y todos los Appendable disponibles!

Entonces, podemos ver cómo cuando existe un tipo apropiado que se refiere a los objetos por sus interfaces puede ser una abstracción poderosa, pero solo si esos tipos existen. Si el tipo no existe, entonces puede considerar definir uno propio si tiene sentido. En este ejemplo de Cat , puede definir la interface SelfBathable , por ejemplo. Entonces, en lugar de referirse a un Cat , puede aceptar cualquier objeto SelfBathable (por ejemplo, un Parakeet )

Si no tiene sentido crear un nuevo tipo, entonces por supuesto puede referirse a él por su class .

Ver también

  • Effective Java 2nd Edition, Item 52: Referir objetos por sus interfaces

    Si existen tipos de interfaz apropiados, entonces los parámetros, los valores de retorno y los campos deben declararse utilizando tipos de interfaz. Si adquiere el hábito de usar tipos de interfaz, su progtwig será mucho más flexible. Es completamente apropiado hacer referencia a un objeto por una clase si no existe una interfaz adecuada.

Enlaces relacionados

  • ID de error: 5066679 – java.util.regex.Matcher debería hacer un mayor uso de Appendable

Sí, estás en lo correcto. Debe declarar como el tipo más general que proporciona los métodos que utiliza.

Este es el concepto de polymorphism .

Estás en lo correcto, pero puedes lanzar desde la interfaz a la mascota deseada si lo necesitas. Por ejemplo:

 PetInterface p = new Cat(); ((Cat)p).batheSelf(); 

Por supuesto, si intentas lanzar a tu mascota a un perro, no puedes llamar al método batheSelf (). Ni siquiera comstackría. Entonces, para evitar problemas, podrías tener un método como este:

 public void bathe(PetInterface p){ if (p instanceof Cat) { Cat c = (Cat) p; c.batheSelf(); } } 

Cuando uses instanceof , asegúrate de no intentar que un perro se bañe durante el tiempo de ejecución. Lo cual arrojaría un error.

Sí, estás en lo correcto. Al hacer que Cat implemente “PetInterface”, puede usarlo en el ejemplo anterior y agregar fácilmente más tipos de mascotas. Si realmente necesita ser un gato específico, necesita acceder a la clase Cat.

Puede llamar al método batheSelf de talk en Cat.

En general, debe preferir interfaces a clases concretas. En esa línea, si puede evitar usar el nuevo operador (que siempre requiere un tipo concreto como en su nuevo ejemplo ArrayList), aún mejor.

Todo esto tiene que ver con administrar dependencias en su código. Lo mejor es depender solo de cosas muy abstractas (como interfaces) porque también tienden a ser muy estables (ver http://objectmentor.com/resources/articles/stability.pdf ). Debido a que no tienen código, solo deben cambiarse cuando la API cambie … en otras palabras, cuando desee que la interfaz presente un comportamiento diferente para el mundo, es decir, un cambio de diseño.

Las clases, por otro lado, cambian todo el tiempo. Al código que depende de una clase no le importa cómo hace lo que hace, siempre que las entradas y las salidas de la API no cambien, las personas que llaman no deberían preocuparse.

Debes esforzarte por definir el comportamiento de tus clases de acuerdo con el principio de Open-Closed (ver http://objectmentor.com/resources/articles/ocp.pdf ), de esa manera las interfaces existentes no necesitan cambiar incluso cuando agregas funcionalidades, usted puede simplemente especificar una nueva subinterfaz.

La antigua forma de evitar el nuevo operador fue mediante el uso del patrón Abstract Factory, pero eso viene con su propio conjunto de problemas. Mejor es usar una herramienta como Guice que hace dependency injection y prefiere la inyección de constructor. Asegúrese de comprender el Principio de Inversión de Dependencia (consulte http://objectmentor.com/resources/articles/dip.pdf ) antes de comenzar a utilizar la dependency injection. He visto a muchas personas inyectar dependencias inapropiadas y luego se quejan de que la herramienta no les está ayudando … no lo convertirá en un gran progtwigdor, igual tendrá que usarlo adecuadamente.

Ejemplo: estás escribiendo un progtwig que ayuda a los estudiantes a aprender física. En este progtwig, los estudiantes pueden poner una pelota en varios escenarios físicos y observar cómo se comporta: disparar desde un cañón desde un acantilado, sumergirlo en el espacio profundo, etc. Pregunta: desea incluir algo sobre la pesadez de la bola en la Ball API … ¿debería incluir un método getMass () o un método getWeight ()?

El peso depende del entorno en el que se encuentre la pelota. Puede ser conveniente que las personas que llaman puedan llamar a un método y obtener el peso de la pelota donde sea que esté, pero ¿cómo se escribe este método? Cada instancia de bola debe realizar un seguimiento constante de dónde está y cuál es la constante gravitacional actual. Entonces debería preferir getMass (), porque la masa es una propiedad intrínseca de la pelota y no depende de su entorno.

Espera, ¿y si usas simplemente getWeight (Environment)? De esta forma, la instancia de bola puede obtener su g actual del entorno y continuar … mejor aún, ¡puedes usar Guice para inyectar el entorno en el constructor de la bola! Este es el tipo de uso indebido que suelo ver, y la gente termina culpando a Guice por no ser capaz de manejar la dependency injection de la forma más sencilla posible.

El problema no es Guice aquí, es el diseño Ball API. El peso no es una propiedad intrínseca de la pelota, por lo que no es una propiedad que debería ser accesible desde la pelota. En cambio, Ball debería implementar la interfaz MassiveObject con un método getMass (), y Environment debería tener un método llamado getWeightOf (MassiveObject). Intrínseco al medio ambiente es su propia constante gravitacional, por lo que esto es mucho mejor. Y el Medio Ambiente solo depende ahora de una interfaz simple, MassiveObject … pero su función es contener objetos, así que esto es lo que debería ser.

¿Por qué no simplemente hacer esto?

 Cat c = new Cat(); PetInterface p = (PetInterface)c; p.talk(); c.batheSelf(); 

Ahora tenemos un solo objeto, que puede ser manipulado usando 2 referencias.
La referencia p se puede usar para llamar funciones definidas en la interfaz yc se puede usar para llamar a funciones definidas en clase (o superclase) solamente.