Files
2017-02-19 00:21:17 -03:00

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.
![](images/2.5.rect_func_without_receiver.png?raw=true)
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.
![](images/2.5.shapes_func_with_receiver_cp.png?raw=true)
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)