En Go, los metodos son funciones asociadas a un tipo. No existen clases, pero cualquier tipo definido por el usuario puede tener metodos.
Sintaxis de Metodos
Un metodo es una funcion con un receiver entre func y el nombre:
type Rectangulo struct {
Ancho, Alto float64
}
func (r Rectangulo) Area() float64 {
return r.Ancho * r.Alto
}
func (r Rectangulo) Perimetro() float64 {
return 2 * (r.Ancho + r.Alto)
}
func main() {
rect := Rectangulo{Ancho: 10, Alto: 5}
fmt.Println(rect.Area()) // 50
fmt.Println(rect.Perimetro()) // 30
}
El receiver (r Rectangulo) le dice a Go que Area pertenece al tipo Rectangulo.
Value Receiver vs Pointer Receiver
Esta es una de las decisiones mas importantes al definir metodos en Go.
Value Receiver
Trabaja sobre una copia del valor. No modifica el original:
type Contador struct {
Valor int
}
func (c Contador) Incrementar() {
c.Valor++ // modifica la copia, no el original
}
func main() {
c := Contador{Valor: 0}
c.Incrementar()
fmt.Println(c.Valor) // 0 (sin cambios)
}
Pointer Receiver
Trabaja sobre el original a traves de un puntero:
func (c *Contador) Incrementar() {
c.Valor++ // modifica el original
}
func main() {
c := Contador{Valor: 0}
c.Incrementar()
fmt.Println(c.Valor) // 1
}
Tabla Comparativa
| Aspecto | Value Receiver (r T) | Pointer Receiver (r *T) |
|---|---|---|
| Modifica el original | No | Si |
| Copia el valor | Si | No (pasa puntero) |
| Seguridad concurrente | Mas seguro (copia aislada) | Requiere sincronizacion |
| Structs grandes | Costoso (copia completa) | Eficiente (solo puntero) |
| Interfaces | Valor y puntero satisfacen | Solo puntero satisface |
Cuando Usar Cada Uno
Usa pointer receiver cuando:
- El metodo necesita modificar el receiver
- El struct es grande y quieres evitar copias
- Quieres consistencia: si un metodo usa pointer, todos deberian
Usa value receiver cuando:
- El metodo solo lee datos
- El tipo es pequeno (int, string, structs de pocos campos)
- Quieres un tipo inmutable por diseno
Resolucion Automatica de & y *
Go resuelve automaticamente punteros al llamar metodos:
type Circulo struct {
Radio float64
}
func (c *Circulo) Escalar(factor float64) {
c.Radio *= factor
}
func main() {
c := Circulo{Radio: 5}
// Go convierte automaticamente c en &c
c.Escalar(2) // equivale a (&c).Escalar(2)
fmt.Println(c.Radio) // 10
p := &Circulo{Radio: 3}
// Go desreferencia automaticamente para value receivers
fmt.Println(p.Radio) // equivale a (*p).Radio
}
Esta conveniencia sintactica aplica solo al llamar metodos, no al satisfacer interfaces.
Metodos en Tipos No-Struct
Puedes definir metodos sobre cualquier tipo que definas, no solo structs:
type Celsius float64
type Fahrenheit float64
func (c Celsius) AFahrenheit() Fahrenheit {
return Fahrenheit(c*9/5 + 32)
}
func (f Fahrenheit) ACelsius() Celsius {
return Celsius((f - 32) * 5 / 9)
}
func main() {
agua := Celsius(100)
fmt.Printf("%.1f°C = %.1f°F\n", agua, agua.AFahrenheit())
// 100.0°C = 212.0°F
}
Restriccion: solo puedes definir metodos para tipos declarados en el mismo paquete. No puedes agregar metodos a int o string directamente.
type MiSlice []int
func (s MiSlice) Suma() int {
total := 0
for _, v := range s {
total += v
}
return total
}
func main() {
nums := MiSlice{1, 2, 3, 4, 5}
fmt.Println(nums.Suma()) // 15
}
Method Sets
El method set define que metodos estan disponibles para un tipo. Esto es critico para interfaces:
| Tipo | Method Set |
|---|---|
T (valor) | Solo metodos con value receiver (t T) |
*T (puntero) | Metodos con value y pointer receiver |
type Notificador interface {
Notificar()
}
type Email struct {
Direccion string
}
func (e *Email) Notificar() {
fmt.Printf("Enviando a %s\n", e.Direccion)
}
func enviar(n Notificador) {
n.Notificar()
}
func main() {
e := Email{Direccion: "[email protected]"}
// enviar(e) // ERROR: Email no implementa Notificador
enviar(&e) // OK: *Email si implementa Notificador
}
Embedding y Promocion de Metodos
Cuando embedes un tipo, sus metodos se promueven al tipo contenedor:
type Animal struct {
Nombre string
}
func (a Animal) Hablar() string {
return fmt.Sprintf("%s hace un sonido", a.Nombre)
}
type Perro struct {
Animal // embedding
Raza string
}
func main() {
p := Perro{
Animal: Animal{Nombre: "Rex"},
Raza: "Pastor Aleman",
}
// Metodo promovido: accesible directamente
fmt.Println(p.Hablar()) // Rex hace un sonido
}
Puedes sobreescribir metodos promovidos definiendo uno con el mismo nombre:
func (p Perro) Hablar() string {
return fmt.Sprintf("%s ladra", p.Nombre)
}
func main() {
p := Perro{
Animal: Animal{Nombre: "Rex"},
Raza: "Pastor Aleman",
}
fmt.Println(p.Hablar()) // Rex ladra
fmt.Println(p.Animal.Hablar()) // Rex hace un sonido
}
Metodos como Valores de Funcion
Los metodos pueden usarse como valores, utiles para callbacks:
type Calculadora struct {
Acumulador float64
}
func (c *Calculadora) Sumar(v float64) {
c.Acumulador += v
}
func (c *Calculadora) Multiplicar(v float64) {
c.Acumulador *= v
}
func main() {
calc := &Calculadora{Acumulador: 10}
// Method value: vinculado a la instancia
op := calc.Sumar
op(5)
fmt.Println(calc.Acumulador) // 15
// Method expression: requiere pasar el receiver
mult := (*Calculadora).Multiplicar
mult(calc, 2)
fmt.Println(calc.Acumulador) // 30
}
Resumen
| Concepto | Descripcion |
|---|---|
| Value receiver | (r T) trabaja sobre copia |
| Pointer receiver | (r *T) trabaja sobre original |
| Resolucion automatica | Go ajusta & y * al llamar metodos |
| Tipos no-struct | Cualquier tipo definido puede tener metodos |
| Method set | T solo value, *T ambos |
| Promocion | Embedding promueve metodos al tipo padre |