Credo che la vera ragione per cui molte persone ritengono che le classi debbano essere final
/ sealed
è che la maggior parte delle classi estendibili non astratte non siano adeguatamente documentate .
Lasciami elaborare. A partire da lontano, alcuni programmatori ritengono che l'ereditarietà come strumento in OOP sia ampiamente sfruttata e abusata. Abbiamo letto tutti il principio di sostituzione di Liskov, anche se questo non ci ha impedito di violarlo centinaia (forse anche migliaia) di volte.
Il fatto è che i programmatori adorano riutilizzare il codice. Anche quando non è una buona idea. E l'ereditarietà è uno strumento chiave in questa "ripresa" del 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. Tutte le solite cose.
Tuttavia, quando si documenta correttamente una classe estensibile, è necessario includere almeno quanto segue :
- Dipendenze tra metodi (quale metodo chiama quale, ecc.)
- Dipendenze da variabili locali
- Contratti interni che la classe allargante dovrebbe onorare
- Chiama la convenzione per ciascun metodo (ad es. Quando lo sostituisci, chiami l'
super
implementazione? La chiami all'inizio del metodo o alla fine? Pensa a costruttore contro distruttore)
- ...
Questi sono appena in cima alla mia testa. E posso fornirti un esempio del motivo per cui ognuno di questi è importante e saltarlo rovinerà una classe in espansione.
Ora, considera quanto sforzo di documentazione dovrebbe essere impiegato per documentare correttamente ognuna di queste cose. Credo che una classe con 7-8 metodi (che potrebbe essere troppo in un mondo idealizzato, ma è troppo poco nella realtà) potrebbe anche avere una documentazione di 5 pagine, solo testo. Quindi, invece, ci allontaniamo a metà strada e non sigilliamo la classe, in modo che altre persone possano usarla, ma non documentarla nemmeno correttamente, dal momento che ci vorrà una quantità enorme di tempo (e, sai, potrebbe mai essere esteso comunque, quindi perché preoccuparsi?).
Se stai progettando una classe, potresti provare la tentazione di sigillarla in modo che le persone non possano usarla in un modo che non hai previsto (e preparato per). D'altra parte 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, mi è costato solo 30 minuti per cercare una soluzione alternativa".
Penso che alcuni degli elementi di una soluzione siano:
- Per prima cosa assicurati che l' estensione sia una buona idea quando sei un cliente del codice e favorire davvero la composizione rispetto all'eredità.
- Secondo per leggere il manuale nella sua interezza (di nuovo come client) per essere sicuro di non trascurare qualcosa che è menzionato.
- In terzo luogo, quando si scrive un pezzo di codice che il client utilizzerà, scrivere la documentazione corretta per il codice (sì, la strada lunga). Come esempio positivo, posso fornire i documenti iOS di Apple. Non sono sufficienti per consentire all'utente di estendere sempre correttamente le proprie classi, ma almeno includono alcune informazioni sull'ereditarietà. Il 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 dell'inclusione di molti campioni e test nelle API e quando si esegue un test, è possibile testare anche le catene di eredità: dopo tutto fanno parte del contratto!
- In quinto luogo, in situazioni in cui sei in dubbio, indica che la classe non è pensata per essere estesa e che farlo è una cattiva idea (tm). Indica che non dovresti essere ritenuto responsabile per tale uso non intenzionale, ma comunque non sigillare la classe. Ovviamente, questo non copre i casi in cui la classe dovrebbe essere sigillata al 100%.
- Infine, quando si sigilla una classe, fornire un'interfaccia come hook intermedio, in modo che il client possa "riscrivere" la propria versione modificata della classe e aggirare la classe "sigillata". In questo modo, può sostituire la classe sigillata con la sua implementazione. Ora, questo dovrebbe essere ovvio, dal momento che è un accoppiamento libero nella sua forma più semplice, ma vale comunque la pena menzionarlo.
Vale anche la pena menzionare la seguente domanda "filosofica": è se una classe è sealed
/ final
parte del contratto per la classe o un dettaglio di implementazione? Ora non voglio andare lì, ma una risposta a ciò dovrebbe anche influenzare la tua decisione di sigillare una classe o meno.
Open/Closed principle
, non ilClosed Principle.