Home>>Principio di sostituibilità di Liskov

lsp Il principio di sostituibilità di Liskov si propone di chiarire cosa significhi "derivare" un sottotipo:

B è un sottotipo di A se e solo se, per ogni programma che usi oggetti di classe A, posso utilizzare al loro posto oggetti di classe B e lasciare immutato il comportamento "logico" del programma.

In generale, le funzioni che utilizzano puntatori e riferimenti ad una classe base, dovrebbero essere in grado di utilizzare istanze di classi derivate dalla classe base senza conoscerne il tipo.  Per rispettare questo principio è necessario progettare le gerarchie di classi in modo tale che sussistano delle corrette relazioni IS-A. E' importante sottolineare che la relazione IS-A attiene al comportamento, cioè il comportamento della classe derivata deve essere consistente con quello della classe base.

 

Figura 1: Esempio di relazione tra Rettangolo e Quadrato

 

Ad esempio potremmo considerare il quadrato come un tipo particolare di rettangolo e quindi modellare le due classi come rappresentato in figura figura 1; in realtà questa scelta potrebbe non rivelarsi del tutto appropriata poichè le due classi non sono coerenti dal punto di vista del loro comportamento. Supponiamo, infatti, che la classe Rettangolo abbia i metodi necessari per impostare e leggere la lunghezza della base e dell'altezza, queste funzioni non sono corrette perla classe quadrato ed il loro comportamento deve essere modificato in modo tale l'impostazione di una delle due grandezze modifichi anche l'altra (vedi il listato 1).

public class Rectangle { private Point topLeft; private double width; private double height; public virtual double Width { get { return width; } set { width = value; } } public virtual double Height { get { return height; } set { height = value; } } } public class Square : Rectangle { public override double Width { set{base.Width = value;base.Height = value;} } public override double Height { set{base.Height = value;base.Width = value;} } }

 

[code=C#;ln=on; Listato 1: Esempio di errata relazione IS-A] public class Rectangle { private Point topLeft; private double width; private double height; public virtual double Width { get { return width; } set { width = value; } } public virtual double Height { get { return height; } set { height = value; } } } public class Square : Rectangle { public override double Width { set{base.Width = value;base.Height = value;} } public override double Height { set{base.Height = value;base.Width = value;} } }[/code]

Purtroppo, sebbene questo codice sia coerente dal punto di vista del comportamento matematico di un quadrato, potrebbe non coincidere con ciò che si aspettano le funzioni client; ad esempio si supponga di avere la seguente funzione del listato 2.

[code=C#;ln=on; Listato 2: Funzione client degli oggetti della gerarchia]void g(Rectangle r) { r.Width = 5; r.Height = 4; if(r.Area() != 20)throw new Exception("Bad area!"); }[/code]

L'autore di questa funzione ha assunto che la modifica di una delle due grandezze lasci l'altra invariata e quindi la funzione g ritornerà un errore nel caso in cui gli sia passato un quadrato! Questa è un'evidente violazione del principio di Liskov, poiché la funzione non è in grado di trattare allo stesso modo sia i rettangoli che i quadrati.

Fondamentalmente dobbiamo usare l'ereditarietà (pubblica) solo quando estendiamo una classe, al contrario non possiamo utilizzarla per restringere la classe base (come nel caso del Quadrato, più restrittivo di un Rettangolo), altrimenti non potremmo usare la classe derivata (più ristretta) ovunque si possa usare la classe base (più generale).

Spesso l'ereditarietà è utilizzata come un comodo meccanismo per il riutilizzo del codice: il codice comune ad un insieme di classi è confinato in una classe, che rappresenta la base della gerarchia, da cui sono derivate tutte le altre sotto classi la cui implementazione sfrutta le funzioni della classe base. Questa scelta progettuale, sebbene molto comoda, è in generale sbagliata poiché l'ereditarietà non è utilizzata per modellare una relazione IS-A o WORKS-LIKE-A, ma la relazione IMPLEMENTED-IN-TERMS-OF. In questo genere di situazioni è preferibile sfruttare la composizione e non l'ereditarietà; al posto della composizione può essere utilizzata l'ereditarietà privata, ma solo quando sia strettamente necessaria (ad esempio per accedere ai membri protetti, oppure per sovrascrivere una funzione virtuale della classe padre).


Aggiungi commento




  Country flag
biuquote
  • Commento
  • Anteprima
Loading


Calendario

<<  febbraio 2012  >>
lumamegivesado
303112345
6789101112
13141516171819
20212223242526
2728291234
567891011

View posts in large calendar

Archivio

Licenza d'uso
Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati mediante:

Licenza Creative Common