Quando devo estendere una classe Swing Java?


35

La mia attuale comprensione dell'implementazione dell'ereditarietà è che si dovrebbe estendere una classe solo se è presente una relazione IS-A . Se la classe genitore può inoltre avere tipi figlio più specifici con funzionalità diverse ma condividerà elementi comuni astratti nel genitore.

Sto mettendo in dubbio questa comprensione a causa di ciò che il mio professore Java ci sta raccomandando di fare. Ha raccomandato che per JSwingun'applicazione stiamo costruendo in classe

Si dovrebbe estendere tutte le JSwingclassi ( JFrame, JButton, JTextBox, ecc) in classi personalizzate separati e specificare la personalizzazione correlata GUI in loro (come la dimensione dei componenti, dell'etichetta del componente, ecc)

Fin qui tutto bene, ma continua a consigliare che ogni JButton dovrebbe avere la sua classe estesa personalizzata anche se l'unico fattore distintivo è la loro etichetta.

Ad esempio, se la GUI ha due pulsanti OK e Annulla . Si consiglia di estenderli come di seguito:

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);        
    }
}

Come puoi vedere, l'unica differenza è nella setTextfunzione.

Quindi è questa pratica standard?

A proposito, il corso in cui questo è stato discusso si chiama Best Programming Practices in Java

[Risposta del Prof]

Quindi ho discusso il problema con il professore e ho sollevato tutti i punti citati nelle risposte.

La sua giustificazione è che la sottoclasse fornisce codice riutilizzabile seguendo gli standard di progettazione della GUI. Ad esempio, se lo sviluppatore ha utilizzato pulsanti personalizzati Okaye Cancelin una finestra, sarà più facile posizionare gli stessi pulsanti anche in altre finestre.

Presumo il motivo suppongo, ma sta ancora sfruttando l' eredità e rendendo fragile il codice.

In seguito, ogni sviluppatore potrebbe accidentalmente chiamare il setTextsu un Okaytasto e di modificarlo. La sottoclasse diventa solo fastidio in quel caso.


3
Perché estendere JButtone chiamare metodi pubblici nel suo costruttore, quando puoi semplicemente creare JButtone chiamare quegli stessi metodi pubblici al di fuori della classe?

17
stai lontano da quel professore se puoi. Questo è piuttosto lontano dall'essere le migliori pratiche . La duplicazione del codice come hai illustrato è un odore pessimo.
njzk2,

7
Chiedigli semplicemente perché, non esitare a comunicare qui le sue motivazioni.
Alex,

9
Voglio votare a fondo perché è un codice terribile, ma voglio anche votare perché è fantastico che tu stia mettendo in discussione invece di accettare ciecamente ciò che dice un cattivo professore.
Fondi Monica's Lawsuit,

1
@Alex Ho aggiornato la domanda con la giustificazione del prof
Paras

Risposte:


21

Ciò viola il Principio di sostituzione di Liskov poiché OkayButtonnon è possibile sostituirlo in nessun luogo Buttonprevisto. Ad esempio, puoi modificare l'etichetta di qualsiasi pulsante come preferisci. Ma farlo con un OkayButtonviola i suoi invarianti interni.

Questo è un classico uso improprio dell'ereditarietà per il riutilizzo del codice. Utilizzare invece un metodo di supporto.

Un altro motivo per non farlo è che questo è solo un modo contorto di ottenere la stessa cosa che farebbe il codice lineare.


Questo non viola LSP a meno che non OkayButtonabbia l'invariante che stai pensando. L' OkayButtonpuò affermare di non avere invarianti in più, si può solo considerare il testo come un default e l'intenzione di sostenere pienamente le modifiche al testo. Non è ancora una buona idea, ma non per questo motivo.
hvd,

Non lo viola perché puoi. Cioè, se un metodo activateButton(Button btn)prevede un pulsante, puoi facilmente dargli un'istanza di OkayButton. Il principio non significa che debba avere esattamente la stessa funzionalità, in quanto ciò renderebbe l'eredità praticamente inutile.
Sebb,

1
@Sebb ma non puoi usarlo con una funzione setText(button, "x")perché viola l'invariante (presunto). È vero che questo particolare OkayButton potrebbe non avere alcun invariante interessante, quindi immagino di aver scelto un cattivo esempio. Ma potrebbe. Ad esempio, cosa succede se il gestore dei clic dice if (myText == "OK") ProcessOK(); else ProcessCancel();? Quindi il testo fa parte dell'invariante.
usr

@usr Non pensavo che il testo fosse parte dell'invariante. Hai ragione, allora è stato violato.
Sebb,

50

È completamente terribile in ogni modo possibile. Al massimo , utilizzare una funzione di fabbrica per produrre JButton. Dovresti ereditare da loro solo se hai delle serie esigenze di estensione.


