308 lines
9.6 KiB
Markdown
308 lines
9.6 KiB
Markdown
# 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 para `byte`.
|
|
- Definimos uma estrutura `Box` que tem os campos height (altura), width (largura), length (comprimento) e color (cor).
|
|
- Definimos uma estrutura `BoxList` que tem `Box` 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.
|
|
|
|
## Links
|
|
|
|
- [Sumário](preface.md)
|
|
- Seção Anterior: [Estrutura](02.4.md)
|
|
- Próxima Seção: [Interface](02.6.md)
|