Metodi
Un metodo è una funzione che si applica solo ad un determinato tipo.
La sintassi è:
func (var tipoinvocante) nomemetodo() tiporitorno { ... }
Qui var
è l'identificatore formale del (pseudo)oggetto invocante, e può essere per valore o riferimento.
Un esempio (segmento di codice invocante - pseudocodice) può essere:
var x tipoinvocante
var y tiporitorno
y = x.nomemetodo()
L'oggetto x
invoca il nomemetodo con l'operatore punto, come in altri linguaggi propriamente Object Oriented.
Un programma più completo è il seguente.
(19methods.go):
package main
import "fmt"
type rect struct {
width, height int
}
// Questa funzione prende come argomento
// un puntatore a struct rect
func (r *rect) area() int {
return r.width * r.height
}
// Questa funzione prende come argomento
// una struct rect passata per valore
func (r rect) perim() int {
return 2*r.width + 2*r.height
}
// Le funzioni sopra diventano effettivamente 'metodi'
// della struct rect - come se fosse una classe
// NB: i metodi sono dichiarati esterni, non interni
// alla struct (<> C++ e Java)
func main() {
r := rect{width: 10, height: 5}
// Non vi è differenza di comportamento all'invocazione
// (completamente diverso dal C)
// Il comportamento interno è diverso ma la
// forma sintattica è uguale - dereferenziazione automatica
fmt.Println("area: ", r.area())
fmt.Println("perim:", r.perim())
// Se rp è un puntatore a r il comportamento non cambia
rp := &r
fmt.Println("area: ", rp.area())
fmt.Println("perim:", rp.perim())
// Si usa il puntatore per
// 1. evitare la copia di tutta la struct
// 2. far si che il metodo cambi la struct originale
}
Nel codice
func (r rect) perim() int {
si indica che solo una variabile var
di tiporect
, che è una struct, può invocare la funzione perim
qui definita, con var.perim()
e tale variabile sarà nota formalmente e localmente col nome r
.
Non è necessario che l'oggetto invocante (attuale) si chiami r
, solo conveniente.
Nel codice
func (r *rect) area() int {
si indica che solo un puntatore a struct di tipo rect può invocare la funzione area.
Se fossimo in linguaggio C++ parleremmo di funzione membro della struct (che però sarebbe una classe), e in Java di metodo.
In Go non esistono classi, solo struct. I metodi sono definiti fuori dalle struct.
Del resto in C++ una classe altro non è che una struct con funzioni membro oltre che variabili membro.
Concetto di Object Oriented: Incapsulamento
In linguaggi tradizionali (C++/Java) viene ritenuto vantaggioso l'incapsulamento dei dati, per ridurre l'accoppiamento di dipendenza tra classi.
Ciò si ottiene
- rendendo gli attributi private
- costruendo dei metodi public per l'accesso e il cambiamento degli attributi (getters e setters)
Questo si può fare anche in Go, con un po' di organizzazione
- ponendo la struct in un package separato
- dando nomi ai campi che iniziano con la minuscola (non sono esportati dal package)
- creando delle funzioni accessorie e mutatorie con nomi che iniziano con la maiuscola (sono automaticamene esportati dal package)
Per esempio:
1
. Creare la directory figures e andarvi dentro
mkdir figures
cd figures
2
. Editare il file figures.go
come segue:
package figures
type Rect struct {
width, height int
}
func (r Rect) GetWidth() int {
return r.width
}
func (r *Rect) SetWidth(w int) {
r.width = w
}
func (r Rect) GetHeight() int {
return r.height
}
func (r *Rect) SetHeight(w int) {
r.height = w
}
3
. Installarlo:
go install
Nel nostro caso il suo percorso di package diventa core/figures
4
. In un'altra directory del workspace scrivere il programma che lo usa:
(19a-use-figures.go):
package main
import "fmt"
// import del package con alias
import fig "core/figures"
func main() {
// Dichiarazione di variabile
var rec fig.Rect
// Le seguenti non funzionano:
// rec.width = 10
// fmt.Println("Direct: ", rec.width)
// Questo è il modo corretto
rec.SetWidth(10)
fmt.Println("Width: ", rec.GetWidth())
rec.SetHeight(5)
fmt.Println("Height: ", rec.GetHeight())
}
Notare che il getter può operare su una struct passata per copia, mentre il setter deve operare su una struct passata tramite puntatore, perchè deve modificare un campo dell'originale e non della copia.