L'ereditarietà aggiunge regole cattive? [duplicare]

7

Sono entrato in un dibattito su questa domanda che viene distillata se è una buona idea per una specializzazione di una classe aggiungere regole aziendali. Sfortunatamente questo punto è stato calpestato nei commenti quindi lo sto chiedendo nuovamente come una domanda a parte.

Credo due cose:

  • Un oggetto è responsabile della sua coerenza interna
  • Una classe di specializzazione / bambino ha regole più specifiche della super classe che può essere vista come il caso generale.

Il risultato logico di ciò è che una specializzazione potrebbe accettare solo alcuni valori di input per il suo metodo o potrebbe modificare alcuni valori per rimanere coerenti. Ma non è OK, dal momento che proteggere la sua coerenza interna è ciò che un oggetto dovrebbe fare?

Un punto che molte persone stavano facendo è che alcuni codici potrebbero rompersi se facessero ipotesi. Ad esempio, l'impostazione della larghezza non cambierebbe l'altezza di un quadrato. Tuttavia non sarebbe un codice cattivo? Dal momento che fai assunzioni su come l'oggetto fa qualcosa invece di dirti cosa fare e non ti preoccupare?

Se non scrivessimo codice come quello, quasi tutto il sovraccarico avrebbe problemi. Con quale frequenza il sovraccarico non aggiunge una condizione di errore extra o più logica interna che potrebbe essere vista attraverso altre parti dell'interfaccia? Forse il punto su cui un ex professore di me ha fatto una volta è corretto: "dovresti usare sempre l'ereditarietà per sovraccaricare il costruttore". All'epoca sembrava un po 'severo, ma ora sembra l'unico modo per garantire che questo tipo di problema non accada mai. Per utilizzare nuovamente il vecchio quadrato: analogia del rettangolo:

public class Rectangle
{
    private int width, height;

    public Rectangle(int width, int height){this.width = width; this.height = height;}

    public void SetWidth... SetHeight...
}

public class Square : Rectangle
{
    public Square(int diameter) : base(diameter, diameter) {}

    public void SetDiameter...
}

Nota: Spero che possiamo svolgere questa domanda un po 'meno "sull'uomo" della domanda che l'ha ispirata. Sono stato su Stack Exchange per più di tre anni ma ero abbastanza intimidito dal tipo di risposte qui.

    
posta Roy T. 08.05.2014 - 14:41
fonte

4 risposte

7

La trappola in cui cadono molte persone sta considerando l'ereditarietà come un mezzo per codificare qualsiasi relazione o somiglianza tra due classi. Questo non è il caso. L'ereditarietà è utile per alcuni tipi limitati di relazioni ed è effettivamente dannosa se utilizzata al di fuori di tali contesti. La mancanza di sostituibilità è una delle ragioni per cui.

Il punto cruciale a cui molte persone mancano nell'esempio del rettangolo quadrato è che è perfettamente sostituibile se si inverte la relazione. Nella progettazione orientata agli oggetti, un rettangolo è una forma specializzata di un quadrato. La ragione che è difficile vedere è che le persone vogliono organizzare le classi in base alle somiglianze tra le classi stesse, magari seguendo le tassonomie del mondo reale, quando dovrebbero occuparsi davvero dell'organizzazione delle classi con i metodi che il codice chiamante sarà necessario utilizzare una raccolta mista di quelle classi. È qui che entra in gioco l'idea di sostituibilità.

Pensaci in questo modo. Hai un mucchio di codice che imposta la larghezza dei quadrati e vuoi lanciare alcuni rettangoli nel mix. Puoi impostare la larghezza su un quadrato o su un rettangolo tutto il giorno senza violare la sostituibilità, ma l'impostazione indipendente di altezza si applica solo al rettangolo, quindi non dovrebbe far parte della classe base. Stai aggiungendo alla classe specializzata, non modificando il comportamento comune.

In altre parole, non fare una scelta sbagliata e dire a te stesso, "Non ho altra scelta che violare la sostituibilità." Se non puoi fare qualcosa senza violare la sostituibilità, allora devi cambiare la relazione di ereditarietà o non usare affatto l'ereditarietà.

    
risposta data 08.05.2014 - 15:26
fonte
3

A point many people were making is that some code could break if it would make assumptions. For example that setting the width would not change the height of a square. However wouldn't that be bad code? Since you make assumptions on how the object does something instead of just telling it what to do and not worry about it?

Sono autorizzato ad assumere che l'oggetto segua le sue specifiche. Se la specifica di Rectangles dice che la larghezza e l'altezza sono indipendentemente modificabili, allora qualsiasi implementazione deve essere conforme. (Se non si richiede conformità, è impossibile ragionare sul proprio programma.) Ora, si potrebbe sostenere che la specifica per Rectangles non ha mai detto che setWidth non può cambiare l'altezza, ma se si tenta di elencare tutto le cose che qualcosa non deve fare, scoprirai che la lista è infinita:

  • setWidth musn't riformattare il mio disco rigido
  • setWidth musn't elimina i file nella mia cartella home
  • setWidth musn't apportare modifiche al registro di Windows
  • setWidth musn't cambia lo stato di un altro oggetto
  • setWidth musn't entra in un ciclo infinito
  • setWidth musn't post su Twitter per mio conto
  • ...

