Duck Typing em Go
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 42 de 107.
O termo duck typing vem do ditado: "Se ele anda como um pato e grasna como um pato, então é um pato." Em Go, isso significa que um tipo não precisa declarar explicitamente que implementa uma interface — ele só precisa ter os métodos corretos.
Considere esta interface e dois tipos completamente não relacionados:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Nem Duck nem Robot mencionam Quacker em qualquer lugar de suas definições. Ainda assim, ambos satisfazem a interface simplesmente porque possuem um método Quack() com a assinatura correta:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Esta satisfação implícita é poderosa porque permite que você defina interfaces após os tipos já existirem. Você pode criar uma interface que corresponda a tipos de pacotes externos sem modificar seu código-fonte. Os tipos não precisam saber sobre sua interface — eles apenas precisam se comportar corretamente.
Duck typing em Go oferece a flexibilidade de linguagens dinâmicas, mantendo a segurança de tipos em tempo de compilação. O compilador verifica se os tipos realmente possuem os métodos necessários antes de permitir que sejam usados como valores de interface.
Desafio
FácilVamos construir um sistema de mensagens que demonstra o duck typing em ação. Você criará tipos completamente não relacionados que podem enviar mensagens através de uma interface comum — sem que nenhum deles declare explicitamente que a implementa.
Você organizará seu código em três arquivos:
messengers.go: Crie três structs não relacionadas que compartilham o mesmo comportamento:Phonecom um campoNumber— seu métodoSendMessage(text string) stringretorna"SMS to [Number]: [text]"Computercom um campoEmail— seu métodoSendMessage(text string) stringretorna"Email to [Email]: [text]"Pigeoncom um campoName— seu métodoSendMessage(text string) stringretorna"[Name] carries: [text]"
sender.go: Defina uma interfaceMessengerque requer um métodoSendMessage(text string) string. Crie uma função chamadaBroadcastque recebe um slice deMessengere uma string de mensagem, e então retorna um slice de strings contendo o resultado da chamada deSendMessageem cada mensageiro.main.go: Leia os detalhes da entrada, crie um de cada tipo de mensageiro, colete-os em um slice deMessengere useBroadcastpara enviar uma mensagem através de todos eles. Imprima cada resultado em sua própria linha.
As seguintes entradas serão fornecidas:
- Linha 1: Número de telefone
- Linha 2: Endereço de e-mail
- Linha 3: Nome do pombo
- Linha 4: Mensagem para transmitir
Por exemplo, dados "555-1234", "alice@mail.com", "Winston" e "Hello World", sua saída deve ser:
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello WorldO ponto principal aqui é que Phone, Computer e Pigeon não têm nada em comum — eles não compartilham um tipo base nem implementam explicitamente Messenger. No entanto, como todos eles "fazem quack" da mesma maneira (possuem o método correspondente), o Go permite que todos sejam usados como valores de Messenger. Isso é o duck typing em funcionamento.
Folha de consulta
Em Go, o duck typing significa que um tipo não precisa declarar explicitamente que implementa uma interface — ele só precisa ter os métodos necessários com assinaturas correspondentes.
Um tipo satisfaz implicitamente uma interface se possuir todos os métodos que a interface exige:
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Tanto Duck e Robot satisfazem a interface Quacker sem mencioná-la em suas definições:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Essa satisfação implícita permite que você defina interfaces após os tipos já existirem, e até mesmo crie interfaces que correspondam a tipos de pacotes externos sem modificar seu código-fonte. O compilador verifica em tempo de compilação se os tipos possuem os métodos necessários, proporcionando segurança de tipos enquanto mantém a flexibilidade.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Ler número de telefone
scanner.Scan()
phoneNumber := scanner.Text()
// Ler endereço de e-mail
scanner.Scan()
email := scanner.Text()
// Ler nome do pombo
scanner.Scan()
pigeonName := scanner.Text()
// Ler mensagem para transmitir
scanner.Scan()
message := scanner.Text()
// TODO: Criar um de cada tipo de mensageiro (Telefone, Computador, Pombo)
// TODO: Coletar todos os mensageiros em um slice de Messenger
// TODO: Usar Broadcast para enviar a mensagem através de todos os mensageiros
// TODO: Imprimir cada resultado em sua própria linha
// Espaço reservado para usar variáveis (remover ao implementar)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
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