← Volver al listado de tecnologías

Metodos

Por: Artiko
gometodosreceiveroop

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

AspectoValue Receiver (r T)Pointer Receiver (r *T)
Modifica el originalNoSi
Copia el valorSiNo (pasa puntero)
Seguridad concurrenteMas seguro (copia aislada)Requiere sincronizacion
Structs grandesCostoso (copia completa)Eficiente (solo puntero)
InterfacesValor y puntero satisfacenSolo puntero satisface

Cuando Usar Cada Uno

Usa pointer receiver cuando:

Usa value receiver cuando:

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:

TipoMethod 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

ConceptoDescripcion
Value receiver(r T) trabaja sobre copia
Pointer receiver(r *T) trabaja sobre original
Resolucion automaticaGo ajusta & y * al llamar metodos
Tipos no-structCualquier tipo definido puede tener metodos
Method setT solo value, *T ambos
PromocionEmbedding promueve metodos al tipo padre

← Capitulo 9: Structs | Capitulo 11: Interfaces →