Interfaces
Em Go, uma interface é um tipo abstrato usado para definir um conjunto de assinaturas de métodos sem fornecer a implementação dos métodos. O conceito central das interfaces é descrever comportamentos, enquanto a implementação concreta do comportamento é fornecida pelos tipos que implementam a interface. Interfaces em Go são amplamente usadas para implementar polimorfismo, acoplamento fraco e reutilização de código.
Conceitos
A história do desenvolvimento de interfaces em Go tem um ponto de divisão. Antes do Go 1.17, a definição oficial de interfaces no manual de referência era: um conjunto de métodos.
An interface type specifies a method set called its interface.
A definição de implementação de interface era
A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface
Traduzindo, quando o conjunto de métodos de um tipo é um superconjunto do conjunto de métodos de uma interface, e o valor desse tipo pode ser armazenado por uma variável do tipo da interface, então se diz que esse tipo implementa a interface.
No entanto, no Go 1.18, a definição de interface mudou para: um conjunto de tipos.
An interface type defines a type set.
A definição de implementação de interface passou a ser
A variable of interface type can store a value of any type that is in the type set of the interface. Such a type is said to implement the interface
Traduzindo, quando um tipo está localizado no conjunto de tipos de uma interface, e o valor desse tipo pode ser armazenado por uma variável do tipo da interface, então se diz que esse tipo implementa a interface. Também foram fornecidas as seguintes definições adicionais.
Quando as seguintes condições são satisfeitas, pode-se dizer que o tipo T implementa a interface I
- T não é uma interface, e é um elemento no conjunto de tipos da interface I
- T é uma interface, e o conjunto de tipos de T é um subconjunto do conjunto de tipos da interface I
Se T implementa uma interface, então os valores de T também implementam essa interface.
A maior mudança no Go 1.18 foi a adição de generics, e a nova definição de interface serve para dar suporte aos generics. No entanto, isso não afeta o uso anterior de interfaces, e as interfaces foram divididas em duas categorias:
- Interface Básica (
Basic Interface): Uma interface que contém apenas um conjunto de métodos é uma interface básica - Interface Geral (
General Interface): Uma interface que contém um conjunto de tipos é uma interface geral
O que é um conjunto de métodos? Um conjunto de métodos é uma coleção de métodos. Da mesma forma, um conjunto de tipos é uma coleção de tipos.
TIP
Você pode achar esses conceitos difíceis de entender, mas na verdade você não precisa entender todo esse texto acima.
Interface Básica
Como mencionado anteriormente, uma interface básica é um conjunto de métodos, ou seja, uma coleção de métodos.
Declaração
Primeiro, vamos ver como é uma interface.
type Person interface {
Say(string) string
Walk(int)
}Esta é uma interface Person, com dois métodos expostos Walk e Say. Na interface, os nomes dos parâmetros das funções não são importantes, embora seja permitido adicionar nomes de parâmetros e valores de retorno se desejar.
Inicialização
Apenas uma interface não pode ser inicializada, pois é apenas um conjunto de especificações sem implementação concreta. No entanto, pode ser declarada.
func main() {
var person Person
fmt.Println(person)
}Saída
<nil>Implementação
Vamos ver um exemplo. Uma empresa de construção quer um guindaste de especificações especiais, então fornece as especificações e desenhos especiais do guindaste, indicando que o guindaste deve ter funções de levantamento e carga. A empresa de construção não é responsável por fabricar o guindaste, apenas fornece uma especificação - isso é uma interface. A empresa A aceita o pedido e, usando sua tecnologia exclusiva, fabrica um guindaste excepcional e o entrega à empresa de construção. A empresa de construção não se importa com qual tecnologia foi usada para implementá-lo, nem se importa com o guindaste excepcional - desde que possa levantar e carregar cargas, é tratado apenas como um guindaste comum. Fornecer funcionalidade concreta de acordo com as especificações - isso é implementação. Usar funcionalidade apenas de acordo com as especificações da interface, ocultando sua implementação interna - isso é programação orientada a interfaces. Depois de algum tempo, o guindaste excepcional quebra e a empresa A foge. A empresa B, seguindo as especificações, fabrica um guindaste gigante ainda mais poderoso. Como tem as mesmas funções de levantamento e carga, pode substituir perfeitamente o guindaste excepcional sem afetar o progresso da construção, que é concluída com sucesso. A implementação interna muda, mas a funcionalidade permanece a mesma, sem afetar o uso anterior, podendo ser substituída à vontade - esse é o benefício da programação orientada a interfaces.
Agora vamos descrever essa situação em Go
// Interface do guindaste
type Crane interface {
JackUp() string
Hoist() string
}
// Guindaste A
type CraneA struct {
work int // campos internos diferentes representam detalhes internos diferentes
}
func (c CraneA) Work() {
fmt.Println("Usando tecnologia A")
}
func (c CraneA) JackUp() string {
c.Work()
return "jackup"
}
func (c CraneA) Hoist() string {
c.Work()
return "hoist"
}
// Guindaste B
type CraneB struct {
boot string
}
func (c CraneB) Boot() {
fmt.Println("Usando tecnologia B")
}
func (c CraneB) JackUp() string {
c.Boot()
return "jackup"
}
func (c CraneB) Hoist() string {
c.Boot()
return "hoist"
}
type ConstructionCompany struct {
Crane Crane // armazena o guindaste apenas pelo tipo Crane
}
func (c *ConstructionCompany) Build() {
fmt.Println(c.Crane.JackUp())
fmt.Println(c.Crane.Hoist())
fmt.Println("Construção concluída")
}
func main() {
// Usando guindaste A
company := ConstructionCompany{CraneA{}}
company.Build()
fmt.Println()
// Trocando para guindaste B
company.Crane = CraneB{}
company.Build()
}Saída
Usando tecnologia A
jackup
Usando tecnologia A
hoist
Construção concluída
Usando tecnologia B
jackup
Usando tecnologia B
hoist
Construção concluídaNo exemplo acima, pode-se observar que a implementação da interface é implícita, o que corresponde à definição oficial de implementação de interface básica: o conjunto de métodos é um superconjunto do conjunto de métodos da interface. Portanto, em Go, implementar uma interface não requer a palavra-chave implements para especificar explicitamente qual interface está sendo implementada. Desde que todos os métodos de uma interface sejam implementados, a interface é considerada implementada. Após ter uma implementação, a interface pode ser inicializada. A estrutura da empresa de construção declarou internamente uma variável membro do tipo Crane, que pode armazenar todos os valores que implementam a interface Crane. Como é uma variável do tipo Crane, os únicos métodos acessíveis são JackUp e Hoist. Outros métodos internos como Work e Boot não podem ser acessados.
Mencionamos anteriormente que qualquer tipo personalizado pode ter métodos, então, pela definição de implementação, qualquer tipo personalizado pode implementar uma interface. Aqui estão alguns exemplos especiais.
type Person interface {
Say(string) string
Walk(int)
}
type Man interface {
Exercise()
Person
}O conjunto de métodos da interface Man é um superconjunto de Person, então Man também implementa a interface Person, embora isso seja mais como uma "herança".
type Number int
func (n Number) Say(s string) string {
return "bibibibibi"
}
func (n Number) Walk(i int) {
fmt.Println("can not walk")
}O tipo Number tem int como tipo subjacente. Embora isso possa parecer estranho em outras linguagens, o conjunto de métodos de Number é realmente um superconjunto de Person, então também conta como uma implementação.
type Func func()
func (f Func) Say(s string) string {
f()
return "bibibibibi"
}
func (f Func) Walk(i int) {
f()
fmt.Println("can not walk")
}
func main() {
var function Func
function = func() {
fmt.Println("do somthing")
}
function()
}Da mesma forma, tipos de função também podem implementar interfaces.
Interface Vazia
type Any interface{
}A interface Any não tem conjunto de métodos interno. Pela definição de implementação, todos os tipos são implementações da interface Any, porque o conjunto de métodos de qualquer tipo é um superconjunto do conjunto vazio. Portanto, a interface Any pode armazenar valores de qualquer tipo.
func main() {
var anything Any
anything = 1
println(anything)
fmt.Println(anything)
anything = "something"
println(anything)
fmt.Println(anything)
anything = complex(1, 2)
println(anything)
fmt.Println(anything)
anything = 1.2
println(anything)
fmt.Println(anything)
anything = []int{}
println(anything)
fmt.Println(anything)
anything = map[string]int{}
println(anything)
fmt.Println(anything)
}Saída
(0xe63580,0xeb8b08)
1
(0xe63d80,0xeb8c48)
something
(0xe62ac0,0xeb8c58)
(1+2i)
(0xe62e00,0xeb8b00)
1.2
(0xe61a00,0xc0000080d8)
[]
(0xe69720,0xc00007a7b0)
map[]Através da saída, você notará que os resultados das duas saídas são inconsistentes. Na verdade, a interface pode ser vista internamente como uma tupla composta por (val,type), onde type é o tipo concreto. Ao chamar métodos, o valor concreto do tipo concreto será chamado.
interface{}Isso também é uma interface vazia, mas uma interface vazia anônima. No desenvolvimento, geralmente se usa a interface vazia anônima para representar a aceitação de valores de qualquer tipo, como no exemplo a seguir
func main() {
DoSomething(map[int]string{})
}
func DoSomething(anything interface{}) interface{} {
return anything
}Em atualizações posteriores, a equipe oficial propôs outra solução. Por conveniência, você pode usar any para substituir interface{}. Ambos são completamente equivalentes, porque o primeiro é apenas um alias de tipo, como mostrado abaixo
type any = interface{}Ao comparar interfaces vazias, seus tipos subjacentes são comparados primeiro. Se os tipos não corresponderem, o resultado é false. Só então os valores são comparados. Por exemplo
func main() {
var a interface{}
var b interface{}
a = 1
b = "1"
fmt.Println(a == b)
a = 1
b = 1
fmt.Println(a == b)
}Saída
false
trueSe o tipo subjacente for incomparável, ocorrerá um panic. Para Go, a comparabilidade dos tipos de dados embutidos é a seguinte
| Tipo | Comparável | Critério |
|---|---|---|
| Numéricos | Sim | Se os valores são iguais |
| Strings | Sim | Se os valores são iguais |
| Arrays | Sim | Se todos os elementos do array são iguais |
| Slices | Não | Incomparável |
| Structs | Sim | Se todos os valores dos campos são iguais |
| Maps | Não | Incomparável |
| Canais | Sim | Se os endereços são iguais |
| Ponteiros | Sim | Se os endereços armazenados são iguais |
| Interfaces | Sim | Se os dados armazenados subjacentes são iguais |
Em Go, existe um tipo de interface especial para representar todos os tipos comparáveis, que é comparable
type comparable interface{ comparable }TIP
Se você tentar comparar tipos incomparáveis, ocorrerá um panic
Interface Geral
Interfaces gerais existem para servir aos generics. Dominando generics, você dominará interfaces gerais. Consulte Generics
