308 lines
9.8 KiB
Markdown
308 lines
9.8 KiB
Markdown
# Orientado a objetos
|
|
|
|
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`).
|
|
|
|
## Métodos
|
|
|
|
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 de `byte`.
|
|
- Definimos un struct `Box` que tiene los campos height, width, length y color.
|
|
- Definimos un struct `BoxList` que tiene `Box` 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.
|
|
|
|
### Utilizar punteros como un receptor
|
|
|
|
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!
|
|
|
|
### Herencia de métodos
|
|
|
|
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()
|
|
}
|
|
```
|
|
### Sobrecarga de métodos
|
|
|
|
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.
|
|
|
|
## Enlaces
|
|
|
|
- [Índice](preface.md)
|
|
- Sección anterior: [struct](02.4.md)
|
|
- Siguiente sección: [interfaces](02.6.md)
|