Orientação a Objetos
Falamos sobre funções e estruturas nas duas últimas seções, mas você já considerou usar funções como campos de uma estrutura? Nesta seção, vou apresentá-lo a outra forma de função que possui um receptor, que é chamado method
(método).
method
Suponha que você definiu uma estrutura "rectangle" (retângulo) e quer calcular a sua área. Geralmente, usamos o seguinte código para atingir esse 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("Area of r1 is: ", area(r1))
fmt.Println("Area of r2 is: ", area(r2))
}
O exemplo acima pode calcular a área de um retângulo. Usamos a função chamada area
, mas ela não é um método da estrutura rectangle (como métodos de classe em linguagens orientadas a objetos clássicas). A função e a estrutura são duas coisas independentes, como você pode notar.
Isto não é um problema. Entretanto, se você também tem que calcular a área de um circulo, quadrado, pentágono ou qualquer outro tipo de forma, você vai precisar adicionar funções adicionais com nomes muito semelhantes.
Figure 2.8 Relacionamento entre funções e estruturas
Evidentemente, isso não é legal. Além disso, a área deve realmente ser a propriedade de um círculo ou retângulo.
Por estas razões, temos o conceito de method
. method
é afiliado ao tipo. Ele possui a mesma sintaxe que as funções, exceto por um parâmetro adicional após a palavra-chave func
chamada receiver
(receptor), que é o corpo principal desse método.
Usando o mesmo exemplo, Rectangle.area()
pertence diretamente ao rectangle, em vez de ser uma função periférica. Mais especificamente, length
, width
e area()
pertencem ao rectangle.
Como Rob Pike disse.
"Um método é um função com um primeiro argumento implícito, chamado receptor."
Sintaxe do método.
func (r ReceiverType) funcName(parameters) (results)
Vamos mudar nosso exemplo usando method
em vez disso.
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("Area of r1 is: ", r1.area())
fmt.Println("Area of r2 is: ", r2.area())
fmt.Println("Area of c1 is: ", c1.area())
fmt.Println("Area of c2 is: ", c2.area())
}
Notas para o uso de métodos.
- Se os nomes dos métodos são os mesmos, mas eles não compartilham os mesmos receptores, eles não são os mesmos método.
- Métodos são capazes de acessar campos dentro de receptores.
- Use
.
para chamar um método na estrutura, da mesma forma que os campos são chamados.
Figure 2.9 Métodos são diferentes em diferentes estruturas
No exemplo acima, os métodos area() pertencem ao Rectangle e ao Circle respectivamente, então os receptores são Rectangle e Circle.
Um ponto que vale notar é que o método com a linha pontilhada significa que o receptor é passado por valor, não por referência. A diferença entre eles é que um método pode mudar os valores do seu receptor quando o receptor é passado por referência, e outro recebe uma cópia do receptor quando o receptor é passado por valor.
O receptor só pode ser uma estrutura? Claro que não. Qualquer tipo pode ser o receptor de um método. Você pode estar confuso sobre tipos customizados. Estrutura é um tipo especial de tipo customizado -há mais tipos customizado.
Use o seguinte formato para definir um tipo customizado.
type typeName typeLiteral
Exemplos de tipos customizados:
type ages int
type money float32
type months map[string]int
m := months {
"January":31,
"February":28,
...
"December":31,
}
Espero que você saiba usar tipos customizados agora. Semelhante ao typedef
em C, usamos ages
para substituir int
no exemplo acima.
Vamos voltar a falar sobre method
.
Você pode usar quantos métodos quiser em tipos customizados.
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 //uma slice de caixas
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("We have %d boxes in our set\n", len(boxes))
fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("Let's paint them all black")
boxes.PaintItBlack()
fmt.Println("The color of the second one is", boxes[1].color.String())
fmt.Println("Obviously, now, the biggest one is", boxes.BiggestsColor().String())
}
Definimos algumas constantes e tipos customizados.
- Usamos
Color
como apelido parabyte
. - Definimos uma estrutura
Box
que tem os campos height (altura), width (largura), length (comprimento) e color (cor). - Definimos uma estrutura
BoxList
que temBox
como seu campo.
Então, definimos alguns métodos para os nossos tipos customizados.
- Volume() usa Box como seu receptor e retorna o volume de Box.
- SetColor(c Color) muda a cor de Box.
- BiggestsColor() retorna a cor que possui o maior volume.
- PaintItBlack() define as cores das Box em BoxList para preto.
- String() usa Color como seu receptor e retorna a palavra formatada do nome da cor.
É muito mais claro quando usamos palavras para descrever nosso requisitos? Frequentemente escrevemos nossos requisitos antes de começar a programar.
Usando ponteiro como receptor
Vamos dar uma olhada no método SetColor
. Seu receptor é um ponteiro de Box. Sim, você pode usar *Box
como um receptor. Por que usamos um ponteiro aqui? Porque queremos mudar as cores de Box neste método. Assim, se não usarmos um ponteiro, ele só mudará o valor dentro de um cópia de Box.
Se vemos que um receptor é o primeiro argumento de um método, não é difícil entender como ele funciona.
Você deve estar perguntando por que não estamos usando (*b).Color=c
em vez de b.Color=c
no método SetColor(). Qualquer um está OK aqui porque GO sabe como interpretar a atribuição. Você acha que Go é mais fascinante agora?
Você também deve estar se perguntando se devemos usar (&bl[i]).SetColor(BLACK)
em PaintItBlack
porque passamos um ponteiro para SetColor
. Novamente, qualquer um está OK porque Go sabe como interpretá-lo!
Herança do Método
Aprendemos sobre herança de campos na última seção. Similarmente, também temos herança de método em Go. Se um campo anônimo tiver métodos, então a estrutura que contém o campo terá todos os métodos dele também.
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
}
// define um método em Human
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %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étodo
Se quisermos que Employee tenha seu próprio método SayHi
, podemos definir um método com o mesmo nome em Employee, e ele irá sobrescrever SayHi
em Human quando nós o chamarmos.
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("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Sim, você pode dividir em 2 linhas aqui.
}
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()
}
Você é capaz de escrever um programa Orientado a Objetos agora, e os métodos usam a regra de capital letter (letra maiúscula) para decidir se é público ou privado também.