Credo che la vera ragione per cui molte persone sentono che le classi dovrebbero essere final
/ sealed
è che la maggior parte delle classi estendibili non astratte sono non correttamente documentate .
Lasciami elaborare. A partire da lontano, c'è la visione di alcuni programmatori che l'ereditarietà come strumento in OOP è ampiamente sfruttata e abusata. Abbiamo letto tutti il principio di sostituzione di Liskov, anche se questo non ci ha impedito di violarne centinaia (forse anche migliaia) di volte.
Il fatto è che i programmatori amano riutilizzare il codice. Anche quando non è una buona idea. E l'ereditarietà è uno strumento chiave in questo "reabusing" di codice. Torna alla domanda finale / sigillata.
La documentazione corretta per una classe finale / sigillata è relativamente piccola: descrivi cosa fa ogni metodo, quali sono gli argomenti, il valore di ritorno, ecc. Tutto il solito.
Tuttavia, quando hai correttamente che documenta una classe estendibile devi includere almeno il seguente :
- Dipendenze tra i metodi (che metodo chiama quali, ecc.)
- Dipendenze dalle variabili locali
- Contratti interni che la classe estendente dovrebbe onorare
- Convenzione di chiamata per ogni metodo (ad es. quando lo sostituisci, chiami l'implementazione
super
? La chiami all'inizio del metodo o alla fine? Pensa al costruttore e al distruttore)
- ...
Questi sono appena in cima alla mia testa. E posso fornirti un esempio del perché ciascuno di questi è importante e saltarlo rovinerà una classe che si estende.
Ora, considera la quantità di documentazione necessaria per documentare correttamente ciascuna di queste cose. Credo che una classe con 7-8 metodi (che potrebbe essere troppo in un mondo idealizzato, ma troppo poco nel reale) potrebbe anche avere una documentazione di 5 pagine, solo testo. Quindi, invece, ci nascondiamo a metà strada e non sigilliamo la classe, in modo che altre persone possano usarla, ma non documentarla neanche correttamente, poiché ci vorrà una quantità enorme di tempo (e, sai, potrebbe mai essere esteso comunque, quindi perché preoccuparsi?).
Se stai progettando un corso, potresti provare la tentazione di sigillarlo in modo che le persone non possano usarlo in un modo che non hai previsto (e preparato). D'altro canto, quando si utilizza il codice di qualcun altro, a volte dall'API pubblica non vi è alcun motivo visibile per cui la classe sia definitiva, e si potrebbe pensare "Accidenti, questo mi è costato solo 30 minuti in cerca di una soluzione alternativa".
Penso che alcuni degli elementi di una soluzione siano:
- Innanzitutto assicurati che estendere sia una buona idea quando sei un cliente del codice e davvero preferisci la composizione all'ereditarietà.
- In secondo luogo a leggi il manuale nella sua interezza (sempre come client) per assicurarti di non trascurare ciò che viene menzionato.
- In terzo luogo, quando si sta scrivendo una parte di codice che il client utilizzerà, scrivere una documentazione adeguata per il codice (sì, la lunga strada). Come esempio positivo, posso fornire i documenti iOS di Apple. Non sono sufficienti per l'utente per estendere sempre correttamente le loro classi, ma includono almeno alcune informazioni sull'ereditarietà. Che è più di quello che posso dire per la maggior parte delle API.
- In quarto luogo, in realtà cerca di estendere la tua classe, per assicurarti che funzioni. Sono un grande sostenitore di includere molti campioni e test nelle API, e quando stai facendo un test, potresti anche testare le catene ereditarie: sono una parte del tuo contratto, dopo tutto!
- Quinto, in situazioni in cui sei in dubbio, indica che la classe non è destinata ad essere estesa e che farlo è una cattiva idea (tm). Indica che non devi essere ritenuto responsabile per tale uso non intenzionale, ma non sigillare la classe. Ovviamente, questo non copre i casi in cui la classe dovrebbe essere sigillata al 100%.
- Infine, sigillando una classe, fornisci un'interfaccia come un hook intermedio, in modo che il client possa "riscrivere" la sua versione modificata della classe e aggirare la tua classe "sigillata". In questo modo, può sostituire la classe sigillata con la sua implementazione. Ora, questo dovrebbe essere ovvio, poiché è un accoppiamento lento nella sua forma più semplice, ma vale comunque la pena menzionarlo.
Vale anche la pena di menzionare la seguente domanda "filosofica": è se una classe è sealed
/ final
parte del contratto per la classe, o un dettaglio di implementazione? Ora non voglio calpestare lì, ma una risposta a questo dovrebbe anche influenzare la tua decisione se sigillare una classe o meno.