En las ultimas dos secciones hablamos de funciones y struct, alguna vez pensaste en usar funciones como campos de un struct? En esta sección, voy a realizar una introducción a otro tipo de funciones , que vamos a llamar métodos (method
).
Supongamos que definimos un struct del estilo rectángulo(rectangle), y buscas calcular el área, por lo general vamos a usar el siguiente código para lograr este objetivo.
package main
import "fmt"
type Rectangle struct {
width, height float64
}
func area(r Rectangle) float64 {
return r.width*r.height
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
fmt.Println("El área de r1 es: ", area(r1))
fmt.Println("El área de r2 is: ", area(r2))
}
En el ejemplo de arriba podemos calcular el área de un rectángulo, usamos la función llamada area
, pero no es un método de un struct “rectangle” (como un método en un clase en un lenguaje orientado a objeto clásico). La función y el struct como se ve son dos cosas independientes.
Hasta ahora eso no es un problema. Pero que pasa si quiere calcular el área de un circulo, cuadrado, pentágono, u otra figura, vamos a tener que agregar mas funciones con nombres muy similares.
Figure 2.8 Relación entre funciones y struct
Obviamente, no es “cool”. Además el área debería ser propiedad del circulo o el rectángulo.
Por estas razones, tenemos conceptos sobre métodos
. métodos
es afiliado a un tipo, tienen la misma sintaxis que una función excepto por una palabra más después de la palabra reservada func
que es llamada receptor (receiver
) que es el cuerpo principal de ese método.
Utiliza el mismo ejemplo, Rectangle.area()
pertenece al rectángulo, no como una función periférica. Mas específicamente, length
, width
y area()
todos pertenecen a rectángulo.
Como dice Rob Pike.
"Un método es una función con un primer argumento implícito, llamado receiver."
Sintaxis de un método.
func (r ReceiverType) funcName(parameters) (results)
Cambiemos el ejemplo anterior para utilizar métodos.
package main
import (
"fmt"
"math"
)
type Rectangle struct {
width, height float64
}
type Circle struct {
radius float64
}
func (r Rectangle) area() float64 {
return r.width*r.height
}
func (c Circle) area() float64 {
return c.radius * c.radius * math.Pi
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
c1 := Circle{10}
c2 := Circle{25}
fmt.Println("El área de r1 es: ", r1.area())
fmt.Println("El área de r2 es: ", r2.area())
fmt.Println("El área de c1 es: ", c1.area())
fmt.Println("El área de c2 es: ", c2.area())
}
Notas para el uso de métodos.
- Si el nombre de un método es el mismo, pero no tiene el mismo receptor, ellos no son iguales.
- Los métodos son capaces de acceder a los campos en los receptores.
- Usamos el
.
para llamar al método en el struct, al igual que cuando llamamos a un campo.
Figure 2.9 Los métodos son diferentes en diferentes struct
En el ejemplo anterior, el método area() es respectivamente perteneciente a Rectangle y a Circle, por lo que los receptores son Rectangle y Circle.
Una cosa es importante notar, que un método con una línea de puntos significa que el receptor se pasa por valor, no por referencia. La diferencia entre ellos es que el método podría cambiar el valor del receptor cuando el mismo es pasado por referencia, y este toma una copia del receptor cuando es pasado por valor.
¿El receptor puede ser unicamente un struct? Por supuesto que no, cualquier tipo podría ser el receptor en un método. Puede ser un poco confuso cuando hablamos de tipos personalizados(“customized type”), struct es un tipo especial de tipo modificado, hay varios tipos personalizado.
Utilice el siguiente formato para definir un tipo personalizado.
type typeName typeLiteral
Un ejemplo sobre tipos personalizados.
type ages int
type money float32
type months map[string]int
m := months {
"January":31,
"February":28,
...
"December":31,
}
Espero que ahora sepa como utilizar los tipos personalizados. Es similar a typedef
en C, nosotros usamos ages
para sustituir a int
en el ejemplo anterior.
Volvamos a los métodos
.
Puedes utilizar tantos métodos en tipos personalizados como desees.
package main
import "fmt"
const(
WHITE = iota
BLACK
BLUE
RED
YELLOW
)
type Color byte
type Box struct {
width, height, depth float64
color Color
}
type BoxList []Box //un slice de boxes
func (b Box) Volume() float64 {
return b.width * b.height * b.depth
}
func (b *Box) SetColor(c Color) {
b.color = c
}
func (bl BoxList) BiggestsColor() Color {
v := 0.00
k := Color(WHITE)
for _, b := range bl {
if b.Volume() > v {
v = b.Volume()
k = b.color
}
}
return k
}
func (bl BoxList) PaintItBlack() {
for i, _ := range bl {
bl[i].SetColor(BLACK)
}
}
func (c Color) String() string {
strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
return strings[c]
}
func main() {
boxes := BoxList {
Box{4, 4, 4, RED},
Box{10, 10, 1, YELLOW},
Box{1, 1, 20, BLACK},
Box{10, 10, 1, BLUE},
Box{10, 30, 1, WHITE},
Box{20, 20, 20, YELLOW},
}
fmt.Printf("Tenemos %d boxes en nuestro conjunto\n", len(boxes))
fmt.Println("El volumen de la primera es ", boxes[0].Volume(), "cm³")
fmt.Println("El color de la última es",boxes[len(boxes)-1].color.String())
fmt.Println("La más grande es", boxes.BiggestsColor().String())
fmt.Println("Vamos a pintarlas a todas de negro")
boxes.PaintItBlack()
fmt.Println("El color de la segunda es", boxes[1].color.String())
fmt.Println("Obviamente, ahora, la más grande es", boxes.BiggestsColor().String())
}
Vamos a definir algunas constantes y tipos personalizados.
- Usamos
Color
como un alias debyte
. - Definimos un struct
Box
que tiene los campos height, width, length y color. - Definimos un struct
BoxList
que tieneBox
como sus campos.
Entonces vamos a definir algunos métodos para personalizar nuestros tipos.
- Volume() usa a Box como su receptor, devuelve el volumen de Box.
- SetColor(c Color) cambiar el color de las Box.
- BiggestsColor() devuelve el colo que tiene la de mayor volumen.
- PaintItBlack() configuramos el color para todas las Box en BoxList a negro.
- String() usamos Color como su receptor, devolvemos un string con formato de el nombre del color.
¿Es mucho mas claro cuando usamos palabras para describir nuestro requerimientos? Usualmente escribimos nuestros requerimientos antes de comenzar a programar.
Vamos a mirar el método SetColor
, su receptor es un puntero de tipo Box. Si, puedes usar *Box
como receptor. ¿Por qué usaríamos un puntero aquí? Porque buscamos cambiar el color de Box en este método, si no usamos un puntero, solo cambiaría el valor de la copia de Box.
Si vemos el receptor como el primer argumento de los métodos, no es difícil de entender como funcionan estos.
Podría decir que deberíamos usar *b.Color=c
en vez de b.Color=c
en el método SetColor(). Cualquiera de los dos estaría bien, porque Go lo sabe interpretar. ¿Ahora crees que Go es mas fascinante?
También podría decir que deberíamos usar (&bl[i]).SetColor(BLACK)
en PaintItBlack
porque le pasamos un puntero a SetColor
. Una vez más, cualquiera de los dos esta bien, porque ¡Go sabe interpretarlo correctamente!
Aprendimos sobre la herencia de campos en la última sección, también tenemos la herencia de métodos en Go. Así que si un campo anónimo tiene métodos, el struct que contiene el campo tendrá todos los métodos del mismo.
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human // campo anónimo
school string
}
type Employee struct {
Human
company string
}
// definimos un método en Human
func (h *Human) SayHi() {
fmt.Printf("Hola, Yo soy %s puedes llamarme al %s\n", h.name, h.phone)
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
Si queremos que Employee tenga su propio método llamado SayHi
, podemos definir el método con el mismo nombre que el de Employee, y así ocultaremos SayHi
de Human cuando lo llamemos.
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human
school string
}
type Employee struct {
Human
company string
}
func (h *Human) SayHi() {
fmt.Printf("Hola, Yo soy %s podes llamarme al %s\n", h.name, h.phone)
}
func (e *Employee) SayHi() {
fmt.Printf("Hola, Yo soy %s, trabajo en %s. Podes llamarme al %s\n", e.name,
e.company, e.phone) //Si, lo podes cortar en dos líneas.
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
Ahora eres capaz de escribir programas que utilicen el paradigma Orientado a Objetos, los métodos utilizan la regla de que los que tienen nombre que se inician con mayúsculas van a ser públicos o privados en caso contrario.
- Índice
- Sección anterior: struct
- Siguiente sección: interfaces