20
+1. Per ulteriori riferimenti, guarda la gerarchia di classi Swing di AbstractButton . Quindi guarda la gerarchia di JToggleButton . L'ereditarietà viene utilizzata per concettualizzare diversi tipi di pulsante. Ma non viene utilizzato per differenziare i concetti aziendali ( Sì, No, Continua, Annulla ... ).
Laiv

2
@Laiv: anche avendo tipi distinti, per esempio JButton, JCheckBox, JRadioButton, è solo una concessione agli sviluppatori avere familiarità con altri toolkit, come AWT piana, dove tali tipi sono lo standard. In linea di principio, tutti potrebbero essere gestiti da un'unica classe di pulsanti. È la combinazione del modello e del delegato dell'interfaccia utente che fa la differenza.
Holger,

Sì, potrebbero essere gestiti da un solo pulsante. Tuttavia, la gerarchia effettiva può essere esposta come buone pratiche. Creare un nuovo pulsante o semplicemente delegare il controllo di eventi e comportamenti ad altri componenti è una questione di preferenze. Se fossi in me, estenderei i componenti Swing per modellare il mio pulsante owm e incapsulare i suoi comportamenti, azioni, ... Solo per semplificarne l'implementazione nel progetto e renderlo facile per i giovani. In ambito web preferisco i componenti web a una gestione eccessiva degli eventi. In ogni modo. Hai ragione, possiamo disaccoppiare l'interfaccia utente da Behaivors.
Laiv

12

Questo è un modo molto non standard di scrivere il codice Swing. In genere, si creano solo raramente sottoclassi di componenti dell'interfaccia utente di Swing, più comunemente JFrame (al fine di impostare finestre figlio e gestori di eventi), ma anche tale sottoclasse non è necessaria e scoraggiata da molti. Fornire la personalizzazione del testo ai pulsanti e così via viene di solito eseguita estendendo la classe AbstractAction (o l' interfaccia Action che fornisce). Ciò può fornire testo, icone e altre personalizzazioni visive necessarie e collegarle al codice reale che rappresentano. Questo è un modo molto migliore di scrivere il codice dell'interfaccia utente rispetto agli esempi che mostri.

(a proposito, google scholar non ha mai sentito parlare del documento che citi : hai un riferimento più preciso?)


Qual è esattamente il "modo standard"? Concordo sul fatto che scrivere una sottoclasse per ogni componente sia probabilmente una cattiva idea, ma i tutorial ufficiali di Swing favoriscono ancora questa pratica. Credo che il professore segua semplicemente lo stile mostrato nella documentazione ufficiale del framework.
VIENI DAL

@COMEFROM Devo ammettere di non aver letto l'intero tutorial di Swing, quindi forse mi sono perso dove viene usato questo stile, ma le pagine che ho visto tendono ad usare le classi di base e non guidare le sottoclassi, ad esempio docs.oracle .com / javase / tutorial / uiswing / components / button.html
Jules

@Jules mi dispiace per la confusione, intendevo che il corso / documento che il professore insegna si chiama Best Practices in Java
Paras

1
In generale è vero, ma c'è un altro grande eccezione - JPanel. In realtà è più utile sottoclassarlo che JFrame, dato che un pannello è solo un contenitore astratto.
Ordous,

10

IMHO, Le migliori pratiche di programmazione in Java sono definite dal libro di Joshua Bloch, "Effective Java". È bello che il tuo insegnante ti dia esercizi di OOP ed è importante imparare a leggere e scrivere gli stili di programmazione di altre persone. Ma al di fuori del libro di Josh Bloch, le opinioni variano abbastanza ampiamente sulle migliori pratiche.

Se hai intenzione di estendere questa classe, potresti anche approfittare dell'eredità. Crea una classe MyButton per gestire il codice comune e sottoclassarlo per le parti variabili:

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");
    }
}

Quando è una buona idea? Quando usi i tipi che hai creato! Ad esempio, se si dispone di una funzione per creare una finestra pop-up e la firma è:

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

Quei tipi che hai appena creato non stanno andando bene. Ma:

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

Ora hai creato qualcosa di utile.

  1. Il compilatore verifica che showPopUp accetta un OkButton e un CancelButton. Qualcuno che legge il codice sa come usare questa funzione perché questo tipo di documentazione provocherà un errore di compilazione se non è aggiornato. Questo è un vantaggio MAJOR. Gli studi empirici 1 o 2 sui benefici della sicurezza del tipo hanno scoperto che la comprensione del codice umano era l'unico vantaggio quantificabile.

  2. Ciò impedisce anche errori in cui si inverte l'ordine dei pulsanti passati alla funzione. Questi errori sono difficili da individuare, ma sono anche piuttosto rari, quindi questo è un vantaggio MINORE. Questo è stato usato per vendere la sicurezza dei tipi, ma non è stato dimostrato empiricamente particolarmente utile.

  3. La prima forma della funzione è più flessibile perché mostrerà due pulsanti qualsiasi. A volte è meglio che limitare il tipo di pulsanti che richiederà. Ricorda solo che devi testare la funzione di qualsiasi pulsante in più circostanze - se funziona solo quando le passi esattamente il giusto tipo di pulsanti, non stai facendo alcun favore a nessuno fingendo che prenda qualsiasi tipo di pulsante.

