È considerato "ragionevole" implementare una parte importante di un programma in gran parte sottoclassi di una classe di libreria? [chiuso]

1

Sto pensando di implementare un word processor con Java e Swing per la GUI.

Stavo pensando a come implementare l'area di testo in cui l'utente digita il testo.

La mia idea era di sottoclasse JTextArea . Ha già un sacco di funzionalità per l'impostazione del testo, la modifica della dimensione del carattere, ecc. Aggiungerò tutte le funzionalità aggiuntive, come la possibilità di essere influenzato dalla pressione dei pulsanti sull'interfaccia utente, ecc.

Questo mi sembra ragionevole, tuttavia ho una domanda su questo:

È considerato ragionevole in un programma "serio", per implementare una parte importante del programma sottoclasse una classe esistente di una libreria comune? È una cosa "seria" e "professionale" fare?

Ovviamente gli sviluppatori professionisti usano sempre e sottoclasse il codice esistente per tutto il tempo, inutile reinventare la ruota.

Ma qui sto parlando di implementare una parte importante dell'applicazione , probabilmente la parte più grande, in gran parte sottoclassi di una classe di librerie esistente.

Non sono un professionista e so che non devo lavorare "come fanno i professionisti". Ma sono interessato a come i professionisti vedono questo.

1 - Qualcosa di simile è considerato ragionevole in un'applicazione 'professionale'?

2 - L'hai mai visto in pratica in un'applicazione "professionale"?

    
posta Aviv Cohn 27.04.2014 - 16:00
fonte

4 risposte

7

In primo luogo, non conosco Swing e le capacità o le potenzialità di JTextArea , quindi non posso commentare la tua decisione di renderlo il punto centrale della tua soluzione (che @ DanPichelman sembra mettere in discussione nella sua risposta). Per l'argomento, suppongo che vada bene.

Qualunque sia il problema, i dettagli tecnici sono discussi meglio su StackOverflow - ci concentriamo sul design generale qui, quindi cercherò di affrontare l'aspetto OOD della tua domanda.

Devi assicurarti che la tua classe non diventi troppo grande. Può seriamente danneggiare la manutenibilità del tuo codice. Come fai a sapere quando è troppo grande?

Il fattore più semplice è la dimensione del suo codice. Non esiste un numero magico che separa "troppo grande" da "ancora buono", ma c'è il buon senso e alcune regole empiriche. Sono abbastanza liberale in questo aspetto, ma secondo i miei standard qualsiasi classe è più grande di 200 o 300 righe di codice alza una bandiera rossa.

La regola concettuale è il principio di singola responsabilità . Qual è la responsabilità della tua classe: diamo un titolo funzionante di JRichTextArea ?

Sta visualizzando un rich text. Visualizzazione . Questo è il suo campo di competenza. Non dovrebbe sapere nulla del mondo esterno, poiché rompe incapsulamento e aggiunge un secondario responsabilità ad esso.

L'unica forma in cui la sua "capacità di essere influenzata dai pulsanti sull'interfaccia utente" dovrebbe essere implementata sta esponendo alcune API pubbliche che consentono ad altri agenti di passarle il contenuto (in un formato comprensibile) che è in grado di visualizzare o renderizzare. Non c'è alcun motivo sotto il sole perché dovrebbe conoscere le pressioni dei pulsanti sull'interfaccia utente, o l'esistenza stessa di qualsiasi pulsante, per quella materia.

Ad esempio, le regole di formattazione (in questo caso la logica aziendale) non sono di sua competenza. JRichTextArea è una vista e non dovrebbe essere altro che. La corretta visualizzazione è stupida e non ha alcuna opinione sui dati visualizzati. L'alternativa è nota come anti-pattern di vista intelligente.

Anche il rendering dei tuoi contenuti non è necessariamente il suo lavoro. Se si desidera aggiungere un'opzione che consente all'utente di creare e modificare complesse equazioni matematiche, il meccanismo dovrebbe essere incorporato in JRichTextArea ?

Queste equazioni non verrebbero disegnate solo sullo schermo, all'interno dell'area di testo. Per stampare i documenti è necessario disegnarli in un buffer grafico. Pertanto, sarebbe meglio spostare il meccanismo in una classe EquationsRenderer separata.

E riguardo il contenuto stesso; il testo formattato? Sono dati - non è uguale alla sua rappresentazione visiva. Probabilmente l'utente avrà bisogno di salvare i documenti formattati sul disco. In realtà stanno andando a salvare i dati, non la sua rappresentazione grafica da JRichTextArea .

Il contenuto appartiene quindi a una sorta di classe Model. Ciò sarebbe coerente con il pattern MVC . Non è un Santo Graal, ma un buon modo universalmente riconosciuto per raggiungere la separazione delle preoccupazioni.

Inoltre, le interazioni con l'interfaccia utente sono un lavoro per una sorta di classe di controller, che delega i comandi dell'utente al modello (ad esempio RichDocument ). Il controllore viene informato che tale e tale pulsante è stato premuto (ad esempio, evidenzia il testo selezionato in grassetto) e indica a RichDocument cosa fare con i dati.

JRichTextArea si trova alla fine di questa catena e il suo unico lavoro è quello di mostrare obbedientemente i risultati di questa catena di operazioni.

Questo è un esempio di ciò che definirei un approccio "serio" e "professionale" qui.

    
risposta data 27.04.2014 - 17:53
fonte
5

Se funzionerà per quello che vuoi, allora è una cosa ragionevole da fare.

