Continuiamo la nostra esplorazione di Scala, questo affascinante linguaggio Object Oriented con retrogusto funzionale. Oggi parliamo dei Tratti: come abbiamo già visto nel 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
Supponiamo 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:
abstract class EssereVivente abstract class Pianta extends EssereVivente abstract class Animale extends EssereVivente abstract class Fungo extends EssereVivente
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:
trait HaGambe extends Animale { def cammina() { println("Sto camminando!") } }
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:
trait HaAli extends Animale { def muoviAli() { println("Flap Flap...") } }
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:
trait PuoVolare { this: HaAli => def vola() { println("Sto volando!") } }
Tutti gli oggetti di tipo Uccello
hanno gambe e ali:
abstract class Uccello extends Animale with HaGambe with HaAli
ma l’Oca
sa volare, mentre lo Struzzo
no:
class Struzzo extends Uccello class Oca extends Uccello with PuoVolare
Creiamo infine un bel Gatto
:
class Gatto extends Animale with HaGambe
Abbiamo quasi messo in piedi uno zoo, direi che possiamo fermarci! Vediamo con un bel diagramma UML riepilogativo il modello che abbiamo costruito:
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:
object TrattiMain { def main(args: Array[String]): Unit = { val gustavo = new Oca() gustavo.vola } }
Ma non solo: possiamo arricchire di tratti una variabile mentre la istanziamo: in questo modo uno struzzo particolarmente atletico può riuscire a volare:
val struzzoAtleta = new Struzzo() with PuoVolare struzzoAtleta.vola
Che bel trucchetto, vero? Adesso voi direte: facciamo volare anche un gatto!
val gattoVolante = new Gatto() with PuoVolare gattoVolante.vola
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:
val gattoVolante = new Gatto() with HaAli with PuoVolare gattoVolante.vola
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!