Interfacce
Un'interfaccia è un concetto molto potente che sta alla base del concetto di astrazione e dell'effetto di polimorfismo di metodo.
Un'interfaccia è un tipo che lista una serie di dichiarazioni di funzioni, ciascuna con il nome e il/i tipo/i di ritorno.
Le interfacce vengono implementate da una struct scrivendo la serie di metodi dichiarati nell'interfaccia.
Come al solito Go è molto più pratico e meno formale, p.es. di Java. Non esiste la parola chiave implements, ne tanto meno extends con eredità di interfacce.
Esempio
(200interfaces.go):
package main
import "fmt"
import "math"
// Un'interface è come una struct ma tutti
// gli elementi sono metodi dichiarati
type geometry interface {
area() float64
perim() float64
}
// Due struct su cui implementare l'interface
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
// I metodi dichiarati in un'interface devono essere implementati
// per tutte le struct a cui si appliacano
// Per rect
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
// Per circle
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
// Se una funzione ha un parametro di tipo interface
// possiamo richiamare tutti i metodi dell'interface
// NB: qui non è definito se measure si applica a rect o circle
// La funzione è richiamabile per tutte le struct
// che implementano l'interface geometry
func measure(t string, g geometry) {
fmt.Print(t, g,": ")
fmt.Printf("area: %8.2f\n", g.area())
fmt.Printf("perimeter: %8.2f\n", g.perim())
fmt.Println()
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
// Dato che sia rect che circle implementano geometry
// si può invocare measure su r e su c
measure("rect", r)
measure("circle", c)
}
// La separazione tra interfaccia e implementazione
// è un pattern molto usato
Separazione di package
Vogliamo che le struct e l'interfaccia siano in un package e che i campi delle struct siano privati.
1
. Creare la directory figs
e andarvi dentro
mkdir figs
cd figs
2
. Editare il file figs.go
:
package figs
import (
"fmt"
"math"
)
// Dichiarazione dell'interfaccia
type Geometry interface {
area() float64
perim() float64
}
// Rettangolo
type Rect struct {
width, height float64
}
// Getters e setters per il rettangolo
func (r Rect) GetWidth() float64 {
return r.width
}
func (r *Rect) SetWidth(w float64) {
r.width = w
}
func (r Rect) GetHeight() float64 {
return r.height
}
func (r *Rect) SetHeight(w float64) {
r.height = w
}
// Implementazione dell'interfaccia
func (r Rect) area() float64 {
return r.width * r.height
}
func (r Rect) perim() float64 {
return 2*r.width + 2*r.height
}
// Cerchio
type Circle struct {
radius float64
}
// Getters e setters per il cerchio
func (c Circle) GetRadius() float64 {
return c.radius
}
func (c *Circle) SetRadius(rad float64) {
c.radius = rad
}
// Implementazione dell'interfaccia
func (c Circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c Circle) perim() float64 {
return 2 * math.Pi * c.radius
}
// E' opportuno che ogni funzione esportata
// abbia un commento a precedere
func Measure(t string, g Geometry) {
fmt.Print(t, g, ": ")
fmt.Printf("area: %8.2f\n", g.area())
fmt.Printf("perimeter: %8.2f\n", g.perim())
fmt.Println()
}
3
. Installare il package
go install
Nel nostro caso il suo percorso di package diventa
core/figs
4
. In un'altra directory del workspace scrivere il programma che lo usa:
(202-interface-package.go):
package main
import (
fig "core/figs"
"fmt"
)
func main() {
// Le seguenti istruzioni non funzionano
// perchè gli attributi non sono esportati
// r := fig.Rect{width: 3, height: 4}
// c := fig.Circle{radius: 5}
var r fig.Rect
r.SetWidth(10.0)
r.SetHeight(5.0)
fmt.Println("Rectangle: width= ", r.GetWidth(),
" height= ", r.GetHeight())
var c fig.Circle
c.SetRadius(5.0)
fmt.Println("Circle: radius= ", c.GetRadius())
// Dato che sia rect che circle implementano geometry
// si può invocare measure su r e su c
fig.Measure("rect", r)
fig.Measure("circle", c)
}
Costruttori
Go non possiede costruttori, ma si possono emulare con una convenzione: per una struct Rect inserire la funzione
func NewRect(w, h float64) *Rect {
return &Rect{w, h}
}
Notare che ritorna un puntatore a Rect, che viene silenziosamente allocato in memoria dinamica.
Tutti i metodi che agiscono sulla struct devono ora prendere un argomento puntatore.
Il programma di package diventa core/figure/figure.go
:
package figure
import (
"fmt"
"math"
)
// Dichiarazione dell'interfaccia
type Geometry interface {
area() float64
perim() float64
}
// Rettangolo
type Rect struct {
width, height float64
}
// Pseudocostruttore
func NewRect(w, h float64) *Rect {
return &Rect{w, h}
}
// Getters e setters per il rettangolo
func (r *Rect) GetWidth() float64 {
return r.width
}
func (r *Rect) SetWidth(w float64) {
r.width = w
}
func (r *Rect) GetHeight() float64 {
return r.height
}
func (r *Rect) SetHeight(w float64) {
r.height = w
}
// Implementazione dell'interfaccia
func (r *Rect) area() float64 {
return r.width * r.height
}
func (r *Rect) perim() float64 {
return 2*r.width + 2*r.height
}
// Cerchio
type Circle struct {
radius float64
}
// Pseudocostruttore
func NewCircle(r float64) *Circle {
return &Circle{r}
}
// Getters e setters per il cerchio
func (c *Circle) GetRadius() float64 {
return c.radius
}
func (c *Circle) SetRadius(rad float64) {
c.radius = rad
}
// Implementazione dell'interfaccia
func (c *Circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c *Circle) perim() float64 {
return 2 * math.Pi * c.radius
}
// E' opportuno che ogni funzione esportata
// abbia un commento a precedere
func Measure(t string, g Geometry) {
fmt.Print(t, g, ": ")
fmt.Printf("area: %8.2f\n", g.area())
fmt.Printf("perimeter: %8.2f\n", g.perim())
fmt.Println()
}
E il programma principale è:
(204-interface-constructor.go):
package main
import (
fig "core/figure"
"fmt"
)
func main() {
// r e c sono puntatori, ma non si nota
r := fig.NewRect(10, 5)
c := fig.NewCircle(5)
// Il resto del programma non cambia
fmt.Println("Rectangle: width= ", r.GetWidth(),
" height= ", r.GetHeight())
fmt.Println("Circle: radius= ", c.GetRadius())
fig.Measure("rect", r)
fig.Measure("circle", c)
}
Le convenzioni sono quindi:
- Le struct sono gestite per riferimento
- Il costruttore di
struct Pippo
èNewPippo
- Se un attributo si chiama
attr
- Il suo getter è
GetAttr
- Il suo setter è
SetAttr
Commento
Abbiamo riprodotto la sitazione di Java, in cui tutti gli oggetti sono silenziosamente gestiti per riferimento.