Regras de Satisfação de Interface
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 43 de 107.
Embora o Go utilize duck typing para a satisfação implícita de interfaces, existem regras específicas que determinam se um tipo realmente satisfaz uma interface. Compreender essas regras ajuda você a evitar bugs sutis.
A regra mais importante envolve recebedores de ponteiro. Se um método for definido com um recebedor de ponteiro, apenas um ponteiro para esse tipo satisfaz a interface — não o valor em si:
type Saver interface {
Save() string
}
type Document struct{ Name string }
func (d *Document) Save() string { // receptor de ponteiro
return "Saved: " + d.Name
}
func Process(s Saver) {
fmt.Println(s.Save())
}
Como Save() possui um receptor de ponteiro, apenas *Document satisfaz Saver:
func main() {
doc := Document{Name: "report.txt"}
Process(&doc) // funciona - o ponteiro satisfaz a interface
// Process(doc) // erro de compilação - o valor não satisfaz a interface
}
No entanto, o inverso é mais flexível. Se um método possui um receptor de valor, ambos os tipos de valor e ponteiro satisfazem a interface. O Go desreferencia ponteiros automaticamente ao chamar métodos com receptor de valor:
func (d Document) Info() string { // receptor de valor
return d.Name
}
// Tanto Document quanto *Document satisfazem uma interface que requer Info()
Essa assimetria existe porque o Go sempre pode obter um valor a partir de um ponteiro (por meio da desreferenciação), mas nem sempre pode obter um ponteiro a partir de um valor (o valor pode não ser endereçável). Manter essa regra em mente evita erros confusos do compilador ao trabalhar com interfaces.
Desafio
FácilVamos construir um sistema de configuração que demonstra como os receptores de ponteiro e de valor afetam a satisfação de uma interface. Você criará tipos onde a escolha do receptor determina se valores, ponteiros ou ambos podem ser usados com uma interface.
Você organizará seu código em três arquivos:
config.go: Defina uma interfaceConfigurableque requer dois métodos:GetValue() stringeSetValue(string). Em seguida, crie dois tipos de configuração:ReadOnlyConfigcom um campoValue—use um receptor de valor paraGetValue()(retorna o Value) e um receptor de ponteiro paraSetValue()(atualiza o Value)Settingcom um campoData—use receptores de valor para ambos os métodos (GetValue retorna Data, SetValue apenas imprime "Cannot modify" sem alterar nada)
processor.go: Crie uma função chamadaProcessConfigque aceita umConfigurablee uma nova string de valor. Ela deve imprimir o valor atual usandoGetValue(), chamarSetValue()com o novo valor e, em seguida, imprimir o valor novamente para mostrar quaisquer alterações.main.go: Leia os detalhes da configuração da entrada e demonstre as regras de satisfação da interface:- Crie um
ReadOnlyConfige passe um ponteiro paraProcessConfig(necessário porqueSetValuetem um receptor de ponteiro) - Crie um
Settinge passe o valor diretamente paraProcessConfig(funciona porque ambos os métodos têm receptores de valor)
- Crie um
As seguintes entradas serão fornecidas:
- Linha 1: Valor inicial para ReadOnlyConfig
- Linha 2: Novo valor para definir em ReadOnlyConfig
- Linha 3: Valor inicial para Setting
- Linha 4: Novo valor para tentar em Setting
Sua função ProcessConfig deve imprimir neste formato:
Current: [value]
Current: [value after SetValue]Por exemplo, dados debug, production, localhost e remote, sua saída deve ser:
Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhostObserve como ReadOnlyConfig realmente altera seu valor (porque passamos um ponteiro), enquanto Setting permanece inalterado (seu SetValue com um receptor de valor não pode modificar o original). O ponto principal é que os valores de ReadOnlyConfig sozinhos não satisfariam Configurable—apenas os ponteiros satisfazem—enquanto os valores de Setting funcionam diretamente porque todos os seus métodos usam receptores de valor.
Folha de consulta
Go utiliza duck typing para a satisfação implícita de interfaces, mas existem regras específicas sobre receptores de ponteiro e de valor:
Regra do Receptor de Ponteiro
Se um método possui um receptor de ponteiro (pointer receiver), apenas um ponteiro para esse tipo satisfaz a interface:
type Saver interface {
Save() string
}
type Document struct{ Name string }
func (d *Document) Save() string { // pointer receiver
return "Saved: " + d.Name
}
func main() {
doc := Document{Name: "report.txt"}
Process(&doc) // works - pointer satisfies interface
// Process(doc) // compile error - value doesn't satisfy interface
}Regra do Receptor de Valor
Se um método possui um receptor de valor (value receiver), tanto o tipo de valor quanto o tipo de ponteiro satisfazem a interface:
func (d Document) Info() string { // value receiver
return d.Name
}
// Both Document and *Document satisfy an interface requiring Info()Por que essa assimetria existe
O Go sempre pode obter um valor a partir de um ponteiro (através da desreferenciação), mas nem sempre pode obter um ponteiro a partir de um valor (o valor pode não ser endereçável).
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Ler entradas
scanner.Scan()
rocInitial := scanner.Text()
scanner.Scan()
rocNew := scanner.Text()
scanner.Scan()
settingInitial := scanner.Text()
scanner.Scan()
settingNew := scanner.Text()
// TODO: Criar um ReadOnlyConfig com o valor rocInitial
// TODO: Passar um PONTEIRO para ProcessConfig (necessário porque SetValue tem receptor de ponteiro)
// TODO: Criar um Setting com o valor settingInitial
// TODO: Passar o VALOR diretamente para ProcessConfig (funciona porque ambos os métodos têm receptores de valor)
// Use estas variáveis para evitar erros de variáveis não utilizadas
_ = rocInitial
_ = rocNew
_ = settingInitial
_ = settingNew
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool