Quando dovrei estendere una classe Java Swing?

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ò ulteriormente avere tipi di 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 di Java ci sta raccomandando di fare. Ha raccomandato che per un'applicazione JSwing stiamo costruendo in classe

Si dovrebbero estendere tutte le classi JSwing ( JFrame , JButton , JTextBox , ecc.) in classi personalizzate separate e specificare la personalizzazione relativa alla GUI (come la dimensione del componente, l'etichetta del componente, ecc.)

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

Ad es. Se la GUI ha due pulsanti Ok e Annulla . Raccomanda che dovrebbero essere estesi come segue:

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 funzione setText .

Quindi questa pratica standard?

Btw, il corso in cui è stato discusso si chiama Le migliori pratiche di programmazione in Java

[Risposta dal Prof]

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

La sua giustificazione è che la sottoclasse fornisce codice riusabile mentre segue gli standard di progettazione della GUI. Ad esempio, se lo sviluppatore ha utilizzato i pulsanti Okay e Cancel personalizzati in una finestra, sarà più semplice posizionare gli stessi pulsanti anche in altri Windows.

Ho il motivo per cui suppongo, ma comunque si tratta solo di sfruttare l'ereditarietà e di rendere il codice fragile.

In seguito, qualsiasi sviluppatore potrebbe chiamare accidently il setText su un pulsante Okay e modificarlo. In questo caso la sottoclasse diventa semplicemente fastidiosa.

    
posta Paras Singh Laddi 04.05.2016 - 09:14
fonte

5 risposte

21

Questo viola il Principio di sostituzione di Liskov perché un OkayButton non può essere sostituito in nessun posto a cui è atteso un Button . Ad esempio, puoi cambiare l'etichetta di qualsiasi pulsante come preferisci. Ma farlo con un OkayButton viola i suoi invarianti interni.

Questo è un classico abuso di ereditarietà per il riutilizzo del codice. Utilizza invece un metodo di supporto.

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

    
risposta data 04.05.2016 - 19:56
fonte
50

È completamente terribile in ogni modo possibile. In la maggior parte , utilizza una funzione di fabbrica per produrre JButtons. Dovresti ereditare da loro solo se hai delle gravi esigenze di estensione.

    
risposta data 04.05.2016 - 09:21
fonte
12

Questo è un modo molto diverso di scrivere codice Swing. Solitamente, raramente crei sottoclassi dei componenti dell'interfaccia utente di Swing, più comunemente JFrame (per impostare finestre figlio e gestori eventi), ma anche quella sottoclasse non è necessaria e scoraggiata da molti. Fornire la personalizzazione del testo ai pulsanti e così via viene in genere eseguita estendendo la classe AbstractAction (o Interfaccia di azione fornisce). Questo può fornire testo, icone e altra personalizzazione visiva necessaria e collegarli al codice effettivo che rappresentano. Questo è un modo molto migliore per scrivere codice UI rispetto agli esempi che mostri.

(a proposito, google studioso non ha mai sentito parlare della carta che hai citato - hai un riferimento più preciso?)

    
risposta data 04.05.2016 - 10:53
fonte
10

IMHO, le migliori pratiche di programmazione in Java sono definite dal libro di Joshua Bloch, "Effective Java". È bello che il tuo insegnante ti stia dando degli esercizi 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 molto sulle migliori pratiche.

Se estenderai questa classe, potresti trarre vantaggio dall'ereditarietà. Crea una classe MyButton per gestire il codice comune e sottoclassalo 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 hai 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 facendo nulla di buono. Ma:

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

Ora hai creato qualcosa di utile.

  1. Il compilatore verifica che showPopUp acquisisca un OkButton e un CancelButton. Qualcuno sta leggendo il codice sa come utilizzare questa funzione perché questo tipo di documentazione causerà un errore in fase di compilazione se non è aggiornato. Questo è un vantaggio importante. Gli 1 o 2 studi empirici sui benefici della sicurezza del tipo hanno rilevato che la comprensione del codice umano era l'unico beneficio quantificabile.

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

  3. La prima forma della funzione è più flessibile perché mostrerà due pulsanti qualsiasi. A volte è meglio che limitare il tipo di pulsanti necessari. Ricorda solo che devi testare la funzione di qualsiasi pulsante in più circostanze - se funziona solo quando si passa esattamente il tipo giusto di pulsanti non stai facendo alcun favore a qualcuno facendo finta di prendere qualsiasi tipo di pulsante.

Il problema con la programmazione orientata agli oggetti è che mette il carrello davanti ai buoi. Per prima cosa è necessario scrivere la funzione per sapere qual è la firma sapere se vale la pena di 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 velocemente, pensando alla complessità asintotica il più possibile, e l'assunzione di immutabilità di Clojure previene i bug tanto quanto i tipi in Java.

Sono ancora un fan dei tipi statici per i progetti di grandi dimensioni e ora utilizzo alcune utilità funzionali che mi consentono di scrivi prima le funzioni e dai il nome ai miei tipi più tardi in Java . Solo un pensiero.

P.S. Ho fatto il puntatore mui final - non ha bisogno di essere mutabile.

    
risposta data 04.05.2016 - 13:52
fonte
8

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

Detto questo, nel mondo reale prediligiamo composizione sull'ereditarietà .

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

I tuoi esempi non corrispondono a questi criteri. Non devi extend della classe JButton solo per impostare il testo. Non devi extend della classe JFrame solo per aggiungere componenti ad esso. Puoi fare queste cose bene usando le loro implementazioni predefinite, quindi aggiungere l'ereditarietà è solo aggiungere complicazioni inutili.

Se vedo una classe che extends un'altra classe, mi chiedo quale sia la classe che sta cambiando . Se non stai cambiando nulla, non farmi assolutamente sfogliare la classe.

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

Ecco un esempio specifico: un modo di fare pittura personalizzata (per un gioco o un'animazione o solo per un componente personalizzato) è l'estensione della classe JPanel . (Maggiori informazioni su questo qui .) Estendi la classe JPanel perché devi sostituisci la funzione paintComponent() . In questo modo stai cambiando il comportamento della classe . Non puoi eseguire la verniciatura personalizzata con l'implementazione predefinita JPanel .

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

    
risposta data 04.05.2016 - 18:42
fonte