Visualizzazione post con etichetta mixin. Mostra tutti i post
Visualizzazione post con etichetta mixin. Mostra tutti i post

venerdì 10 luglio 2009

Ereditarietà multipla in Scala

Il problema dell'ereditarietà multipla nei linguaggi fortemente tipizzati (C++) è, molto banalmente, che se un oggetto eredita da due differenti classi che implementano lo stesso metodo, il compilatore non sa quale implementazione di quel metodo associargli (bind).

In Java e in C# perciò si è deciso di abolire l'ereditarietà multipla, e di inserire il concetto di interfaccia. Il problema delle interfacce, però, è che attraverso di loro viene descritto il comportamento di un oggetto (quali metodi sicuramente quel metodo avrà), ma non viene implementato, perché l'implementazione si demanda alla classe di quell'oggetto.

Questa soluzione non è ottimale, perché se nella mia tassonomia di classi ho delle forti "somiglianze" tra una classe e l'altra, la scrittura dell'implementazione dell'interfaccia comune rischia di doversi ripetere da una classe all'altra. Insomma, rischiamo ripetizioni e riscritture.

In Ruby esiste il concetto di mixin, per cui posso "prendere a prestito" del codice che esiste "altrove" e inserirlo nella mia classe, senza doverlo riscrivere. Il problema è che Ruby è un linguaggio di scripting, e non è tipizzato.

Scala è invece un linguaggio tipizzato che permette - in un certo senso! - l'ereditarietà multipla, e lo fa attraverso i "trait".

Ecco un esempio:


abstract class Animale
{
def nome:String;
def classe:String;
def comeMiChiamo:String = ("Sono il " + this.nome + " e sono un " + this.classe);
}

trait Mammifero extends Animale
{
override def classe:String = "Mammifero";
}

trait Rettile extends Animale
{
override def classe:String = "Rettile";
}

class Delfino(aName: String) extends Animale with Mammifero
{
def nome:String = "delfino "+ aName;
}

class Serpente(aName: String) extends Animale with Rettile
{
def nome:String = "serpente "+aName;
}

object Main
{
def main(args: Array[String])
{
parla(new Delfino("Pippo"));
parla(new Serpente("Pluto"));
}

def parla(animale: Animale)
{
println(animale.comeMiChiamo);
}
}


In questo esempio, la classe Animale è una classe astratta, che definisce il metodo "comeMiChiamo" (ok, non mi veniva niente di meglio!). Questo metodo è polimorfico rispetto alla "specie" dell'animale. Definisco perciò due nuove classi, che incidentalmente sono anche "classi di animali": i rettili e i mammiferi.

L'output del programma in console è:


Sono il delfino Pippo e sono un Mammifero
Sono il serpente Pluto e sono un Rettile


Quello che voglio dimostrare, è che posso scrivere in Scala una classe che eredita sia da Animale (il metodo "come mi chiamo"), sia da Rettile oppure Mammifero. Per farlo, "Rettile" e "Mammifero" sono due trait, di fatto la trasposizione dei Mixin per Scala. Se vogliamo, sono delle interfacce, solo che, a differenza delle interfacce in Java, il metodo che espongono è anche già implementato - evitandoci riscritture e copia/incolla nel codice (che è sempre male).

PS: La questione della duplicazione dei metodi alla base del problema dell'ereditarietà multipla, non è risolta, in Scala, è solo "evitata", dal momento che il compilatore, quando si tratta di "trait", fa un bel copia e incolla e non si preoccupa di controllare la coerenza - provate infatti ad esempio a far ereditare a Delfino sia il trait Mammifero che il trait Rettile:


class Delfino(aName: String) extends Animale with Mammifero with Rettile

martedì 22 gennaio 2008

Mixin come interfacce in Ruby

Un utilizzo forse un po' anomalo dei mixin in Ruby è quello di considerarli alla stregua di interfacce per implementare un comportamento polimorfico.

Chi conosce almeno un linguaggio a oggetti puro (come Java o C#) sa che le interfacce sono dei contratti che il programmatore stipula con l'utilizzatore del suo codice sorgente per garantirgli che una classe "implementa" certi metodi, con certi parametri. Le interfacce sono alla base dell'utilizzo del polimorfismo nei linguaggi a oggetti compilati.

In genere nei linguaggi di scripting a oggetti, possiamo utilizzare il polimorfismo anche senza le interfacce, cioè ammettendo che, se l'oggetto passato al metodo polimorfico non implementa determinati metodi, avremo un errore a run-time.

In realtà a volte può essere difficile se non impossibile, non avendo a disposizione ad esempio il commento di un certo codice sorgente, sapere se una certa classe è stata progettata per un utilizzo polimorfico.

Bene, grazie ai mixin, noi possiamo rendere esplicita la nostra intenzione di voler far implementare a una classe una determinata interfaccia, o, in altri termini, ad assicurare il programmatore che utilizzerà il nostro codice, che questo utilizza un certo modulo come interfaccia, quindi garantendo che i metodi in quel modulo saranno implementati.

Ad esempio:


module Animale
def mangio
puts "Sono un animale e mangio"
end
end

class Uccello
def volo
puts "Sono un uccello e volo"
end
end

class Gazzella < Uccello
include Animale

def mangio
puts "Sono una gazzella e mangio"
end
end

def metodopolimorfico(animale)
animale.mangio
end

metodopolimorfico(Gazzella.new)

In questo codice noi abbiamo una classe di Gazzelle che eredita dalla classe base Uccello. Qui il programmatore vuole dirci che Gazzella è (IS-A) sì un Uccello, ma è "anche" un Animale (infatti implementa "mangio"). In questo modo il client-programmer che vuole sapere se potrà passare una "Gazzella" al suo metodo "metodopolimorfico" che prende in ingresso un "Animale" è assicurato: siccome Gazzella è un mixin di Animale, sicuramente rispetta l'interfaccia di Animale (che nel nostro caso è il metodo mangio).

Non solo: Gazzella può fare l'override di un metodo incluso. Infatti lo fa, e "mangio" di Gazzella ha un'implementazione diversa da "mangio" di Animale. Chiaramente, poteva anche non farlo, mantenendo l'implementazione originale (e qui sta la differenza con le interfacce, che sono sempre astratte e vanno sempre e comunque implementate).