Regras de Satisfação de Interfaces
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy. Lição 43 de 107.
Embora Go use tipagem pato para a satisfação implícita de interfaces, há regras específicas que determinam se um tipo realmente satisfaz uma interface. Entender essas regras ajuda você a evitar bugs sutis.
A regra mais importante envolve receptores de ponteiro. Se um método for definido com um receptor de ponteiro, apenas um ponteiro para esse tipo satisfaz a interface, não o próprio valor:
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() tem um receptor ponteiro, apenas *Document satisfaz Saver:
func main() {
doc := Document{Name: "report.txt"}
Process(&doc) // funciona - ponteiro satisfaz a interface
// Process(doc) // erro de compilação - valor não satisfaz a interface
}
Entretanto, o inverso é mais flexível. Se um método tiver um receptor de valor, tanto os tipos valor quanto ponteiro satisfazem a interface. Go remove automaticamente a referência dos ponteiros ao chamar métodos com receptores 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 Go sempre pode obter um valor a partir de um ponteiro (por 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 criar um sistema de configuração que demonstra como receptores de ponteiro e de valor afetam a satisfação de uma interface. Você criará tipos em que 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 exija 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 aceite umaConfigurablee 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(obrigató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 a ser definido para ReadOnlyConfig
- Linha 3: valor inicial para Setting
- Linha 4: novo valor a ser tentado para 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). A principal conclusão é que valores de ReadOnlyConfig sozinhos não satisfariam Configurable, apenas ponteiros o fazem, enquanto valores de Setting funcionam diretamente porque todos os seus métodos usam receptores de valor.
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: Crie um ReadOnlyConfig com o valor rocInitial
// TODO: Passe um PONTEIRO para ProcessConfig (necessário porque SetValue tem receptor de ponteiro)
// TODO: Crie um Setting com o valor settingInitial
// TODO: Passe 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ável não utilizada
_ = 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 ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa para métodos genéricosRecapitulação - Coleção genérica13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online