mercoledì 5 marzo 2008

Se starnazza come un'anatra...

... allora è un'anatra!

No, non sono diventato un membro del club degli ammiratori di Massimo Catalano (ricordate "Quelli della notte"?). E' solo un modo per esprimere un concetto che differenzia i linguaggi a oggetti di scripting da quelli compilati. I primi genericamente sono blandamente tipizzati, i secondi invece fortemente tipizzati. Questo impatta sul discorso delle interfacce che facevo in precedenza: nel primo caso (Ruby o Python, ad esempio) il fatto che io esprima un'interfaccia dei miei oggetti è solamente un aiuto che do a chi legge il mio programma: gli comunico esplicitamente come ho pensato il design a oggetti della mia soluzione. Nel secondo caso (Java o C#, ad esempio), senza un'interfaccia, il metodo polimorfico semplicemente non funziona.

Nel secondo caso, per tornare alle anatre, bisogna dire al compilatore: guarda che ti sto passando un'anatra, comportati di conseguenza! Nel primo, all'interprete, non dico niente: se starnazza come un'anatra, allora è un'anatra. Punto e basta.

Un esempio polimorfico in Java e in Ruby ci può aiutare a capire meglio. Cominciamo da un linguaggio fortemente tipizzato come Java:


package anatre;

import java.util.ArrayList;
import java.util.Collection;

interface IGallinaceo
{
String starnazza();
}

public class Anatra implements IGallinaceo
{
@Override
public String starnazza()
{
return "Sono un'anatra e starnazzo!";
}
}

public class Oca implements IGallinaceo
{
@Override
public String starnazza()
{
return "Sono un'oca e starnazzo!";
}
}


public final class Cortile
{

public static void main(String[] args)
{
Collection<IGallinaceo> cortile =
new ArrayList<IGallinaceo>();

cortile.add(new Anatra());
cortile.add(new Oca());
cortile.add(new Anatra());

for (IGallinaceo gall : cortile)
{
System.out.println(gall.starnazza());
}
}
}



Come si vede, le due classi Ocae Anatra implementano esplicitamente l'interfaccia IGallinaceo, cioè sono (IS-A) a tutti gli effetti dei Gallinacei e il compilatore lo sa a compile-time.

Vediamo lo stesso esempio in Ruby.


class Oca
def starnazza
return "Sono un'oca e starnazzo";
end
end

class Anatra
def starnazza
return "Sono un'anatra e starnazzo"
end
end

cortile = Array.new
cortile.push(Anatra.new)
cortile.push(Oca.new)
cortile.push(Anatra.new)

for animale in cortile
puts(animale.starnazza)
end


Qui semplicemente io passo una serie di classi al mio metodo polimorfico, e mi limito a richiamare il metodo "starnazza". Se facessi questa cosa in Java avrei un errore di compilazione! Invece, all'interprete Ruby non interessa che io "affermi" che un'Anatra è (IS-A) un Gallinaceo: se starnazza, allora lo è. Si fida! (Beato lui). E se gli passo un oggetto che non implementa "starnazza"? Be', semplicemente avrò un errore a run-time.

Si tratta dunque di due filosofie diverse: nella prima, io devo dichiarare sempre la classe dell'oggetto passato a un metodo, nella seconda posso farne a meno. Non c'è chiaramente una soluzione migliore dell'altra: nel primo caso privilegio le performance, e nel secondo la flessibilità del design.

(E se avete dei dubbi sul fatto che Oche e Anatre starnazzino, date un'occhiata qui!)

1 commento:

alepuzio ha detto...

ciao!
Direi più che altro che col primo sistema strutturi esplicitamente il codice in modo piu' "formale".