L'unico modo ragionevole per specificare qualcosa è elencare le cose che deve e può fare e assumere che tutto ciò che non è elencato è proibito. Quindi se la specifica per setWidth dice che cambia la larghezza del rettangolo, presumo che non cambi l'altezza.

How often doesn't overloading add an extra fail condition...

Fare questo ti porterà sicuramente dolore e sofferenza. Qualsiasi programma scritto secondo le specifiche presuppone che una determinata operazione possa fallire solo a causa di A , B e C . Se si introduce una nuova condizione di errore D , nessuno può gestirla.

Una parola per il saggio, però - se hai bisogno di questo tipo di sostituibilità, l'ereditarietà probabilmente non è ciò che desideri. Cominci con qualche tipo Foo e poi realizzi che vuoi ShinyFoo . Più tardi vuoi un TransparentFoo . Alla fine vorrai un ShinyTransparentFoo e poi sarai nei guai. Non ti imbatti in questo tipo di problema se utilizzi un'interfaccia e ti affidi alla composizione per riutilizzare il comportamento.

    
risposta data 08.05.2014 - 16:35
fonte
0

Prendi il classico esempio di ereditarietà - Cane: Animale dove Cane è una classe che eredita da Animale. Mentre Animal può mangiare, un cane può abbaiare, saltare e nuotare. Quindi aggiungi questi metodi a Dog.

Ha senso dal punto di vista della relazione. Perché l'animale dovrebbe essere in grado di abbaiare? Perché Dog non dovrebbe essere in grado di abbaiare? In realtà, nessuno sta rivendicando il contrario. Vedete questo tipo di relazione spesso nel codice, tuttavia, ogni volta che è necessario utilizzare la corteccia, è necessario sapere contemporaneamente se si tratta di un cane. Pertanto, qualsiasi utilizzo di corteccia aggiunge automaticamente una dipendenza diretta a Dog in qualsiasi modo lo si affetta. Anche se prendi un animale e controlli se si tratta di un cane, e poi agisci di conseguenza, non stai più agendo sul caso generale di gestione degli animali. Non c'è nulla che ti impedisca di diong questo, anche se hai perso qualsiasi vantaggio avevi con l'ereditarietà.

Per approfittare veramente dell'ereditarietà, devi lasciare che le classi ereditate rappresentino varie implementazioni della super classe. In questo modo, non è sufficiente che una classe "sia un tipo di" un'altra classe. Deve anche giocare la parte della super-classe con piccole eccezioni come durante la sua creazione che è l'unico punto nel tuo programma che dovrebbe conoscere l'implementazione specifica utilizzata.

Quindi forse un esempio migliore non sarebbe Dog: Animal ma piuttosto Square: Drawable, dove Drawable è un oggetto che può essere chiamato per disegnare se stesso indipendentemente da come.

    
risposta data 08.05.2014 - 15:52
fonte
0

The logical result of this is that a specialization might only accept some values of input for its method or might change some values in order to stay consistent. But isn't that OK, since guarding its internal consistency is what an object should do?

Il problema non è in realtà ciò che l'oggetto fa internamente per implementare il messaggio che viene richiesto di eseguire (ad es. setWidth). Il problema è ciò che il chiamante capisce il risultato finale del messaggio.

Sia un metodo setWidth su un rettangolo che imposta solo la larghezza, sia un metodo setWidth su un quadrato che imposta sia la larghezza sia l'altezza sono perfettamente validi purché sia chiaro a chiunque esegua le chiamate che cosa fanno questi metodi.

Il problema è quando dici un quadrato è un rettangolo e poi guardi il metodo setWidth di un rettangolo. Questo metodo mette l'oggetto in uno stato particolare. Resta inteso da tutti coloro che chiamano il metodo che aggiornerà la larghezza e solo la larghezza.

Quando dici che un rettangolo è un rettangolo stai dicendo a chiunque lavori con il quadrato che tutto ciò che sapevi sui rettangoli è ancora valido quando si lavora con i quadrati . E una delle cose che il programmatore sapeva dei rettangoli era che setWidth ha aggiornato solo la larghezza. Questo è un comportamento noto.

Quindi il programmatore sa cosa fa questo metodo, sa che il quadrato è un rettangolo, quindi sa che setWidth aggiornerà solo la larghezza.

Tranne che non aggiorna solo la larghezza, ora aggiorna anche l'altezza. Il programma ha appena mentito al programmatore, ha rotto il suo contratto.

Questo esempio potrebbe sembrare banale, ma diventa molto più importante quando si calcola il polimorfismo.

Potresti non sapere quale forma hai, potresti non sapere se hai un rettangolo o un quadrato. Se sai che un quadrato è un rettangolo, allora conosci che qualunque oggetto ti si comporti si comporterà come ti aspetti che si comporti un rettangolo se applichi il comportamento del rettangolo ad esso.

Ma ovviamente se non lo è, hai un problema serio perché non puoi fidarti che i tuoi oggetti si comportino come te li aspetti, e quindi non puoi fidarti del tuo codice per fare ciò che ti aspetti che faccia.

Invece quello che devi fare è interrompere la connessione, dire al programmatore che un quadrato NON è un rettangolo e che il programmatore deve sapere se ha un quadrato o meno e deve capire quale comportamento ha un quadrato in particolare perché non è uguale a un rettangolo.

    
risposta data 08.05.2014 - 20:10
fonte

Leggi altre domande sui tag