ScalaContinuiamo la nostra esplorazione di Scala, questo affascinante linguaggio Object Oriented con retrogusto funzionale. Oggi parliamo dei Tratti: come abbiamo già visto nel Post Introduttivo su Scala i tratti sono maledettamente simili alle interfacce in Java; i tratti tuttavia consentono anche di implementare dei metodi (ovvero non devono essere necessariamente super astratti) e possiedono altre caratteristiche interessanti: oggi le vedremo assieme e scopriremo come i tratti di Scala possano essere molto flessibili e potenti.

L’esempio delle cose viventi

Uml Esseri ViventiSupponiamo di dover modellizzare un modello biologico molto semplice: esiste il concetto più generico di EssereVivente, che a sua volta può essere classificato come Pianta, Animale o Fungo. Questa classificazione è molto generica e le classi del modello corrispondenti sono astratte:

[code lang=”scala”]
abstract class EssereVivente
abstract class Pianta extends EssereVivente
abstract class Animale extends EssereVivente
abstract class Fungo extends EssereVivente
[/code]

Tutto sarà un EssereVivente o un suo sottotipo. Supponiamo adesso di voler aumentare il dettaglio: tra questi animali possiamo operare infatti un’ulteriore distinzione, tra quelli che sono dotati di zampe/gambe e quelli che non lo sono. In Java useremmo le interfacce, in Scala sotto con i tratti:

[code lang=”scala”]
trait HaGambe extends Animale {
def cammina() { println("Sto camminando!") }
}
[/code]

Come dicevamo i metodi contenuti nei tratti possono avere un’implementazione (anche se poco significativa come un println :-)). Ma c’è di più in questo caso: cosa significa che il tratto HaGambe estende una classe? Significa che è possibile applicare il tratto solo a classi che sono sottotipi di Animale: in caso contrario il compilatore si lamenterà; bene, siamo riusciti a modelizzare il fatto che solo gli animali possono avere le gambe!

Focalizziamo ora la nostra attenzione sui pennuti: un tratto HaAli ci sta proprio bene:

[code lang=”scala”]
trait HaAli extends Animale {
def muoviAli() { println("Flap Flap…") }
}
[/code]

Vogliamo ora creare un tratto PuoVolare, ma vorremmo che fosse definibile una regola per cui solo chi ha le ali può volare. E’ possibile farlo nel modo seguente:

[code lang=”scala”]
trait PuoVolare {
this: HaAli =>
def vola() { println("Sto volando!") }
}
[/code]

Tutti gli oggetti di tipo Uccello hanno gambe e ali:

[code lang=”scala”]
abstract class Uccello extends Animale with HaGambe with HaAli
[/code]
ma l’Oca sa volare, mentre lo Struzzo no:
[code lang=”scala”]
class Struzzo extends Uccello
class Oca extends Uccello with PuoVolare
[/code]

Creiamo infine un bel Gatto:

[code lang=”scala”]
class Gatto extends Animale with HaGambe
[/code]

Abbiamo quasi messo in piedi uno zoo, direi che possiamo fermarci! Vediamo con un bel diagramma UML riepilogativo il modello che abbiamo costruito:


Uml Esseri Viventi Completo

A proposito: questo diagramma è stato creato online con yUML, tool interessante che colgo l’occasione di segnalare.

Venghino siori e siore

Vediamo cosa possiamo fare con la gerarchia di classi e tratti che abbiamo impostato. Ovviamente possiamo fare volare un’oca:

[code lang=”scala”]
object TrattiMain {
def main(args: Array[String]): Unit = {
val gustavo = new Oca()
gustavo.vola
}
}
[/code]
Ma non solo: possiamo arricchire di tratti una variabile mentre la istanziamo: in questo modo uno struzzo particolarmente atletico può riuscire a volare:
[code lang=”scala”]
val struzzoAtleta = new Struzzo() with PuoVolare
struzzoAtleta.vola
[/code]

Che bel trucchetto, vero? Adesso voi direte: facciamo volare anche un gatto!

[code lang=”scala”]
val gattoVolante = new Gatto() with PuoVolare
gattoVolante.vola
[/code]

Otteniamo invece questo errore in fase di compilazione:

illegal inheritance; self-type it.cosenonjaviste.tratti.Gatto with it.cosenonjaviste.tratti.PuoVolare does not conform to it.cosenonjaviste.tratti.PuoVolare‘s selftype it.cosenonjaviste.tratti.PuoVolare with it.cosenonjaviste.tratti.HaAli TrattiMain.scala /Tratti/src/it/cosenonjaviste/tratti line 12 Scala Problem

Ricordate? Avevamo stabilito che il tratto PuoVolare può essere applicato solo a chi possiede anche il tratto HaAli. Possiamo sistemare attaccando al nostro gatto delle ali di carta:

[code lang=”scala”]
val gattoVolante = new Gatto() with HaAli with PuoVolare
gattoVolante.vola
[/code]

Conclusioni

Con questo breve post zoologico abbiamo salito un altro gradino di Scala e abbiamo visto come i tratti siano flessibili e sicuri: Platone, che definiva l’essere umano un “Bipede implume dotato di anima“, aveva sicuramente in mente i tratti Scala, nella sua lungimiranza :-).

L’ereditarietà multipla del C++, potentissima ma complicatissima, sembra essere stata sacrificata sull’altare della semplificazione dei linguaggi più moderni mentre l’astrazione delle interfacce offerta da Java talvolta rende difficile centralizzare il codice; per fortuna sembra proprio che la flessibilità sia un…Tratto distintivo di Scala.

Alla prossima!

2 thoughts on “Alla faccia dell'interfaccia: Scala a Tratti”

Comments are closed.