396 lines
13 KiB
Markdown
396 lines
13 KiB
Markdown
# 2.6 Interface
|
|
|
|
## Interface
|
|
|
|
Uma das características de projeto mais sutil em Go são interfaces. Depois de ler esta seção, você provavelmente ficará impressionado com sua implementação.
|
|
|
|
### O que é uma interface
|
|
|
|
Resumidamente, uma interface é um conjunto de métodos que usamos para definir um conjunto de ações.
|
|
|
|
Como os exemplos nas seções anteriores, tanto Student (Estudante) quanto Employee (Empregado) podem `SayHi()` (DigaOi()), mas não fazem a mesma coisa.
|
|
|
|
Vamos fazer mais trabalho. Vamos adicionar mais um método `Sing()` (Cantar()) para eles, juntamente com o método `BorrowMoney()` (EmprestarDinheiro()) para Student e `SpendSalary()` (GastarSalario()) para Employee.
|
|
|
|
Agora, Student tem três métodos chamados `SayHi()`, `Sing()` e `BorrowMoney()`, e Employee tem `SayHi()`, `Sing()` e `SpendSalary()`.
|
|
|
|
Esta combinação de métodos é chamada de interface e é implementada por ambos Student e Employee. Então, Student e Employee implementam a interface: `SayHi()` e `Sing()`. Ao mesmo tempo, Employee não implementa a interface: `SayHi()`, `Sing()`, `BorrowMoney()`, e Student não implementa a interface: `SayHi()`, `Sing()`, `SpendSalary()`. Isso ocorre porque Employee não tem o método `BorrowMoney()` e Student não tem o método `SpendSalary()`.
|
|
|
|
### Tipo de Interface
|
|
|
|
Uma interface define um conjunto de métodos, portanto, se um tipo implementa todos os métodos, dizemos que ele implementa a interface.
|
|
|
|
type Human struct {
|
|
name string
|
|
age int
|
|
phone string
|
|
}
|
|
|
|
type Student struct {
|
|
Human
|
|
school string
|
|
loan float32
|
|
}
|
|
|
|
type Employee struct {
|
|
Human
|
|
company string
|
|
money float32
|
|
}
|
|
|
|
func (h *Human) SayHi() {
|
|
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
|
}
|
|
|
|
func (h *Human) Sing(lyrics string) {
|
|
fmt.Println("La la, la la la, la la la la la...", lyrics)
|
|
}
|
|
|
|
func (h *Human) Guzzle(beerStein string) {
|
|
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
|
|
}
|
|
|
|
// Employee sobrecarrega Sayhi
|
|
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 (s *Student) BorrowMoney(amount float32) {
|
|
s.loan += amount // (novamente e novamente e...)
|
|
}
|
|
|
|
func (e *Employee) SpendSalary(amount float32) {
|
|
e.money -= amount // Mais vodka por favor!!! Para aguentar o dia! (More vodka please!!! Get me through the day!)
|
|
}
|
|
|
|
// define interface
|
|
type Men interface {
|
|
SayHi()
|
|
Sing(lyrics string)
|
|
Guzzle(beerStein string)
|
|
}
|
|
|
|
type YoungChap interface {
|
|
SayHi()
|
|
Sing(song string)
|
|
BorrowMoney(amount float32)
|
|
}
|
|
|
|
type ElderlyGent interface {
|
|
SayHi()
|
|
Sing(song string)
|
|
SpendSalary(amount float32)
|
|
}
|
|
|
|
Sabemos que uma interface pode ser implementada por qualquer tipo, e um tipo pode implementar muitas interfaces simultaneamente.
|
|
|
|
Observe que qualquer tipo implementa a interface vazia `interface{}` porque ela não tem nenhum método e todos os tipos possuem zero métodos por padrão.
|
|
|
|
### Valor da interface
|
|
|
|
Então, que tipo de valores podem ser colocados na interface? Se definirmos uma variável como uma interface de tipo, qualquer tipo que implemente a interface pode ser atribuído a essa variável.
|
|
|
|
Como no exemplo acima, se definirmos uma variável "m" como interface Men, então qualquer Student, Human ou Employee pode ser atribuído a "m". Então nós poderíamos ter um slice de Men, e qualquer tipo que implemente a interface Men pode atribuir a este slice. Lembre-se no entanto que o slice da interface não tem o mesmo comportamento de uma slice de outros tipos.
|
|
|
|
package main
|
|
|
|
import "fmt"
|
|
|
|
type Human struct {
|
|
name string
|
|
age int
|
|
phone string
|
|
}
|
|
|
|
type Student struct {
|
|
Human
|
|
school string
|
|
loan float32
|
|
}
|
|
|
|
type Employee struct {
|
|
Human
|
|
company string
|
|
money float32
|
|
}
|
|
|
|
func (h Human) SayHi() {
|
|
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
|
|
}
|
|
|
|
func (h Human) Sing(lyrics string) {
|
|
fmt.Println("La la la la...", lyrics)
|
|
}
|
|
|
|
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.
|
|
}
|
|
|
|
// Interface Men implementada por Human, Student e Employee
|
|
type Men interface {
|
|
SayHi()
|
|
Sing(lyrics string)
|
|
}
|
|
|
|
func main() {
|
|
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
|
|
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
|
|
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
|
|
tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}
|
|
|
|
// define interface i
|
|
var i Men
|
|
|
|
// posso armazenar Student
|
|
i = mike
|
|
fmt.Println("This is Mike, a Student:")
|
|
i.SayHi()
|
|
i.Sing("November rain")
|
|
|
|
// posso armazenar Employee
|
|
i = tom
|
|
fmt.Println("This is Tom, an Employee:")
|
|
i.SayHi()
|
|
i.Sing("Born to be wild")
|
|
|
|
// slice de Men
|
|
fmt.Println("Let's use a slice of Men and see what happens")
|
|
x := make([]Men, 3)
|
|
// Estes três elementos são de tipos diferentes, mas todos eles implementam a interface Men
|
|
x[0], x[1], x[2] = paul, sam, mike
|
|
|
|
for _, value := range x {
|
|
value.SayHi()
|
|
}
|
|
}
|
|
|
|
|
|
Uma interface é um conjunto de métodos abstratos, e pode ser implementada por tipos não interface. Não pode, portanto, implementar-se.
|
|
|
|
### Interface vazia
|
|
|
|
Uma interface vazia é uma interface que não contém nenhum método, portanto, todos os tipos implementam uma interface vazia. Esse fato é muito útil quando queremos armazenar todos os tipos em algum momento, e é semelhante ao void* em C.
|
|
|
|
// define a como interface vazia
|
|
var a interface{}
|
|
var i int = 5
|
|
s := "Hello world"
|
|
// a pode armazenar valor de qualquer tipo
|
|
a = i
|
|
a = s
|
|
|
|
Se uma função usa uma interface vazia como seu tipo de argumento, ela pode aceitar qualquer tipo; Se uma função usa a interface vazia como seu tipo de valor de retorno, ela pode retorna qualquer tipo.
|
|
|
|
### Argumentos de métodos de uma interface
|
|
|
|
Qualquer variável pode ser usada em uma interface. Então, como podemos usar esse recurso para passar qualquer tipo de variável para uma função?
|
|
|
|
Por exemplo, usamos muito fmt.Println, mas você já notou que ele pode aceitar qualquer tipo de argumento? Olhando para o código aberto de fmt, vemos a seguinte definição.
|
|
|
|
type Stringer interface {
|
|
String() string
|
|
}
|
|
|
|
Isso significa que qualquer tipo que implemente a interface Stringer pode ser passada para fmt.Println como um argumento. Vamos provar isso.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
)
|
|
|
|
type Human struct {
|
|
name string
|
|
age int
|
|
phone string
|
|
}
|
|
|
|
// Human implementa fmt.Stringer
|
|
func (h Human) String() string {
|
|
return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone
|
|
}
|
|
|
|
func main() {
|
|
Bob := Human{"Bob", 39, "000-7777-XXX"}
|
|
fmt.Println("This Human is : ", Bob)
|
|
}
|
|
|
|
|
|
Olhando para o exemplo anterior de Box (Caixa), você verá que Color (Cor) implementa a interface Stringer também, assim que somos capazes de personalizar o formato de impressão. Se não implementarmos essa interface, fmt.Println imprime o tipo com seu formato padrão.
|
|
|
|
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
|
|
fmt.Println("The biggest one is", boxes.BiggestsColor())
|
|
|
|
Atenção: Se o tipo implementa a interface `error`, fmt chamará `error()`, então você não tem que implementar Stringer neste momento.
|
|
|
|
### Tipo de variável em uma interface
|
|
|
|
Se uma variável é o tipo que implementa uma interface, sabemos que qualquer outro tipo que implementa a mesma interface pode ser atribuído a essa variável. A questão é como podemos saber o tipo específico armazenado na interface. Há duas maneiras que vou lhe mostrar.
|
|
|
|
- Declaração do padrão Comma-ok (Vírgula-ok)
|
|
|
|
Go possui a sintaxe `value, ok := element.(T)`. Isso verifica se a variável é o tipo que esperamos, onde "value" (valor) é o valor da variável, "ok" é uma variável de tipo booleano, "element" (elemento) é a variável da interface e T é o tipo da declaração.
|
|
|
|
Se o elemento é o tipo que esperamos, ok será verdadeiro, e falso caso contrário.
|
|
|
|
Vamos usar um exemplo para ver mais claramente.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
)
|
|
|
|
type Element interface{}
|
|
type List []Element
|
|
|
|
type Person struct {
|
|
name string
|
|
age int
|
|
}
|
|
|
|
func (p Person) String() string {
|
|
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
|
|
}
|
|
|
|
func main() {
|
|
list := make(List, 3)
|
|
list[0] = 1 // um int (inteiro)
|
|
list[1] = "Hello" // uma string (cadeia de caracteres)
|
|
list[2] = Person{"Dennis", 70}
|
|
|
|
for index, element := range list {
|
|
if value, ok := element.(int); ok {
|
|
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
|
|
} else if value, ok := element.(string); ok {
|
|
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
|
|
} else if value, ok := element.(Person); ok {
|
|
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
|
|
} else {
|
|
fmt.Printf("list[%d] is of a different type\n", index)
|
|
}
|
|
}
|
|
}
|
|
|
|
É muito fácil usar esse padrão, mas se tivermos muitos tipos para testar, é melhor usar `switch`.
|
|
|
|
- teste de comutação (switch test)
|
|
|
|
Vamos usar `switch` para reescrever o exemplo acima.
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
)
|
|
|
|
type Element interface{}
|
|
type List []Element
|
|
|
|
type Person struct {
|
|
name string
|
|
age int
|
|
}
|
|
|
|
func (p Person) String() string {
|
|
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
|
|
}
|
|
|
|
func main() {
|
|
list := make(List, 3)
|
|
list[0] = 1 // um int (inteiro)
|
|
list[1] = "Hello" // uma string (cadeia de caracteres)
|
|
list[2] = Person{"Dennis", 70}
|
|
|
|
for index, element := range list {
|
|
switch value := element.(type) {
|
|
case int:
|
|
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
|
|
case string:
|
|
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
|
|
case Person:
|
|
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
|
|
default:
|
|
fmt.Println("list[%d] is of a different type", index)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Uma coisa que você deve lembrar é que `element.(type)` não pode ser usado fora do corpo do `switch`, o que significa que nesse caso você tem que usar o padrão `comma-ok`.
|
|
|
|
### Interfaces incorporadas (embedded interfaces)
|
|
|
|
A coisa mais bonita é que Go tem várias sintaxes lógicas incorporadas, como campos anônimos em estruturas. Não supreendentemente, podemos usar interfaces como campos anônimos também, mas chamamos-lhes de `Embedded interfaces` (interfaces incorporadas). Aqui, seguimos as mesmas regras que campos anônimos. Mais especificamente, se uma interface tiver outra interface incorporada no seu interior, isso será como se ela possuísse todos os métodos que a interface incorporada tem.
|
|
|
|
Podemos ver que o arquivo fonte em `container/heap` tem a seguinte definição:
|
|
|
|
type Interface interface {
|
|
sort.Interface // sort.Interface incorporado
|
|
Push(x interface{}) // um método Push para empurrar elementos para a pilha
|
|
Pop() interface{} // um método Pop para remover elementos da pilha
|
|
}
|
|
|
|
Vemos que `sort.Interface` é uma interface incorporada, então a interface acima tem os três métodos contidos em `sort.Interface` implicitamente.
|
|
|
|
type Interface interface {
|
|
// Len é o número de elementos na coleção.
|
|
Len() int
|
|
// Less retorna se o elemento com índice i deve ser ordenado
|
|
// antes do elemento com o índice j.
|
|
Less(i, j int) bool
|
|
// Swap troca os elementos com índices i e j.
|
|
Swap(i, j int)
|
|
}
|
|
|
|
Outro exemplo é o `io.ReadWriter` no pacote `io`.
|
|
|
|
// io.ReadWriter
|
|
type ReadWriter interface {
|
|
Reader
|
|
Writer
|
|
}
|
|
|
|
### Reflection
|
|
|
|
Reflection em Go é usado para determinar informações em tempo de execução. Usamos o pacote `reflect`, e este [artigo](http://golang.org/doc/articles/laws_of_reflection.html) oficial explica como reflect funciona em Go.
|
|
|
|
Há três etapas envolvidas quando usamos reflect. Primeiro, precisamos converter um interface para tipos reflect (reflect.Type ou reflect.Value, isso depende da situação).
|
|
|
|
t := reflect.TypeOf(i) // recebe meta-dados no tipo i, e usa t para receber todos os elementos
|
|
v := reflect.ValueOf(i) // recebe o valor atual no tipo i, e usa v para alterar seu valor
|
|
|
|
Depois disso, podemos converter os tipos reflect para obter os valores que precisamos.
|
|
|
|
var x float64 = 3.4
|
|
v := reflect.ValueOf(x)
|
|
fmt.Println("type:", v.Type())
|
|
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
|
|
fmt.Println("value:", v.Float())
|
|
|
|
Finalmente, se quisermos mudar os valores dos tipos reflect, precisamos torná-los modificáveis. Como discutido anteriormente, há uma diferença entre passar por valor e passar por referência. O código a seguir não compilará.
|
|
|
|
var x float64 = 3.4
|
|
v := reflect.ValueOf(x)
|
|
v.SetFloat(7.1)
|
|
|
|
Em vez disso, precisamos usar o seguinte código para alterar os valores dos tipos reflect.
|
|
|
|
var x float64 = 3.4
|
|
p := reflect.ValueOf(&x)
|
|
v := p.Elem()
|
|
v.SetFloat(7.1)
|
|
|
|
Acabamos de discutir os fundamentos de reflect, no entanto você deve praticar mais para entender mais.
|
|
|
|
## Links
|
|
|
|
- [Sumário](preface.md)
|
|
- Seção Anterior: [Orientação a Objetos](02.5.md)
|
|
- Próxima Seção: [Concorrência](02.7.md)
|