Perché usare 'final' in una classe è davvero così male?


34

Sto eseguendo il refactoring di un sito Web legacy di PHP OOP.

Sono così tentato di iniziare a usare "final" sulle classi per " make it explicit that the class is currently not extended by anything". Questo potrebbe farti risparmiare un sacco di tempo se vengo a una lezione e mi chiedo se posso rinominare / cancellare / modificare una protectedproprietà o un metodo. Se voglio davvero estendere una classe, posso semplicemente rimuovere la parola chiave finale per sbloccarla per l'estensione.

Vale a dire se vengo a una classe che non ha classi secondarie posso registrare quella conoscenza contrassegnando la classe come finale. La prossima volta che ci arrivo non dovrei cercare nuovamente la base di codice per vedere se ha figli. Risparmiando così tempo durante i refactoring.

Sembra una buona idea per risparmiare tempo ... ma ho letto spesso che le lezioni dovrebbero essere rese "definitive" solo in occasioni rare / speciali.

Forse rovina la creazione di oggetti Mock o ha altri effetti collaterali a cui non sto pensando.

Cosa mi sto perdendo?


3
Se hai il pieno controllo del tuo codice, non è terribile, suppongo, ma un po 'sul lato OCD. Che cosa succede se ti manca una lezione (ad es. Non ha figli ma non è definitiva)? È meglio lasciare che uno strumento esegua la scansione del codice e ti dica se una classe ha figli. Non appena impacchettalo come una libreria e lo dai a qualcun altro, trattare con "final" diventa un dolore.
Giobbe

Ad esempio, MbUnit è un framework aperto per i test di unità .Net e MsTest non lo è (sorpresa a sorpresa). Di conseguenza, DEVI sborsare 5k su MSFT solo perché vuoi eseguire alcuni test nel modo MSFT. Altri runner di test non possono eseguire la dll di test creata con MsTest, tutto a causa delle classi finali utilizzate da MSFT.
Giobbe

3
Alcuni post sul blog sull'argomento: Final Classes: Open for Extension, Closed for Inheritance (maggio 2014; di Mathias Verraes)
hakre,

Bell'articolo Parla i miei pensieri su questo. Non sapevo di quelle annotazioni quindi era utile. Saluti.
JW01

"PHP 5 introduce la parola chiave final, che impedisce alle classi figlio di sovrascrivere un metodo prefissando la definizione con final. Se la classe stessa viene definita final, non può essere estesa." php.net/manual/en/language.oop5.final.php Non è affatto male.
Nero

Risposte:


54

Ho letto spesso che le lezioni dovrebbero essere rese "definitive" solo in occasioni rare / speciali.

Chiunque abbia scritto che è sbagliato. Usa finalliberamente, non c'è niente di sbagliato in questo. Documenta che una classe non è stata progettata pensando all'ereditarietà, e questo di solito è vero per tutte le classi per impostazione predefinita: progettare una classe che può essere ereditata in modo significativo richiede più di una semplice rimozione di un finalidentificatore; ci vuole molta cura.

Quindi l'utilizzo finaldi default non è affatto male. In effetti, molte persone propongono che questo dovrebbe essere il valore predefinito, ad esempio Jon Skeet .

Forse rovina la creazione di oggetti Mock ...

Questo è davvero un avvertimento, ma puoi sempre ricorrere alle interfacce se devi deridere le tue classi. Questo è certamente superiore a rendere tutte le classi aperte all'eredità solo allo scopo di deridere.


1
1+: dal momento che rovina beffardo; prendere in considerazione la creazione di interfacce o classi astratte per ogni classe "finale".
Spoike,

Bene, questo mette una chiave in cantiere. Questo arrivo in ritardo contraddice tutte le altre risposte. Ora non so cosa credere: o. (Deselezionata la mia risposta accettata e sospensione della decisione durante il nuovo processo)
JW01

1
Puoi considerare l'API Java stessa e la frequenza con cui troverai l'uso della parola chiave 'final'. In realtà, non viene usato molto spesso. Viene utilizzato nei casi in cui le prestazioni potrebbero peggiorare a causa dell'associazione dinamica che si verifica quando vengono chiamati i metodi "virtuali" (in Java tutti i metodi sono virtuali se non vengono dichiarati come "finali" o "privati") o quando si introduce la personalizzazione le sottoclassi di una classe API Java potrebbero presentare problemi di coerenza, come la sottoclasse "java.lang.String". Una stringa Java è un oggetto valore per definizione e non deve modificarne il valore in un secondo momento. Una sottoclasse potrebbe violare questa regola.
Jonny Dee,

5
@Jonny La maggior parte delle API Java è mal progettata. Ricorda che la maggior parte ha più di dieci anni e nel frattempo sono state sviluppate molte buone pratiche. Ma non crederci sulla parola: gli stessi ingegneri Java lo dicono, prima di tutto Josh Bloch che ha scritto in dettaglio su questo, ad esempio in Effective Java . Siate certi che se l'API Java fosse sviluppata oggi, sembrerebbe molto diversa e finalavrebbe un ruolo molto più grande.
Konrad Rudolph,

