Mi comprensión actual de la implementación de la herencia es que solo se debe extender una clase si existe una relación IS-A . Si la clase padre puede tener más tipos secundarios específicos con diferentes funcionalidades pero compartirá elementos comunes abstraídos en el padre.
Estoy cuestionando esa comprensión por lo que mi profesor de Java nos recomienda hacer. Él ha recomendado que para una JSwing
aplicación que estemos construyendo en clase
Uno debe extender todas las JSwing
clases ( JFrame
, JButton
, JTextBox
, etc.) en clases separadas personalizados y especificar la personalización relacionada GUI en ellos (como el tamaño de los componentes, componente, etc.)
Hasta ahora todo bien, pero continúa recomendando que cada JButton debería tener su propia clase extendida personalizada, aunque el único factor distintivo es su etiqueta.
Por ejemplo, si la interfaz gráfica de usuario tiene dos botones Ok y Cancelar . Recomienda que se extiendan de la siguiente manera:
class OkayButton extends JButton{
MainUI mui;
public OkayButton(MainUI mui) {
setSize(80,60);
setText("Okay");
this.mui = mui;
mui.add(this);
}
}
class CancelButton extends JButton{
MainUI mui;
public CancelButton(MainUI mui) {
setSize(80,60);
setText("Cancel");
this.mui = mui;
mui.add(this);
}
}
Como puede ver, la única diferencia está en la setText
función.
Entonces, ¿es esta práctica estándar?
Por cierto, el curso donde se discutió esto se llama Mejores prácticas de programación en Java
[Respuesta del profesor]
Así que discutí el problema con el profesor y planteé todos los puntos mencionados en las respuestas.
Su justificación es que la subclase proporciona código reutilizable mientras sigue los estándares de diseño de la GUI. Por ejemplo, si el desarrollador tiene la costumbre usada Okay
y Cancel
botones en una ventana, será más fácil colocar los mismos botones en otras ventanas también.
Supongo que tengo la razón, pero aún así es solo explotar la herencia y hacer que el código sea frágil.
Más tarde, cualquier desarrollador podría llamar accidentalmente setText
a un Okay
botón y cambiarlo. La subclase se convierte en una molestia en ese caso.
JButton
y llamar a métodos públicos en su constructor, cuando simplemente puede crear ayJButton
llamar a esos mismos métodos públicos fuera de la clase?Respuestas:
Esto viola el Principio de sustitución de Liskov porque
OkayButton
no se puede sustituir en ningún lugar dondeButton
se espera. Por ejemplo, puede cambiar la etiqueta de cualquier botón como desee. Pero hacer eso con unOkayButton
viola sus invariantes internos.Este es un mal uso clásico de la herencia para la reutilización de código. Use un método auxiliar en su lugar.
Otra razón para no hacer esto es que esta es solo una forma complicada de lograr lo mismo que lo haría el código lineal.
fuente
OkayButton
tenga el invariante en el que estás pensando. ElOkayButton
puede reclamar no tener ningún invariantes adicionales, puede simplemente considerar el texto como un defecto y la intención de apoyar plenamente modificaciones en el texto. Todavía no es una buena idea, pero no por eso.activateButton(Button btn)
espera un botón, puede darle fácilmente una instancia deOkayButton
. El principio no significa que tenga que tener exactamente la misma funcionalidad, ya que esto haría que la herencia sea prácticamente inútil.setText(button, "x")
porque eso viola la (supuesta) invariante. Es cierto que este OkayButton en particular podría no tener ninguna invariante interesante, así que supongo que elegí un mal ejemplo. Pero puede ser. Por ejemplo, ¿qué pasa si el controlador de clic diceif (myText == "OK") ProcessOK(); else ProcessCancel();
? Entonces el texto es parte de la invariante.Es completamente terrible en todos los sentidos. Como máximo , use una función de fábrica para producir JButtons. Solo debe heredar de ellos si tiene algunas necesidades serias de extensión.
fuente
JButton
,JCheckBox
,JRadioButton
, es sólo una concesión a los desarrolladores de estar familiarizado con otras herramientas, como el AWT llanura, donde estos tipos son la norma. En principio, todos podrían manejarse con una sola clase de botón. Es la combinación del modelo y el delegado de UI lo que marca la diferencia real.Esta es una forma muy no estándar de escribir código Swing. Por lo general, rara vez se crean subclases de componentes Swing UI, más comúnmente JFrame (para configurar ventanas secundarias y controladores de eventos), pero incluso esa subclasificación es innecesaria y desaconsejada por muchos. La personalización del texto de los botones y demás se realiza generalmente ampliando la clase AbstractAction (o la interfaz Action que proporciona). Esto puede proporcionar texto, iconos y otras personalizaciones visuales necesarias y vincularlas con el código real que representan. Esta es una forma mucho mejor de escribir código UI que los ejemplos que muestra.
(Por cierto, Google Académico nunca ha oído hablar del documento que cita , ¿tiene una referencia más precisa?)
fuente
JPanel
. En realidad, es más útil subclasificar eso queJFrame
, ya que un panel es solo un contenedor abstracto.En mi humilde opinión, las mejores prácticas de programación en Java se definen en el libro de Joshua Bloch, "Java eficaz". Es genial que tu maestro te esté dando ejercicios de OOP y es importante aprender a leer y escribir los estilos de programación de otras personas. Pero fuera del libro de Josh Bloch, las opiniones varían ampliamente sobre las mejores prácticas.
Si va a extender esta clase, también podría aprovechar la herencia. Haga una clase MyButton para administrar el código común y subclase para las partes variables:
¿Cuándo es una buena idea? Cuando se utiliza el tipo que ha creado! Por ejemplo, si tiene una función para crear una ventana emergente y la firma es:
Esos tipos que acaba de crear no están haciendo ningún bien. Pero:
Ahora has creado algo útil.
El compilador verifica que showPopUp toma un OkButton y un CancelButton. Alguien que lea el código sabe cómo se debe usar esta función porque este tipo de documentación causará un error en tiempo de compilación si no está actualizado. Este es un beneficio MAYOR. Los estudios empíricos 1 o 2 de los beneficios de la seguridad de tipo encontraron que la comprensión del código humano era el único beneficio cuantificable.
Esto también evita errores cuando invierte el orden de los botones que pasa a la función. Estos errores son difíciles de detectar, pero también son bastante raros, por lo que este es un beneficio MENOR. Esto se usó para vender seguridad de tipo, pero no se ha demostrado empíricamente que sea particularmente útil.
La primera forma de la función es más flexible porque mostrará dos botones. A veces, eso es mejor que restringir qué tipo de botones tomará. Solo recuerde que debe probar la función de cualquier botón en más circunstancias: si solo funciona cuando le pasa exactamente el tipo correcto de botones, no le está haciendo ningún favor a nadie al pretender que tomará cualquier tipo de botón.
El problema con la programación orientada a objetos es que pone el carro antes que el caballo. Primero debe escribir la función para saber cuál es la firma para saber si vale la pena crear tipos para ella. Pero en Java, primero debe hacer sus tipos para poder escribir la firma de la función.
Por esta razón, es posible que desee escribir un código Clojure para ver cuán increíble puede ser escribir sus funciones primero. Puede codificar rápidamente, pensando en la complejidad asintótica tanto como sea posible, y la suposición de inmutabilidad de Clojure evita errores tanto como los tipos en Java.
Todavía soy un fanático de los tipos estáticos para grandes proyectos y ahora uso algunas utilidades funcionales que me permiten escribir funciones primero y nombrar mis tipos más tarde en Java . Solo un pensamiento.
PD: hice que el
mui
puntero sea definitivo; no es necesario que sea mutable.fuente
Estaría dispuesto a apostar que su maestro realmente no cree que siempre deba extender un componente Swing para usarlo. Apuesto a que solo están usando esto como un ejemplo para obligarlo a practicar la extensión de clases. Todavía no me preocuparía demasiado por las mejores prácticas del mundo real.
Dicho esto, en el mundo real favorecemos la composición sobre la herencia .
Su regla de "uno solo debe extender una clase si hay una relación IS-A" está incompleta. Debería terminar con "... y necesitamos cambiar el comportamiento predeterminado de la clase" en letras grandes y en negrita.
Sus ejemplos no se ajustan a ese criterio. No tiene que ir a
extend
laJButton
clase solo para configurar su texto. No tiene que ir aextend
laJFrame
clase solo para agregarle componentes. Puede hacer estas cosas bien utilizando sus implementaciones predeterminadas, por lo que agregar herencia es solo agregar complicaciones innecesarias.Si veo una clase que
extends
otra clase, me preguntaré qué está cambiando esa clase . Si no está cambiando nada, no me haga mirar a través de la clase.Volviendo a su pregunta: ¿ cuándo debería extender una clase Java? Cuando tienes una muy, muy, muy buena razón para extender una clase.
Aquí hay un ejemplo específico: una forma de hacer pintura personalizada (para un juego o animación, o solo para un componente personalizado) es extendiendo la
JPanel
clase. (Más información sobre eso aquí .) Extiende laJPanel
clase porque necesita anular lapaintComponent()
función. Realmente estás cambiando el comportamiento de la clase al hacer esto. No puede hacer pintura personalizada con laJPanel
implementación predeterminada .Pero, como dije, su maestro probablemente solo esté usando estos ejemplos como una excusa para obligarlo a practicar extender las clases.
fuente
Border
que pinta decoraciones adicionales. O cree ayJLabel
pase unaIcon
implementación personalizada . No es necesario subclaseJPanel
para pintar (y por qué enJPanel
lugar deJComponent
).