¿Cuándo debería extender una clase Java Swing?

35

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 JSwingaplicación que estemos construyendo en clase

Uno debe extender todas las JSwingclases ( 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 setTextfunció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 Okayy Cancelbotones 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 setTexta un Okaybotón y cambiarlo. La subclase se convierte en una molestia en ese caso.

Paras
fuente
3
¿Por qué extender JButtony llamar a métodos públicos en su constructor, cuando simplemente puede crear ay JButtonllamar a esos mismos métodos públicos fuera de la clase?
17
mantente alejado de ese profesor si puedes. Esto está bastante lejos de ser las mejores prácticas . La duplicación de código como ilustraste es un mal olor.
njzk2
77
Simplemente pregúntele por qué, no dude en comunicar su motivación aquí.
Alex
99
Quiero votar en contra porque es un código horrible, pero también quiero votar porque es genial que estés cuestionando en lugar de aceptar ciegamente lo que dice un mal profesor.
Financia la demanda de Mónica
1
@Alex He actualizado la pregunta con la justificación del profesor
Paras

Respuestas:

21

Esto viola el Principio de sustitución de Liskov porque OkayButtonno se puede sustituir en ningún lugar donde Buttonse espera. Por ejemplo, puede cambiar la etiqueta de cualquier botón como desee. Pero hacer eso con un OkayButtonviola 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.

usr
fuente
Esto no viola el LSP a menos que OkayButtontenga el invariante en el que estás pensando. El OkayButtonpuede 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.
hvd
No lo viola porque puedes. Es decir, si un método activateButton(Button btn)espera un botón, puede darle fácilmente una instancia de OkayButton. 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.
Sebb
1
@Sebb pero no puede usarlo con una función 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 dice if (myText == "OK") ProcessOK(); else ProcessCancel();? Entonces el texto es parte de la invariante.
usr
@usr No pensé en que el texto fuera parte de la invariante. Tienes razón, se viola entonces.
Sebb
50

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.

DeadMG
fuente
20
+1. Para más referencia, mire la jerarquía de clases Swing de AbstractButton . Luego mira la jerarquía de JToggleButton . La herencia se utiliza para conceptualizar diferentes tipos de botones. Pero no se utiliza para diferenciar conceptos de negocio ( Sí, No, Continuar, Cancelar ... ).
Laiv
2
@Laiv: ni siquiera tener tipos distintos de, por ejemplo 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.
Holger
Sí, podrían manejarse con un solo botón. Sin embargo, la jerarquía real puede exponerse como buenas prácticas. Crear un nuevo botón o simplemente delegar el control de eventos y comportamientos a otros componentes es cuestión de preferencias. Si fuera yo, ampliaría los componentes de Swing para modelar mi botón de búho y encapsular sus comportamientos, acciones, ... Solo para facilitar su implementación en el proyecto y facilitar a los jóvenes. En el entorno web, prefiero los componentes web sobre el manejo excesivo de eventos. De todos modos Tienes razón, podemos desacoplar la interfaz de usuario de los comportamientos.
Laiv
12

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?)

Jules
fuente
¿Qué es exactamente la "forma estándar"? Estoy de acuerdo en que escribir una subclase para cada componente es probablemente una mala idea, pero los tutoriales oficiales de Swing aún favorecen esta práctica. Creo que el profesor solo sigue el estilo que se muestra en la documentación oficial del marco.
VENIDO DEL
@COMEFROM Admito que no he leído todo el tutorial de Swing, por lo que tal vez me haya perdido dónde se usa este estilo, pero las páginas que he visto tienden a usar las clases base y no a conducir subclases, por ejemplo, docs.oracle .com / javase / tutorial / uiswing / components / button.html
Julio
@Jules perdón por la confusión, quise decir que el curso / trabajo que enseña el profesor se llama Mejores Prácticas en Java
Paras
1
En general, es cierto, pero hay otra excepción importante - JPanel. En realidad, es más útil subclasificar eso que JFrame, ya que un panel es solo un contenedor abstracto.
Ordous
10

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:

class MyButton extends JButton{
    protected final MainUI mui;
    public MyButton(MainUI mui, String text) {
        setSize(80,60);
        setText(text);
        this.mui = mui;
        mui.add(this);        
    }
}

class OkayButton extends MyButton{
    public OkayButton(MainUI mui) {
        super(mui, "Okay");
    }
}

class CancelButton extends MyButton{
    public CancelButton(MainUI mui) {
        super(mui, "Cancel");
    }
}

¿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:

public void showPopUp(String text, JButton ok, JButton cancel)

Esos tipos que acaba de crear no están haciendo ningún bien. Pero:

public void showPopUp(String text, OkButton ok, CancelButton cancel)

Ahora has creado algo útil.

  1. 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.

  2. 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.

  3. 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 muipuntero sea definitivo; no es necesario que sea mutable.

GlenPeterson
fuente
66
De sus 3 puntos, 1 y 3 también pueden manejarse con JButtons genéricos y un esquema de nombres de parámetros de entrada adecuado. el punto 2 es en realidad una desventaja, ya que hace que su código esté más estrechamente acoplado: ¿qué sucede si desea que se invierta el orden del botón? ¿Qué sucede si, en lugar de los botones Aceptar / Cancelar, desea utilizar los botones Sí / No? ¿Creará un método showPopup completamente nuevo que tome un YesButton y un Nobutton? Si solo usa JButtons predeterminados, no necesita hacer sus tipos primero porque ya existen.
Nzall
Extendiendo lo que dijo @NateKerkhofs, un IDE moderno le mostrará los nombres de los argumentos formales mientras escribe las expresiones reales.
Solomon Slow
8

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 extendla JButtonclase solo para configurar su texto. No tiene que ir a extendla JFrameclase 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 extendsotra 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 JPanelclase. (Más información sobre eso aquí .) Extiende la JPanelclase porque necesita anular la paintComponent()función. Realmente estás cambiando el comportamiento de la clase al hacer esto. No puede hacer pintura personalizada con la JPanelimplementación predeterminada .

Pero, como dije, su maestro probablemente solo esté usando estos ejemplos como una excusa para obligarlo a practicar extender las clases.

Kevin Workman
fuente
1
Oh, espera, puedes configurar un Borderque pinta decoraciones adicionales. O cree ay JLabelpase una Iconimplementación personalizada . No es necesario subclase JPanelpara pintar (y por qué en JPanellugar de JComponent).
Holger
1
Creo que tiene la idea correcta aquí, pero probablemente podría elegir mejores ejemplos.
1
Pero sí quiero reiterar que su respuesta es correcta y usted hace buenos puntos . Solo creo que el ejemplo específico y el tutorial lo apoyan débilmente.
1
@KevinWorkman No sé sobre el desarrollo del juego Swing, pero he hecho algunas IU personalizadas en Swing y "no extender las clases Swing, por lo que es fácil conectar componentes y renderizadores a través de config" es bastante estándar allí.
1
"Apuesto a que solo están usando esto como un ejemplo para forzarte a ..." ¡Una de mis principales molestias! Instructores que enseñan cómo hacer X usando ejemplos horriblemente trastornados de por qué hacer X.
Solomon Slow