Quadrato estende rettangolo?

Un vero classico, a cui ho sentito/letto/visto rispondere molte volte.

Alla Java Conference 2005 ricordo che si è sfruttata questa domanda per dimostrare che ereditare in modo selvaggio può infrangere il principio di sostituzione di Liskov. In particolare questa era stata la dimostrazione (con buona approssimazione, sto andando a memoria) :

public class Rectangle {
private int base = 0;
private int height = 0;
…..
public void setBase(int base){
this.base = base;
}

public void setHeight(int height){
this.height = height;
}

public int getArea(){
return base * height;
}
}

public class Square extends Rectangle {

public void setBase(int base){
super.setBase(base);
super.setHeight(base);
}

public void setHeight(int height){
this.setBase(height);
}

}

public void testAreaIsTheProduceOfBaseAndHeight(){
Rectangle rectangle = new Rectangle();
rectangle.setBase(10);
rectangle.setHeight(5);
assertEquals(50, rectangle.getArea());
}

Visto che Square eredita da Rectangle possiamo in teoria sostituire un quadrato nella variabile rectangle :

public void testAreaIsTheProduceOfBaseAndHeight(){
Rectangle rectangle = new Square();
rectangle.setBase(10);
rectangle.setHeight(5);
assertEquals(50,rectangle.getArea());
}

Ovviamente questo test fallisce. Si potrebbe opinare che il test presume troppo rispetto all’implementazione di getArea, o rispetto all’implementazione dei due setters, o anche che il test dovrebbe tenere conto dell’oggetto concreto e non dell’astrazione. A questo rispondo che dentro al sistema comunemente non si conosce l’implementazione concreta, solo l’astrazione usata dalla variabile. Se non fosse così non staremmo programmando sull’interfaccia e tanti saluti polimorfismo. La triste verità è che visto che Square ha la stessa interfaccia di Rectangle abbiamo a disposizione un setter di troppo, con dei nomi che non fanno di certo pensare a un quadrato.
L’alchimia usata in Square negli override dei due setter (di per sè già brutta) mantiene le proprietà del quadrato, ma presenta comunque un’interfaccia che confonde il cliente dell’oggetto. Facciamo fare cose strane a dei setters per avere la coerenza, ma non abbiamo comunque un’interfaccia parlante.
Oltrettutto si infrange Dry (Don’t repeat yourself), perchè inseriamo la stessa informazione in due posti diversi.

La soluzione era definire un’astrazione comune a Square e Rectangle che definisca una getArea astratta e nessun setter (che in generale sono una pessima idea in un’astrazione che non presuppone una precisa struttura dati):

public abstract class Shape {
public abstract int getArea();
}

Poi, qualche tempo fa, mi sono ritrovato anche io a rispondere al classico.

La mia risposta ha seguito il manuale e sono finito sullo Strategy, che alla questione dell’area risponde bene. Ma in verità sono stato precipitoso.

A questo quesito, la prima risposta che io penso si dovrebbe proporre è un’altra domanda.
Quali caratteristiche di Quadrato e Rettangolo dobbiamo modellare?

Il motivo per cui spesso si pensa subito a Square extends Rectangle è che si pensa che la definizione matematica di queste due forme ne rappresenti la natura stessa e che quindi per descriverle correttamente si debba seguirla.

Ora, le relazioni matematiche tra le componenti delle forme geometriche, per quanto sia il metodo che viene usato per definirle a scuola, sono semplicemente la descrizione di uno dei tanti aspetti e interconnessioni di queste forme.

Pensare che gli oggetti debbano rappresentare la “natura” di un’entità, la sua identità, porta inesorabilmente a caricare la classe di responsabilità e quindi a rendere impossibile uno schema di astrazioni valido (dove per valido intendo sostituibile). E’ anche per questo che descrivere “extends” con “is a” è così pericoloso, sembra implicare che si parli della natura delle cose, mentre quello che gli oggetti rappresentano meglio sono i comportamenti delle cose. Altrimenti non si nasconderebbero le informazioni per pubblicarne i comportamenti, si nasconderebbero invece i comportamenti per pubblicarne le informazioni.

Però, se adesso qualcuno mi ponesse la domanda, e, alla mia seguente domanda, mi rispondesse che la caratteristica da rappresentare sono proprio le relazioni matematiche tra le componenti delle due forme, forse prenderei in considerazione di dire che “Square is a Rectangle”, ma a quel punto l’interfaccia di Rectangle non avrebbe nulla a chè vedere col calcolo dell’area e i dati immagazzinati non sarebbero certamente base ed altezza.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s