Il problema con la programmazione orientata agli oggetti è che mette il carrello davanti al cavallo. Devi prima scrivere la funzione per sapere cosa deve sapere la firma se vale la pena crearne dei tipi. Ma in Java, devi prima creare i tuoi tipi in modo da poter scrivere la firma della funzione.

Per questo motivo, potresti voler scrivere del codice Clojure per vedere quanto può essere fantastico scrivere prima le tue funzioni. Puoi programmare rapidamente, pensando il più possibile alla complessità asintotica, e l'assunzione di immutabilità di Clojure previene i bug tanto quanto i tipi fanno in Java.

Sono ancora un fan dei tipi statici per grandi progetti e ora uso alcune utility funzionali che mi consentono di scrivere prima le funzioni e di nominare i miei tipi in seguito in Java . Solo un pensiero.

PS Ho reso il muipuntatore definitivo - non deve essere mutabile.


6
Dei 3 punti, 1 e 3 possono essere gestiti anche con JButton generici e uno schema di denominazione dei parametri di input appropriato. il punto 2 è in realtà un aspetto negativo in quanto rende il codice più strettamente accoppiato: cosa succede se si desidera invertire l'ordine dei pulsanti? Cosa succede se, invece dei pulsanti OK / Annulla, si desidera utilizzare i pulsanti Sì / No? Farai un metodo showPopup completamente nuovo che accetta YesButton e Nobutton? Se usi semplicemente i JButton predefiniti, non devi prima creare i tuoi tipi perché già esistono.
Nzall

Estendendo ciò che ha detto @NateKerkhofs, un moderno IDE ti mostrerà i nomi degli argomenti formali mentre scrivi le espressioni reali.
Solomon Slow

8

Sarei disposto a scommettere che il tuo insegnante in realtà non crede che dovresti sempre estendere un componente Swing per usarlo. Scommetto che stanno solo usando questo come esempio per costringerti a esercitarti ad estendere le lezioni. Non mi preoccuperei ancora troppo delle migliori pratiche del mondo reale.

Detto questo, nel mondo reale preferiamo la composizione rispetto all'eredità .

La tua regola di "uno dovrebbe estendere una classe solo se è presente una relazione IS-A" è incompleta. Dovrebbe terminare con "... e dobbiamo cambiare il comportamento predefinito della classe" in grandi lettere in grassetto.

I tuoi esempi non soddisfano tali criteri. Non devi seguire extendla JButtonlezione solo per impostarne il testo. Non è necessario per extendla JFrameclasse solo per aggiungere componenti ad essa. Puoi fare queste cose bene usando le loro implementazioni predefinite, quindi aggiungere l'ereditarietà è solo aggiungere complicazioni inutili.

Se vedo una classe che extendsun'altra classe, mi chiederò cosa sta cambiando quella classe . Se non stai cambiando nulla, non farmi guardare attraverso la classe.

Torna alla tua domanda: quando dovresti estendere una classe Java? Quando hai un motivo davvero molto valido per estendere una lezione.

Ecco un esempio specifico: un modo per eseguire la pittura personalizzata (per un gioco o un'animazione o solo per un componente personalizzato) è estendere la JPanelclasse. (Maggiori informazioni qui ). Estendi la JPanelclasse perché devi sovrascrivere la paintComponent()funzione. In questo modo stai cambiando il comportamento della classe . Non è possibile eseguire la pittura personalizzata con l' JPanelimplementazione predefinita .

Ma come ho detto, il tuo insegnante probabilmente sta solo usando questi esempi come scusa per costringerti a esercitarti ad estendere le lezioni.


1
Oh aspetta, puoi impostare uno Borderche dipinge decorazioni aggiuntive. Oppure crea un JLabele passa Iconun'implementazione personalizzata ad esso. Non è necessario sottoclassare JPanelper la pittura (e perché JPanelinvece di JComponent).
Holger,

1
Penso che tu abbia l'idea giusta qui, ma probabilmente potresti scegliere esempi migliori.

1
Ma voglio ribadire che la tua risposta è corretta e fai buoni punti . Penso solo che l'esempio specifico e il tutorial lo supportino debolmente.

1
@KevinWorkman Non conosco lo sviluppo del gioco Swing, ma ho fatto alcune interfacce utente personalizzate in Swing e "non estendere le classi Swing, quindi è facile collegare componenti e renderer tramite la configurazione" è abbastanza standard lì.

1
"Scommetto che stanno solo usando questo come esempio per costringerti a ..." Uno dei miei maggiori problemi! Gli istruttori che insegnano come fare X utilizzando esempi orribilmente squilibrati di motivo di fare X.
Solomon lento
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.