1
Non sono ancora convinto che l'uso 'finale' più spesso sia diventato una buona pratica. A mio avviso, il "finale" dovrebbe davvero essere utilizzato solo se i requisiti di prestazione lo dettano o se è necessario garantire che la coerenza non possa essere interrotta da sottoclassi. In tutti gli altri casi, la documentazione necessaria dovrebbe essere inclusa nella documentazione (che deve essere comunque scritta) e non nel codice sorgente come parole chiave che impongono determinati schemi di utilizzo. Per me, è un po 'come suonare Dio. L'uso delle annotazioni dovrebbe essere una soluzione migliore. Si potrebbe aggiungere un'annotazione "@final" e il compilatore potrebbe emettere un avviso.
Jonny Dee,

8

Se vuoi lasciare una nota a te stesso che una classe non ha sottoclassi, allora fallo e usa un commento, ecco a cosa servono. La parola chiave "finale" non è un commento e usare parole chiave in lingua solo per segnalarti qualcosa (e solo tu sapresti mai cosa significhi) è una cattiva idea.


17
Questo è completamente falso! "Usare le parole chiave della lingua solo per segnalarti qualcosa [...] è una cattiva idea". Al contrario, questo è esattamente il motivo per cui le parole chiave della lingua sono disponibili. Il tuo avvertimento provvisorio, "e solo tu sapresti mai cosa significa", è ovviamente corretto ma non si applica qui; l'OP sta usando finalcome previsto. Non c'è niente di sbagliato in questo. E l'utilizzo di una funzione del linguaggio per imporre un vincolo è sempre superiore all'utilizzo di un commento.
Konrad Rudolph,

8
@Konrad: Tranne che finaldovrebbe indicare "Nessuna sottoclasse di questa classe dovrebbe mai essere creata" (per motivi legali o qualcosa del genere), non "questa classe al momento non ha figli, quindi sono ancora al sicuro con i suoi membri protetti". L'intento finalè l'antitesi stessa di "liberamente modificabile", e una finalclasse non dovrebbe nemmeno avere alcun protectedmembro!
SF.

3
@Konrad Rudolph Non è affatto minuscolo. Se altre persone in seguito vogliono estendere le sue lezioni, c'è una grande differenza tra un commento che dice "non esteso" e una parola chiave che dice "non può estendere".
Jeremy,

2
@Konrad Rudolph Sembra un'ottima opinione per porre una domanda sul design del linguaggio OOP. Ma sappiamo come funziona PHP e ci vuole l'altro approccio per consentire l'estensione se non sigillato. Fingere che sia giusto confondere le parole chiave perché "ecco come dovrebbe essere" è solo difesa.
Jeremy,

3
@Jeremy Non sto combinando parole chiave. La parola chiave finalsignifica "questa classe non deve essere estesa [per ora]". Niente di più, niente di meno. Il fatto che PHP sia stato progettato tenendo presente questa filosofia è irrilevante: dopo tutto ha la finalparola chiave. In secondo luogo, argomentare dal design di PHP è destinato a fallire, dato che PHP è progettato in modo patchwork e malamente generale.
Konrad Rudolph

5

C'è un bell'articolo su "Quando dichiarare le classi finali" . Alcune citazioni da esso:

TL; DR: crea sempre le tue classi final, se implementano un'interfaccia, e non sono definiti altri metodi pubblici

Perché devo usare final?

  1. Prevenire la massiccia catena ereditaria del destino
  2. Composizione incoraggiante
  3. Forza lo sviluppatore a pensare all'API pubblica dell'utente
  4. Forza lo sviluppatore a ridurre l'API pubblica di un oggetto
  5. Una finalclasse può sempre essere resa estensibile
  6. extends rompe l'incapsulamento
  7. Non hai bisogno di quella flessibilità
  8. Sei libero di cambiare il codice

Quando evitare final:

Le classi finali funzionano efficacemente solo con le seguenti ipotesi:

  1. C'è un'astrazione (interfaccia) implementata dalla classe finale
  2. Tutta l'API pubblica della classe finale fa parte di tale interfaccia

Se manca una di queste due pre-condizioni, probabilmente raggiungerai un momento in cui renderai la classe estensibile, poiché il tuo codice non si basa davvero sulle astrazioni.

PS Grazie a @ocramius per l'ottima lettura!


5

"final" per una classe significa: vuoi una sottoclasse? Vai avanti, elimina la sottoclasse "finale" quanto vuoi, ma non lamentarti se non funziona. Sei da solo.

Quando una classe può essere sottoclassata, è il comportamento su cui gli altri si basano, deve essere descritto in termini astratti a cui le sottoclassi obbediscono. I chiamanti devono essere scritti per aspettarsi una certa variabilità. La documentazione deve essere scritta con cura; non puoi dire alla gente "guarda il codice sorgente" perché il codice sorgente non è ancora lì. Questo è tutto sforzo. Se non mi aspetto che una classe sia sottoclassata, è uno sforzo inutile. "final" dice chiaramente che questo sforzo non è stato fatto e dà un equo avvertimento.


