un pratico esempio del perchè l'Object Oriented non è la Soluzione Universale©'
Ieri molti amici e lettori mi hanno chiesto perchè avessi iniziato ad approfondire il paradigma funzionale, quando con l’object oriented riesci a risolvere quasi tutti i problemi del mondo.
Voglio farvi vedere un esempio di codice che, siccome dipende dallo stato interno degli oggetti e questo stato è mutevole, risulta molto difficile da seguire e da analizzare.
Stavolta l’esempio l’ho preparato in Java, il linguaggio OO per eccellenza. Ho creato la classe “Stormo (di gabbiani)” che modella uno stormo. Ogni stormo ha due metodi, il metodo “unisci ” con un altro stormo, e “riproduci“, che modella essenzialmente quella cosa là :D.
Nel main invece vedete un po’ di operazioni sugli Stormi. Quanto vale result?
class Stormo {
public int gabbiani;
public Stormo(int n) {
this.gabbiani = n;
}
public Stormo unisci(Stormo other) {
this.gabbiani += other.gabbiani;
return this;
}
public Stormo riproduci(Stormo other) {
this.gabbiani = this.gabbiani * other.gabbiani;
return this;
}
}
public class Test {
public static void main(String args[]) {
Stormo stormo_a = new Stormo(4);
Stormo stormo_b = new Stormo(2);
Stormo stormo_c = new Stormo(0);
int result = stormo_a.unisci(stormo_c).riproduci(stormo_b)
.unisci(stormo_a.riproduci(stormo_b)).gabbiani;
System.out.println(result);
}
}
Quanti gabbiani contate? Quanto vale result
alla fine del Main ?
La risposta che avete contato voi è … 16. La riposta del compilatore invece è … 32. E l’oggetto stormo_a
è addirittura cambiato!
Come vedete, sono bastate poche righe di codice per ottenere un risultato sballato e un bug piuttosto evidente.
Uno dei problemi che affligge questo codice è che va a mutare lo stato interno dell’oggetto; se i metodi unisci
e riproduci
avessero restituito copie e lasciato immutato la classe stessa, ora non saremmo qui a parlarne.
EDIT:
Amici suggeriscono di mostrare cosa si dovrebbe cambiare affinchè il codice funzioni.
Io modificherei i metodi unisci
e riproduci
per ottenere il risultato corretto:
public Stormo unisci(Stormo other) {
return new Stormo(this.gabbiani+other.gabbiani);
}
public Stormo riproduci(Stormo other) {
return new Stormo(this.gabbiani*other.gabbiani);
}
Ed è qui che si applica il concetto di immutabilità: non ci sono side effects sull’oggetto chiamato e viene restituito un nuovo oggetto contenente le nuove proprietà.
Dunque ciò che dicono i miei amici e colleghi è giusto, nel senso che con la programmazione a oggetti (ma anche con la programmazione iterativa) i problemi si risolvono comunque; l’approccio funzionale permette però di avere qualche altro gadget nel coltellino svizzero del programmatore, di scrivere codice più bello, più espressivo, più succinto.
Per completezza, riporto qui l’esempio (con i termini inglesi) in javascript così potrete eseguirlo nella console del browser (senza che aprite eclipse…). Anche questo esempio è preso da Mostly Adequate Guide to Functional Programming, capitolo 1.
var Flock = function(n) {
this.seagulls = n;
};
Flock.prototype.conjoin = function(other) {
this.seagulls += other.seagulls;
return this;
};
Flock.prototype.breed = function(other) {
this.seagulls = this.seagulls * other.seagulls;
return this;
};
var flock_a = new Flock(4);
var flock_b = new Flock(2);
var flock_c = new Flock(0);
var result = flock_a.conjoin(flock_c)
.breed(flock_b).conjoin(flock_a.breed(flock_b)).seagulls;
//=> 32