In che modo la progettazione dell'ereditarietà può comportare costi aggiuntivi? [chiuso]

11

Quindi volevo ereditare da sealed class in csharp e sono stato masterizzato. Non c'è modo di annullarlo a meno che tu non abbia accesso alla fonte.

Poi mi ha fatto pensare "perché sealed esiste anche?". 4 mesi fa. Non riuscivo a capirlo, nonostante leggessi molte cose a riguardo, come ad esempio:

Da allora ho provato a digerire tutto ciò, ma è troppo per me. Alla fine, ieri ho provato di nuovo. Ho scansionato di nuovo tutti e altri ancora:

Infine, dopo aver riflettuto a lungo, ho deciso di pesante modificare la domanda originale in base al nuovo titolo.

La vecchia domanda era troppo ampia e soggettiva. In pratica chiedevo:

  1. Nel vecchio titolo: un buon motivo per usare sigillato
  2. Nel corpo: come modificare correttamente una classe sigillata? Dimentica l'ereditarietà? Usa la composizione?

Ma ora, comprensione (che non ho ieri) che tutto sealed fa sta impedendo l'ereditarietà , e possiamo e dovremmo effettivamente usare composizione sull'ereditarietà , mi sono reso conto che quello di cui avevo bisogno erano esempi pratici .

Credo che la mia domanda qui sia (e in effetti è sempre stata) esattamente ciò che Mr.Mindor mi ha suggerito in una chat : In che modo la progettazione dell'ereditari può comportare costi aggiuntivi?

    
posta cregox 04.09.2013 - 18:47
fonte

9 risposte

26

Non è così difficile da comprendere. sealed è stato creato SPECIFICAMENTE per Microsoft al fine di semplificare la vita, risparmiare un sacco di soldi e aiutare la loro reputazione. Dal momento che è una funzione linguistica, tutti gli altri possono usarlo anche tu, ma probabilmente non avrai mai bisogno di usare sealed.

La maggior parte delle persone si lamenta di non essere in grado di estendere una classe e se lo fanno allora dicono che tutti sanno che è responsabilità degli sviluppatori farla funzionare correttamente. Ciò è corretto, TRANNE quelle stesse persone non hanno alcun indizio sulla storia di Windows e uno dei problemi che sealed sta cercando di risolvere.

Supponiamo che uno sviluppatore abbia esteso una classe core .Net (perché sealed non esisteva) e ha funzionato perfettamente. Sì, per lo sviluppatore. Lo sviluppatore consegna il prodotto al cliente. L'app dello sviluppatore funziona alla grande. Il cliente è felice La vita è bella.

Ora Microsoft rilascia un nuovo sistema operativo, che include la correzione di bug in questa particolare classe core .Net. Il cliente desidera stare al passo con i tempi e sceglie di installare il nuovo sistema operativo. All'improvviso, l'applicazione a cui il cliente piace così tanto non funziona più, perché non teneva conto dei bug corretti nella classe core .Net.

Chi si prende la colpa?

Coloro che hanno familiarità con la storia di Microsoft sanno che il nuovo SO di Microsoft avrà la colpa e non il software applicativo che ha usato male le librerie di Windows. Quindi, diventa quindi necessario che Microsoft risolva il problema anziché la società applicativa che ha creato il problema. Questo è uno dei motivi per cui il codice di Windows è diventato gonfio. Da quello che ho letto, il codice del sistema operativo Windows è disseminato di specifici controlli if-if per verificare se un'applicazione e una versione specifiche sono in esecuzione e, in tal caso, eseguire alcune elaborazioni speciali per consentire al programma di funzionare. Questo è un sacco di soldi spesi da Microsoft per riparare l'incompetenza di un'altra azienda.

L'utilizzo di sealed non elimina completamente lo scenario sopra, ma aiuta.

    
risposta data 04.09.2013 - 20:47
fonte
23

sealed è usato per indicare che lo scrittore della classe non lo ha progettato per essere ereditato. Niente di meno, niente di più.

