Skip to content

Latest commit

 

History

History
307 lines (234 loc) · 9.75 KB

File metadata and controls

307 lines (234 loc) · 9.75 KB

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