-1

Una cosa a cui potresti non aver pensato è il fatto che QUALSIASI cambio di classe significa che deve sottoporsi a nuovi test di qualità.

Non contrassegnare le cose come finali a meno che tu non lo intenda davvero.


Grazie. Potresti spiegarlo ulteriormente. Vuoi dire che se segnare una classe come final(una modifica) allora devo solo riprovare?
JW01

4
Direi questo più fortemente. Non contrassegnare le cose come finali a meno che non sia possibile far funzionare un'estensione a causa del design della classe.
S.Lott

Grazie S.Lott - Sto cercando di capire come un cattivo utilizzo di final influisca sul codice / progetto. Hai mai avuto brutte storie dell'orrore che ti hanno portato ad essere così dogmatico sull'uso parsimonioso di final. Questa è un'esperienza di prima mano?
JW01

1
@ JW01: una finalclasse ha un caso d'uso primario. Esistono classi polimorfiche che non si desidera estendere perché una sottoclasse potrebbe interrompere il polimorfismo. Non utilizzare a finalmeno che non sia necessario impedire la creazione di sottoclassi. A parte questo, è inutile.
S.Lott

@ JW01, in un ambiente di produzione potrebbe non essere 'PERMESSO per rimuovere una finale, perché questo non è il codice del cliente testato e approvato. Tuttavia, potresti essere autorizzato ad aggiungere un codice aggiuntivo (per i test) ma potresti non essere in grado di fare ciò che desideri perché non puoi estendere la tua classe.

-1

L'uso di 'final' toglie la libertà agli altri che vogliono usare il tuo codice.

Se il codice che scrivi è solo per te e non verrà mai rilasciato al pubblico o a un cliente, ovviamente puoi fare con il tuo codice quello che vuoi. Altrimenti, impedisci ad altri di costruire sul tuo codice. Troppo spesso ho dovuto lavorare con un'API che sarebbe stata facile da estendere per le mie esigenze, ma poi sono stato ostacolato da "final".

Inoltre, v'è spesso il codice che dovrebbe meglio non essere fatto private, ma protected. Certo, privatesignifica "incapsulamento" e nascondere cose considerate dettagli di implementazione. Ma come programmatore API potrei anche documentare il fatto che il metodo xyzè considerato come dettaglio di implementazione e, quindi, potrebbe essere modificato / eliminato nella versione futura. Quindi tutti coloro che faranno affidamento su tale codice nonostante l'avvertimento lo stanno facendo a proprio rischio. Ma in realtà può farlo e riutilizzare il codice (si spera già testato) e trovare una soluzione più velocemente.

Naturalmente, se l'implementazione dell'API è open source, si può semplicemente rimuovere il metodo "final" o rendere "protetti" i metodi, ma è necessario modificare il codice e tenere traccia delle modifiche in forma di patch.

Tuttavia, se l'implementazione è chiusa, si rimane indietro con la ricerca di una soluzione alternativa o, nel peggiore dei casi, con il passaggio a un'altra API con meno restrizioni per quanto riguarda le possibilità di personalizzazione / estensione.

Nota che non trovo che 'final' o 'private' siano malvagi, ma penso che vengano usati troppo spesso perché il programmatore non ha pensato al suo codice in termini di riutilizzo ed estensione del codice.


2
"L'ereditarietà non è il riutilizzo del codice".
quant_dev,

2
Ok, se insisti su una definizione perfetta, hai ragione. L'ereditarietà esprime una relazione "is-a" ed è questo fatto che consente il polimorfismo e fornisce un modo per estendere un'API. Ma in pratica, l'ereditarietà è molto spesso usata per riutilizzare il codice. Questo è particolarmente vero con l'ereditarietà multipla. E non appena chiami il codice di una super classe, in realtà stai riutilizzando il codice.
Jonny Dee,

@quant_dev: Riusabilità in OOP significa innanzitutto polimorfia. E l'ereditarietà è un punto critico per il comportamento polimorfico (condivisione dell'interfaccia).
Falcon,

@Falcon Sharing interface! = Codice di condivisione. Almeno non nel solito senso.
quant_dev,

3
@quant_dev: la reuseability nel senso di OOP è sbagliata. Significa che un singolo metodo può operare su molti tipi di dati, grazie alla condivisione dell'interfaccia. Questa è una parte importante del riutilizzo del codice OOP. E l'ereditarietà ci consente di condividere automaticamente le interfacce, aumentando così notevolmente la reusabilità del codice. Ecco perché parliamo di riusabilità in OOP. Solo la creazione di librerie con routine è vecchia quasi quanto la programmazione stessa e può essere eseguita con quasi tutte le lingue, è comune. Il riutilizzo del codice in OOP è molto più di questo.
Falcon,
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.