Metodi

Metodi1

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.