Vediamo lontano perché siamo sulle spalle dei giganti che ci hanno preceduto. Come programmatori professionisti seri, possiamo e dovremmo sfruttare ogni strumento o libreria disponibile (entro limiti ragionevoli) che renderà più veloce il nostro lavoro di produzione di un prodotto finito. più facile.

Ma ...

Sebbene non conosca personalmente JTextArea , I dubbio è compito di essere un word processor completo in un barattolo.

Secondo la documentazione,

It is intended to be a lightweight component ...

Dovrai testarlo per vedere cosa succede quando tenti di usarlo con oltre 100 pagine di testo formattato. Inoltre, cosa succede quando provi a stampare? Il wrap delle parole, le interruzioni di pagina, ecc. Funzioneranno come previsto?

La mia ipotesi è che rimarrai deluso.

Molti anni fa Apple ha pubblicato una TechNote che diceva essenzialmente che la loro casella di riepilogo a colonne a più colonne non era un foglio di calcolo. I loro argomenti erano simili: lo strumento leggero non si adatta ai livelli delle applicazioni, le prestazioni saranno terribili e scrivere un'intera app è più complicato che mettere un wrapper attorno a un elemento dell'interfaccia utente esistente.

    
risposta data 27.04.2014 - 17:39
fonte
3

Alcune classi sono progettate per essere ereditate, ed è giusto ereditarle e basare una grande porzione del progetto sulle classi ereditate.

JTextArea (e il resto delle classi del componente Swing) non è uno di questi. A meno che tu non voglia creare un nuovo componente riusabile, non dovresti ereditare una classe componente.

JTextArea - come molte delle classi di componenti di Swing - fornisce una vasta selezione di hook (a.k.a. ascoltatori) che puoi usare per costruire sulla sua funzionalità. Questo è il modo preferito di utilizzare JTextArea - non ereditarietà.

Detto questo - a volte è più comodo ereditare la classe ma usarla tramite i ganci. Ciò significa che non devi eseguire l'override di alcun metodo, ma utilizzare la funzione di costruzione per aggiungere listener al tuo JTextArea . Questo approccio consente di aggiungere campi e nuovi metodi al componente, che sia gli ascoltatori che le altre classi potrebbero utilizzare. Questo è comune con le classi di componenti del contenitore (ad esempio JFrame , JPanel ), dove i componenti figlio vengono creati nel costruttore e quelli importanti sono memorizzati nei campi, ma se il tuo JTextArea è abbastanza grande questo approccio può essere utile a te - a patto di evitare di sovrascrivere i metodi di JTextArea !

    
risposta data 27.04.2014 - 17:55
fonte
1

Ignorerò completamente la classe specifica e cercherò di rispondere alle tue domande. Come accennato altrove, le specifiche sono meglio gestite su Stack overflow.

Is it considered reasonable in a 'serious' program, to implement a major part of the program by subclassing an existing class of a common library? Is it a 'serious' and 'professional' thing to do?

1- Is something like this considered reasonable in a 'professional' application?

2- Have you ever seen this in practice in a 'professional' application?

La mia risposta è no, non farlo, vivrai per pentirtene. No, questo non è un approccio 'professionale'. No, non l'ho visto fare bene e chi ha provato ha desiderato di non averlo fatto.

Come riassunto qui , ci sono buone ragioni per l'ereditarietà (sottotipizzazione e polimorfismo) e poi c'è l'ereditarietà dell'implementazione, che è intrinsecamente malvagio in tutti tranne i casi più convincenti.

Sì, se hai intenzione di implementare un nuovo tipo di pulsante, ha molto senso ereditare dalla classe Button e aggiungere il tuo comportamento. Non sarai in grado di conservare l'incapsulamento del tuo codice perché sarà tutto confuso con il codice Button ma probabilmente ne varrà la pena.

Tuttavia, se il nuovo codice ha solo il numero di funzioni in comune con un pulsante, utilizzare l'aggregazione. Includere un pulsante da qualche parte nella tua implementazione nella misura in cui ha senso utilizzare le funzionalità dei pulsanti, ma tenerlo a distanza. Conserva il tuo incapsulamento. Mantieni i livelli tra te e il codice fornito da altri.

Un modo di vedere questo è il test "è un / ha un". Se il tuo codice è davvero "è un" pulsante, allora erediti. Se si codifica semplicemente "ha bisogno" di funzionalità del pulsante piuttosto che includere un pulsante, ma non ereditarlo.

Un altro motivo: i servizi della classe da cui pensi di ereditare dovrebbero essere (dal tuo punto di vista) sostituibili. Cioè, dovresti essere libero in qualche momento futuro per scartare quella classe specifica in favore di un'altra. Invece di usare la classe Button, dovresti essere in grado di passare all'utilizzo della classe SpecialButton o della classe SuperButton.

La capacità di farlo sarà strongmente controllata dal livello di incapsulamento che fai attorno al tuo codice. Se erediti, la possibilità di cambiare è probabilmente persa.

Infine: penso che tu possa probabilmente averlo in entrambe le direzioni. Scrivi una (piccola) sottoclasse che cattura le funzionalità essenziali di cui hai bisogno e usala per esporre un'API personalizzata in base alle tue esigenze specifiche. Quindi scrivi il resto del codice per consumare quell'API come servizio. Se è necessario sostituire la classe ereditata, è sufficiente riscrivere la sottoclasse piccola per ereditare una classe genitore diversa o utilizzare un servizio diverso e l'API interna rimane intatta. Ciò preserva il tuo incapsulamento.

Sulla riflessione, questo è il percorso che probabilmente prenderei. Posso chiarire cosa significa se necessario.

    
risposta data 28.04.2014 - 13:48
fonte

Leggi altre domande sui tag