Progettare correttamente un'interfaccia è già abbastanza difficile per molti programmatori. La corretta progettazione di una classe che può essere potenzialmente ereditata aggiunge difficoltà e linee di codice. Più righe di codice scritte significano più codice da mantenere. Per evitare questa crescente difficoltà, sealed può mostrare che la classe non è mai stata destinata ad essere ereditata e in questo contesto, sigillare una classe è una soluzione perfettamente valida per un problema .

Immagina il caso in cui la classe CustomBoeing787 è derivata da Airliner . Questo CustomBoeing787 sovrascrive alcuni metodi protetti da Airliner , ma non tutti. Se lo sviluppatore ha abbastanza tempo e ne vale la pena, può testare la classe per assicurarsi che tutto funzioni come previsto, anche in un contesto quando vengono chiamati metodi non sovrascritti (ad esempio setter protetti che cambiano lo stato dell'oggetto). Se lo stesso sviluppatore (1) non ha tempo, (2) non vuole spendere altre centinaia o migliaia di dollari del suo capo / cliente, o (3) non vuole scrivere codice aggiuntivo che non bisogno in questo momento (YAGNI), quindi può:

  • rilasciano il codice in modo naturale, considerando che è la preoccupazione dei programmatori che manterranno questo progetto in seguito a preoccuparsi dei potenziali bug,

  • o contrassegna la classe come sealed per mostrare esplicitamente ai futuri programmatori che questa classe non è destinata ad essere ereditata, e che la sua dissociazione e usarla come genitore dovrebbe essere fatta a proprio rischio.

È una buona idea sigillare il maggior numero possibile di classi o il meno possibile? Dalla mia esperienza, non c'è una risposta definitiva. Alcuni sviluppatori tendono a utilizzare troppo sealed ; gli altri non si preoccupano di come la classe sarebbe ereditata e il problema che può causare. Dato tale utilizzo, il problema è simile a quello che consiste nel determinare se i programmatori debbano utilizzare due o quattro spazi per il rientro: non esiste una risposta corretta.

    
risposta data 04.09.2013 - 19:00
fonte
4

Quando una classe è sigillata, consente al codice che utilizza quel tipo di sapere esattamente con cosa hanno a che fare.

Ora in C # string è sealed , non puoi ereditare da esso, ma supponiamo per un secondo che non sia così. Se lo facessi, allora qualcuno potrebbe scrivere una classe come questa:

public class EvilString// : String
{
    public override string ToString()
    {
        throw new Exception("I'm mean");
    }
    public override bool Equals(object obj)
    {
        return false;
    }
    public override int GetHashCode()
    {
        return 0;
    }
}

Questa sarebbe ora una classe stringa che sta violando tutti i tipi di proprietà che i progettisti di string sapevano essere veri. Sapevano che il metodo Equals sarebbe transitivo, non lo è. Diamine, questo metodo equals non è nemmeno simmetrico, in quanto una stringa potrebbe essere uguale ad esso ma non sarebbe uguale a quella stringa. Sono sicuro che ci sono tutti i tipi di programmatori che hanno scritto codice in base al presupposto che il metodo ToString di string non genererà un'eccezione per valori non nulli. Ora sarai in grado di violare tale ipotesi.

Ci sono un certo numero di altre classi per le quali potremmo fare questo, e ogni sorta di altre supposizioni che potremmo violare. Se rimuovi la funzione lingua per sealed , i progettisti linguistici devono ora iniziare a rendere conto di cose come questa in tutto il loro codice di libreria. Devono eseguire tutti i tipi di controlli per garantire che i tipi estesi dei tipi che desiderano utilizzare siano scritti "correttamente", il che può essere una cosa molto costosa da fare (sia in fase di sviluppo che in fase di esecuzione). Essendo in grado di sigillare una classe, possono garantire che ciò non sia possibile e che qualsiasi asserzione fatta sui dettagli di implementazione dei propri tipi non possa essere violato, ad eccezione di quelli intenzionalmente progettati per essere estesi. Per quei tipi progettati per essere estesi, troverai che il codice della lingua deve essere molto più difensivo su come si comporta con loro.

    
risposta data 04.09.2013 - 19:53
fonte
3

Is there any good reason to use sealed?

Enh? Non spesso. Userò sigillato solo quando ho un tipo immutabile (o qualche altra limitazione) che veramente veramente deve rimanere immutabile e non posso fidarmi degli eredi per soddisfare questo requisito senza introdurre bug sottili e catastrofici nel sistema.

Suppose there is a good reason to use sealed and we should use it as default for everything, what we do with inheritance?

Usiamo l'ereditarietà per quelle cose che non sono predefinite. Anche se la composizione è preferita rispetto all'eredità (ed è), ciò non significa che l'eredità debba essere scartata. Significa che l'ereditarietà ha alcuni problemi intrinseci che introduce nella progettazione e nella manutenibilità del codice e la composizione fa molto della stessa cosa con (in generale) meno problemi. E significa che anche se si preferisce la composizione, l'ereditarietà è buona per alcuni sottoinsiemi di problemi.

Non intendo essere too ma se hai appena sentito parlare di SOLID e test di unità, forse dovresti uscire da sotto il tuo rock e scrivere codice. Le cose non sono chiare e raramente "farai sempre X" o "non usare mai Y" o "Z è il migliore" essere nel modo giusto (come se ti sembrasse di gravitare sulla base delle opinioni della tua domanda). Mentre scrivi il codice puoi vedere casi in cui sealed potrebbe essere buono e casi in cui ti causano dolore - proprio come qualsiasi altro compromesso.

    
risposta data 04.09.2013 - 19:55
fonte
2

Penso che questa domanda non abbia una risposta obiettiva e che tutto dipenda dalla tua prospettiva.

Da un lato, alcune persone vogliono che tutto sia "aperto" e l'onere di garantire che tutto funzioni correttamente è di persona che estende la classe. Questo è il motivo per cui le classi sono aperte all'ereditarietà per impostazione predefinita. E perché i metodi Java sono tutti virtuali per impostazione predefinita.

Dall'altro lato, alcuni vogliono che tutto sia chiuso per impostazione predefinita e per fornire opzioni per aprirlo. In questo caso, è l'autore della classe base che deve assicurarsi che tutto ciò che rende "aperto" sia sicuro da usare in qualsiasi modo immaginabile. Questo è il motivo per cui in C # tutti i metodi non sono virtuali per impostazione predefinita e devono essere dichiarati virtuali per essere sovrascritti. È anche per quelle persone, che devono essere un modo per aggirare i default "aperti". Come rendere la classe sigillata / finale, perché vedono qualcuno che deriva dalla classe un grosso problema per il design della propria classe.

    
risposta data 04.09.2013 - 19:15
fonte
2

Prima di tutto, dovresti leggere il post di Eric Lippert sul perché Microsoft sigillare le loro classi.

link

In secondo luogo, la parola chiave sealed esiste per fare esattamente quello che fa: prevenire l'ereditarietà. Esistono molte situazioni in cui si desidera impedire l'ereditarietà. Considera una classe di cui hai una collezione. Se il tuo codice dipende da quella classe che funziona in un modo particolare, non vuoi che qualcuno sostituisca uno dei metodi o proprietà in quella classe e causi il fallimento della tua applicazione.

In terzo luogo, l'uso di suggellamenti incoraggia la composizione sull'ereditarietà, che è una buona abitudine per entrare. Usare la composizione sull'ereditarietà significa che non puoi rompere il principio di sostituzione di Liskov e stai seguendo il principio Aperto / Chiuso. sealed è quindi una parola chiave che ti assiste nel seguire due dei cinque principi più importanti della programmazione o-o. Direi che lo rende una funzionalità molto preziosa.

    
risposta data 05.09.2013 - 03:46
fonte
-2

il problema con C # sigillato è piuttosto definitivo. In 7 anni di sviluppo commerciale in C #, l'unico posto in cui l'ho visto è su classi Microsoft .NET in cui sento di avere una ragione legittima per estendere una classe framework per implementare un comportamento corretto, e poiché la classe era chiusa, non potevo .

È impossibile scrivere una classe pensando a tutti i possibili modi in cui potrebbe essere utilizzata e decidendo che nessuno di essi è legittimo. Allo stesso modo, se la sicurezza del framework dipende dal fatto che la classe non sia ereditata, allora si ha un difetto nella progettazione della sicurezza.

Quindi, in conclusione, sono fermamente convinto che lo spazio sigillato non abbia uno scopo utile, e nulla di ciò che ho letto qui finora ha cambiato questa visione.

Riesco a vedere che finora ci sono stati tre argomenti che giustificano la chiusura di posizioni sigillate finora.

  1. L'argomento 1 è che elimina una fonte di bug quando le persone eredita dalle classi e quindi ha aumentato l'accesso agli interni di quella classe senza capire veramente l'interno correttamente.

  2. L'argomento 2 è che se estendi una classe microsoft e poi microsoft implementare una correzione di bug in quella classe ma la tua estensione si è basata su quell'errore allora il tuo codice ora è rotto, microsoft si prende la colpa e sigillato impedisce che

  3. L'argomento 3 è che, come sviluppatore della classe, è mia scelta se permetterti di estenderlo, come parte del mio design della classe.

L'argomento 1 sembra essere un argomento per cui il programmatore finale non sa come usare il mio codice. Potrebbe essere il caso, ma se mi permetti comunque di accedere all'interfaccia degli oggetti, è ancora vero che sto usando il tuo oggetto e faccio chiamate senza capire come usarlo, e così posso fare altrettanto danno in quel modo . Questa è una debole giustificazione della necessità di sigillare.

Capisco da dove viene la base fattuale per Arg 2. In particolare, quando Microsoft mette ulteriori controlli del sistema operativo su win32 chiamate api per identificare le chiamate malformate, che hanno migliorato la stabilità generale di Windows XP rispetto a Windows NT, ma a breve termine che molte app sono state interrotte quando è stato rilasciato Windows XP. Microsoft ha risolto questo problema inserendo "regole" per questi controlli per dire, ignorare quando l'app X rompe questa regola, è un bug noto

L'argomento 2 è un argomento povero perché sigillato al meglio non fa quasi nessuna differenza perché se c'è un errore nella libreria .NET non hai altra scelta che trovare un lavoro in giro e quando viene fuori la correzione microsoft, è abbastanza probabile che il tuo lavoro attorno al quale dipende il comportamento interrotto sia ora rotto. Sia che tu abbia lavorato su questo utilizzando l'ereditarietà o con qualche altro codice di wrapper aggiuntivo, quando il codice è corretto, il tuo lavoro sarà interrotto.

L'argomento 3 è l'unico argomento che ha qualche sostanza per giustificare se stesso. Ma è ancora debole. È semplicemente contrario al riutilizzo del codice.

    
risposta data 04.09.2013 - 19:29
fonte
-7

Uso solo la parola chiave sealed su classi che contengono solo metodi statici.

    
risposta data 04.09.2013 - 21:26
fonte
-8

Come è evidente dalla mia domanda, non sono esperto qui. Ma non vedo alcun motivo perché sealed esista addirittura.

Sembra che sia stato fatto per aiutare gli architetti del codice a progettare un'interfaccia. Se vuoi scrivere una classe che uscirà allo scoperto e molte persone ne erediteranno, modificare qualsiasi cosa in quella classe può romperla per troppe persone. Quindi possiamo sigillarlo e nessuno può ereditare. "Problema risolto".

Bene, sembra "coprire un punto e scoprirne un altro". E non risolve nemmeno un problema: è solo eliminare uno strumento : ereditarietà. Come ogni strumento, ha molti usi e eredita da una classe instabile è un rischio che si prende. Chiunque abbia preso questo rischio dovrebbe saperlo meglio.

Forse potrebbe esserci una parola chiave stable , quindi le persone saprebbero che è più sicuro ereditare da essa.

    
risposta data 04.09.2013 - 19:23
fonte

Leggi altre domande sui tag