Métodos
Lição 8: Métodos
Analogia do Mundo Real
Imagine que você comprou um carro. O carro tem várias funções — ligar, acelerar, frear, acender as luzes. Essas funções não existem independentemente; elas pertencem ao carro. Você não diria "ligar" é uma ação genérica; você diria "o carro ligou."
Em Go, um método é uma função vinculada a um tipo específico. Assim como "ligar" pertence ao "carro", um método pertence ao tipo ao qual está vinculado. Você não colocaria a função ligar em uma geladeira — da mesma forma, métodos são definidos no tipo que realmente atendem.
Conceitos Principais
O que é um Método?
Um método é uma função com um receptor. O receptor vincula o método a um tipo, permitindo que variáveis desse tipo chamem o método diretamente.
Receptor
O receptor é a ponte entre um método e um tipo. Sua sintaxe aparece entre a palavra-chave func e o nome do método:
func (variavelReceptor TipoReceptor) nomeMetodo(parametros) tipoRetorno {
// Corpo do método
}
Receptor por Valor vs Receptor por Ponteiro
| Característica | Receptor por Valor func (s Struct) |
Receptor por Ponteiro func (s *Struct) |
|---|---|---|
| Opera em cópia | Sim (modificações não afetam o original) | Não (modifica diretamente o original) |
| Caso de uso | Operações somente leitura, structs pequenas | Necessidade de modificar campos, structs grandes |
| Implementação de interface | Chamável em valores e ponteiros | Chamável apenas em tipos ponteiro |
Regras de Conjunto de Métodos
- Conjunto de métodos de tipo por valor: inclui apenas métodos com receptor por valor
- Conjunto de métodos de tipo por ponteiro: inclui métodos com receptor por valor e por ponteiro
Isso significa: se você tem uma variável do tipo *T, ela pode chamar todos os métodos de T e *T; enquanto uma variável do tipo T só pode chamar métodos de T.
Composição em Vez de Herança
Go não tem herança de classes; em vez disso, a reutilização de código é alcançada através de composição (embedding). Embuta uma struct em outra, e os métodos da struct embutida são automaticamente "promovidos" para a struct externa.
Sintaxe e Uso Básico
Definindo Métodos
package main
import "fmt"
// Definir uma struct
type Retangulo struct {
Largura float64
Altura float64
}
// Método com receptor por valor: calcular área
func (r Retangulo) Area() float64 {
return r.Largura * r.Altura
}
// Método com receptor por ponteiro: escalar (precisa modificar campos)
func (r *Retangulo) Escalar(fator float64) {
r.Largura *= fator
r.Altura *= fator
}
func main() {
ret := Retangulo{Largura: 10, Altura: 5}
fmt.Println("Area:", ret.Area()) // Saída: Area: 50
ret.Escalar(2)
fmt.Println("Area escalada:", ret.Area()) // Saída: Area escalada: 200
}
Diferença Entre Métodos e Funções
// Isso é uma função, não vinculada a nenhum tipo
func Area(r Retangulo) float64 {
return r.Largura * r.Altura
}
// Isso é um método, vinculado ao tipo Retangulo
func (r Retangulo) Area() float64 {
return r.Largura * r.Altura
}
Herança de Métodos com Structs Embutidas
package main
import "fmt"
// Tipo base
type Animal struct {
Nome string
}
// Método do Animal
func (a Animal) Falar() string {
return a.Nome + " faz um som"
}
// Tipo que embute Animal
type Cachorro struct {
Animal // Embute, sem nome de campo
Raca string
}
// Método próprio do Cachorro
func (c Cachorro) Latir() string {
return c.Nome + " late!"
}
func main() {
cachorro := Cachorro{
Animal: Animal{Nome: "Rex"},
Raca: "Golden Retriever",
}
// Pode chamar diretamente os métodos do tipo embutido
fmt.Println(cachorro.Falar()) // Saída: Rex faz um som
fmt.Println(cachorro.Latir()) // Saída: Rex late!
}
Area(), Escalar(), Salvar(), EhValido().
func (r Retangulo) em vez de func (ret Retangulo). Esta é a convenção do Go.
sync.Mutex) ou quando a struct é grande.
Exemplos
Exemplo: Definição Básica de Método (Dificuldade ⭐)
Definir uma struct Circulo e implementar métodos para calcular área e perímetro.
package main
import (
"fmt"
"math"
)
// Struct Circulo
type Circulo struct {
Raio float64
}
// Area calcula a área (receptor por valor, operação somente leitura)
func (c Circulo) Area() float64 {
return math.Pi * c.Raio * c.Raio
}
// Perimetro calcula o perímetro
func (c Circulo) Perimetro() float64 {
return 2 * math.Pi * c.Raio
}
// DefinirRaio define um novo raio (receptor por ponteiro, modifica campo)
func (c *Circulo) DefinirRaio(r float64) {
if r > 0 {
c.Raio = r
}
}
func main() {
c := Circulo{Raio: 5}
fmt.Printf("Raio: %.2f\n", c.Raio)
fmt.Printf("Area: %.2f\n", c.Area())
fmt.Printf("Perimetro: %.2f\n", c.Perimetro())
c.DefinirRaio(10)
fmt.Printf("\nNovo raio: %.2f\n", c.Raio)
fmt.Printf("Nova area: %.2f\n", c.Area())
fmt.Printf("Novo perimetro: %.2f\n", c.Perimetro())
}
Saída:
Raio: 5.00
Area: 78.54
Perimetro: 31.42
Novo raio: 10.00
Nova area: 314.16
Novo perimetro: 62.83
Exemplo: Receptor por Valor vs Receptor por Ponteiro (Dificuldade ⭐⭐)
Demonstra as diferenças comportamentais chave entre os dois.
package main
import "fmt"
// Conta conta bancaria
type Conta struct {
Titular string
Saldo float64
}
// Receptor por valor: obter saldo (somente leitura, sem modificação)
func (c Conta) ObterSaldo() float64 {
return c.Saldo
}
// Receptor por ponteiro: depositar (precisa modificar saldo)
func (c *Conta) Depositar(valor float64) {
if valor > 0 {
c.Saldo += valor
fmt.Printf(" Depositado %.2f, Saldo: %.2f\n", valor, c.Saldo)
}
}
// Receptor por ponteiro: sacar
func (c *Conta) Sacar(valor float64) bool {
if valor > 0 && c.Saldo >= valor {
c.Saldo -= valor
fmt.Printf(" Sacado %.2f, Saldo: %.2f\n", valor, c.Saldo)
return true
}
fmt.Printf(" Saque de %.2f falhou, saldo insuficiente\n", valor)
return false
}
// Receptor por valor: formatar informações da conta
func (c Conta) String() string {
return fmt.Sprintf("Conta[%s] Saldo: %.2f", c.Titular, c.Saldo)
}
func main() {
conta := Conta{Titular: "João Silva", Saldo: 1000}
fmt.Println(conta)
fmt.Println("\n--- Histórico de Transações ---")
conta.Depositar(500)
conta.Sacar(200)
conta.Sacar(2000) // Saldo insuficiente
fmt.Println("\n--- Status Final ---")
fmt.Println(conta)
fmt.Printf("Saldo atual: %.2f\n", conta.ObterSaldo())
}
Saída:
Conta[João Silva] Saldo: 1000.00
--- Histórico de Transações ---
Depositado 500.00, Saldo: 1500.00
Sacado 200.00, Saldo: 1300.00
Saque de 2000.00 falhou, saldo insuficiente
--- Status Final ---
Conta[João Silva] Saldo: 1300.00
Saldo atual: 1300.00
Exemplo: Composição em Vez de Herança (Dificuldade ⭐⭐⭐)
Demonstrar herança de métodos através de structs embutidas e sobrescrita de métodos.
package main
import "fmt"
// ==================== Camada Base ====================
// Struct base (similar a uma "classe pai")
type Base struct {
ID int
Nome string
}
// Describe retorna uma descrição básica
func (b Base) Describe() string {
return fmt.Sprintf("Base[ID=%d, Nome=%s]", b.ID, b.Nome)
}
// Identify retorna informações de identificação
func (b Base) Identify() string {
return fmt.Sprintf("Eu sou %s (ID: %d)", b.Nome, b.ID)
}
// ==================== Camada de Negócio ====================
// Funcionario embute Base
type Funcionario struct {
Base // Embute, herda métodos de Base
Departamento string
Salario float64
}
// Sobrescreve o método Describe de Base
func (f Funcionario) Describe() string {
return fmt.Sprintf("Funcionario[ID=%d, Nome=%s, Dept=%s, Salario=%.0f]",
f.ID, f.Nome, f.Departamento, f.Salario)
}
// Método próprio do Funcionario
func (f Funcionario) SalarioAnual() float64 {
return f.Salario * 12
}
// Gerente embute Funcionario (embedding multi-nível)
type Gerente struct {
Funcionario // Embute Funcionario
TamanhoEquipe int
}
// Sobrescreve o método Describe
func (g Gerente) Describe() string {
return fmt.Sprintf("Gerente[ID=%d, Nome=%s, Dept=%s, Equipe=%d pessoas]",
g.ID, g.Nome, g.Departamento, g.TamanhoEquipe)
}
// Método próprio do Gerente
func (g Gerente) RelatorioEquipe() string {
return fmt.Sprintf("%s gerencia uma equipe de %d pessoas", g.Nome, g.TamanhoEquipe)
}
func main() {
// Criar uma instância de Base
b := Base{ID: 1, Nome: "Objeto Base"}
fmt.Println("=== Base ===")
fmt.Println(b.Describe())
fmt.Println(b.Identify())
// Criar uma instância de Funcionario
func1 := Funcionario{
Base: Base{ID: 2, Nome: "Maria Santos"},
Departamento: "Engenharia",
Salario: 15000,
}
fmt.Println("\n=== Funcionario ===")
fmt.Println(func1.Describe()) // Chama o método sobrescrito do Funcionario
fmt.Println(func1.Identify()) // Herdado de Base
fmt.Printf("Salario anual: %.0f\n", func1.SalarioAnual())
// Criar uma instância de Gerente
gerente := Gerente{
Funcionario: Funcionario{
Base: Base{ID: 3, Nome: "Pedro Oliveira"},
Departamento: "P&D",
Salario: 25000,
},
TamanhoEquipe: 8,
}
fmt.Println("\n=== Gerente ===")
fmt.Println(gerente.Describe()) // Chama o método sobrescrito do Gerente
fmt.Println(gerente.Identify()) // Herdado de Base através de embedding multi-nível
fmt.Printf("Salario anual: %.0f\n", gerente.SalarioAnual()) // Herdado de Funcionario
fmt.Println(gerente.RelatorioEquipe()) // Método próprio do Gerente
}
Saída:
=== Base ===
Base[ID=1, Nome=Objeto Base]
Eu sou Objeto Base (ID: 1)
=== Funcionario ===
Funcionario[ID=2, Nome=Maria Santos, Dept=Engenharia, Salario=15000]
Eu sou Maria Santos (ID: 2)
Salario anual: 180000
=== Gerente ===
Gerente[ID=3, Nome=Pedro Oliveira, Dept=P&D, Equipe=8 pessoas]
Eu sou Pedro Oliveira (ID: 3)
Salario anual: 300000
Pedro Oliveira gerencia uma equipe de 8 pessoas
Cenários do Mundo Real
Cenário 1: Sistema de Autenticação de Usuário
Usando métodos para encapsular a lógica de autenticação de usuário.
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
)
// Struct Usuario
type Usuario struct {
Nome string
HashSenha string
Email string
CriadoEm time.Time
Ativo bool
ContagemLogin int
}
// NovoUsuario cria um novo usuário (padrão construtor)
func NovoUsuario(nome, senha, email string) *Usuario {
return &Usuario{
Nome: nome,
HashSenha: hashSenha(senha),
Email: email,
CriadoEm: time.Now(),
Ativo: true,
ContagemLogin: 0,
}
}
// hashSenha faz hash de uma senha (função de pacote, não um método)
func hashSenha(senha string) string {
h := sha256.Sum256([]byte(senha))
return hex.EncodeToString(h[:])
}
// VerificarSenha verifica a senha
func (u *Usuario) VerificarSenha(senha string) bool {
return u.HashSenha == hashSenha(senha)
}
// Login login do usuário
func (u *Usuario) Login(senha string) error {
if !u.Ativo {
return fmt.Errorf("conta %s foi desativada", u.Nome)
}
if !u.VerificarSenha(senha) {
return fmt.Errorf("senha incorreta")
}
u.ContagemLogin++
return nil
}
// Desativar desativa a conta
func (u *Usuario) Desativar() {
u.Ativo = false
}
// Info retorna informações do usuário
func (u Usuario) Info() string {
status := "Ativo"
if !u.Ativo {
status = "Desativado"
}
return fmt.Sprintf("[%s] %s | Email: %s | Status: %s | Contagem de login: %d",
u.Nome, u.Nome, u.Email, status, u.ContagemLogin)
}
func main() {
usuario := NovoUsuario("joaosilva", "minhaSenha123", "joaosilva@exemplo.com")
fmt.Println("Usuário criado:")
fmt.Println(usuario.Info())
// Login com senha correta
fmt.Println("\nLogin com senha correta:")
err := usuario.Login("minhaSenha123")
if err != nil {
fmt.Println("Login falhou:", err)
} else {
fmt.Println("Login bem-sucedido!")
}
fmt.Println(usuario.Info())
// Login com senha errada
fmt.Println("\nLogin com senha errada:")
err = usuario.Login("senhaErrada")
if err != nil {
fmt.Println("Login falhou:", err)
}
// Tentar login após desativar conta
fmt.Println("\nTentar login após desativar conta:")
usuario.Desativar()
err = usuario.Login("minhaSenha123")
if err != nil {
fmt.Println("Login falhou:", err)
}
fmt.Println(usuario.Info())
}
Saída de Exemplo:
Usuário criado:
[joaosilva] joaosilva | Email: joaosilva@exemplo.com | Status: Ativo | Contagem de login: 0
Login com senha correta:
Login bem-sucedido!
[joaosilva] joaosilva | Email: joaosilva@exemplo.com | Status: Ativo | Contagem de login: 1
Login com senha errada:
Login falhou: senha incorreta
Tentar login após desativar conta:
Login falhou: conta joaosilva foi desativada
[joaosilva] joaosilva | Email: joaosilva@exemplo.com | Status: Desativado | Contagem de login: 1
Cenário 2: Sistema de Carrinho de Compras
Usando métodos para implementar CRUD do carrinho de compras e checkout.
package main
import "fmt"
// Produto produto
type Produto struct {
Nome string
Preco float64
Categoria string
}
// ItemCarrinho item do carrinho
type ItemCarrinho struct {
Produto Produto
Quantidade int
}
// Subtotal calcula o subtotal
func (ic ItemCarrinho) Subtotal() float64 {
return ic.Produto.Preco * float64(ic.Quantidade)
}
// CarrinhoCompras carrinho de compras
type CarrinhoCompras struct {
Itens []ItemCarrinho
Dono string
}
// Adicionar adiciona um produto ao carrinho
func (cc *CarrinhoCompras) Adicionar(p Produto, qtd int) {
// Verificar se já existe
for i := range cc.Itens {
if cc.Itens[i].Produto.Nome == p.Nome {
cc.Itens[i].Quantidade += qtd
fmt.Printf(" Quantidade atualizada: %s x%d\n", p.Nome, cc.Itens[i].Quantidade)
return
}
}
cc.Itens = append(cc.Itens, ItemCarrinho{Produto: p, Quantidade: qtd})
fmt.Printf(" Produto adicionado: %s x%d\n", p.Nome, qtd)
}
// Remover remove um produto do carrinho
func (cc *CarrinhoCompras) Remover(nome string) bool {
for i, item := range cc.Itens {
if item.Produto.Nome == nome {
cc.Itens = append(cc.Itens[:i], cc.Itens[i+1:]...)
fmt.Printf(" Produto removido: %s\n", nome)
return true
}
}
fmt.Printf(" Produto não encontrado: %s\n", nome)
return false
}
// Total calcula o preço total
func (cc CarrinhoCompras) Total() float64 {
var total float64
for _, item := range cc.Itens {
total += item.Subtotal()
}
return total
}
// Contagem retorna o número de tipos de produto
func (cc CarrinhoCompras) Contagem() int {
return len(cc.Itens)
}
// Exibir imprime o conteúdo do carrinho
func (cc CarrinhoCompras) Exibir() {
fmt.Printf("\n🛒 Carrinho de %s (%d produtos):\n", cc.Dono, cc.Contagem())
fmt.Println(" ─────────────────────────────────────")
for _, item := range cc.Itens {
fmt.Printf(" %-12s $%.2f x %d = $%.2f\n",
item.Produto.Nome, item.Produto.Preco, item.Quantidade, item.Subtotal())
}
fmt.Println(" ─────────────────────────────────────")
fmt.Printf(" Total: $%.2f\n", cc.Total())
}
func main() {
carrinho := CarrinhoCompras{Dono: "João Silva"}
// Definir produtos
notebook := Produto{Nome: "Notebook", Preco: 999, Categoria: "Eletrônicos"}
mouse := Produto{Nome: "Mouse Sem Fio", Preco: 29, Categoria: "Eletrônicos"}
livro := Produto{Nome: "Programação Go", Preco: 45, Categoria: "Livros"}
cafe := Produto{Nome: "Café", Preco: 5, Categoria: "Alimentos"}
// Adicionar produtos
fmt.Println("Adicionando produtos:")
carrinho.Adicionar(notebook, 1)
carrinho.Adicionar(mouse, 2)
carrinho.Adicionar(livro, 3)
carrinho.Adicionar(cafe, 5)
carrinho.Exibir()
// Atualizar quantidades
fmt.Println("\nAtualizando quantidades:")
carrinho.Adicionar(mouse, 1) // Adicionar 1 mouse
carrinho.Adicionar(livro, -1) // Remover 1 livro (via número negativo)
carrinho.Exibir()
// Remover produto
fmt.Println("\nRemovendo produto:")
carrinho.Remover("Café")
carrinho.Exibir()
}
Saída:
Adicionando produtos:
Produto adicionado: Notebook x1
Produto adicionado: Mouse Sem Fio x2
Produto adicionado: Programação Go x3
Produto adicionado: Café x5
🛒 Carrinho de João Silva (4 produtos):
─────────────────────────────────────
Notebook $999.00 x 1 = $999.00
Mouse Sem Fio $29.00 x 2 = $58.00
Programação Go $45.00 x 3 = $135.00
Café $5.00 x 5 = $25.00
─────────────────────────────────────
Total: $1217.00
Atualizando quantidades:
Quantidade atualizada: Mouse Sem Fio x3
Quantidade atualizada: Programação Go x2
🛒 Carrinho de João Silva (4 produtos):
─────────────────────────────────────
Notebook $999.00 x 1 = $999.00
Mouse Sem Fio $29.00 x 3 = $87.00
Programação Go $45.00 x 2 = $90.00
Café $5.00 x 5 = $25.00
─────────────────────────────────────
Total: $1201.00
Removendo produto:
Produto removido: Café
🛒 Carrinho de João Silva (3 produtos):
─────────────────────────────────────
Notebook $999.00 x 1 = $999.00
Mouse Sem Fio $29.00 x 3 = $87.00
Programação Go $45.00 x 2 = $90.00
─────────────────────────────────────
Total: $1176.00
❓ Perguntas Frequentes
P1: Como escolher entre receptor por valor e receptor por ponteiro?
Siga estas regras:
- Precisa modificar campos do receptor → Receptor por ponteiro
- Receptor é uma struct grande (muitos campos ou arrays grandes) → Receptor por ponteiro (evita sobrecarga de cópia)
- Struct contém campos não copiáveis como
sync.Mutex→ Deve usar receptor por ponteiro - Operação somente leitura e struct é pequena → Receptor por valor é suficiente
- Em caso de dúvida → Prefira receptor por ponteiro
type Pequeno struct { X int }
func (p Pequeno) Obter() int { return p.X } // ✅ Receptor por valor, somente leitura
type Grande struct { Dados [1024]byte }
func (g *Grande) Processar() { /* ... */ } // ✅ Receptor por ponteiro, evita cópia
type Seguro struct { mu sync.Mutex }
func (s *Seguro) Travar() { s.mu.Lock() } // ✅ Deve usar ponteiro, Mutex é não copiável
P2: Por que o compilador dá erro quando chamo um método?
A razão mais comum é confundir convenções de chamada de receptor por valor e por ponteiro.
type Cachorro struct { Nome string }
func (c *Cachorro) Renomear(nome string) {
c.Nome = nome
}
func main() {
c := Cachorro{Nome: "Rex"}
c.Renomear("Buddy") // ✅ Go pega endereço automaticamente, equivalente a (&c).Renomear("Buddy")
// Mas isso causará erro:
// Cachorro{"Rex"}.Renomear("Buddy") // ❌ Não é possível pegar endereço de valor não endereçável
}
O compilador Go automaticamente lida com a conversão c.Renomear() → (&c).Renomear(), mas apenas se a variável for endereçável. Literais e valores temporários não são endereçáveis.
P3: Métodos podem retornar múltiplos valores?
Sim, métodos e funções têm regras de assinatura idênticas e podem ter qualquer número de parâmetros e valores de retorno.
type Calculadora struct {
Valor float64
}
// Retorna quociente e resto
func (c Calculadora) DividirPor(divisor float64) (float64, float64, error) {
if divisor == 0 {
return 0, 0, fmt.Errorf("divisor não pode ser zero")
}
return c.Valor / divisor, math.Mod(c.Valor, divisor), nil
}
P4: E sobre conflitos de métodos com structs embutidas?
Quando dois tipos embutidos têm métodos com o mesmo nome, o compilador Go reportará um erro, e você precisa especificar explicitamente qual chamar.
type A struct{}
func (A) Ola() string { return "A" }
type B struct{}
func (B) Ola() string { return "B" }
type C struct {
A
B
}
func main() {
c := C{}
// c.Ola() // ❌ Erro de compilação: seletor ambíguo c.Ola
fmt.Println(c.A.Ola()) // ✅ Explícito: saída "A"
fmt.Println(c.B.Ola()) // ✅ Explícito: saída "B"
}
📖 Resumo
Nesta lição aprendemos sobre métodos em Go:
- Definição de método: Vincular funções a tipos através de receptores
- Receptor por valor vs receptor por ponteiro: Receptores por valor operam em cópias; receptores por ponteiro podem modificar valores originais
- Regras de conjunto de métodos: Tipos por valor têm apenas métodos com receptor por valor; tipos por ponteiro têm todos os métodos
- Composição em vez de herança: Alcançar reutilização e sobrescrita de métodos através de structs embutidas
- Melhores práticas: Mantenha nomes de receptores curtos, use tipos de receptor consistentes para o mesmo tipo, use ponteiros quando modificações são necessárias
Métodos são a base da programação orientada a objetos em Go. A próxima lição abordará interfaces — a poderosa ferramenta do Go para polimorfismo, que trabalha em conjunto com métodos para formar o núcleo do sistema de tipos do Go.
📝 Exercícios
Exercício 1: Conversor de Temperatura ⭐
Criar uma struct Temperatura com um campo Celsius, e implementar os seguintes métodos:
ParaFahrenheit()— Converter para Fahrenheit (F = C × 9/5 + 32)ParaKelvin()— Converter para Kelvin (K = C + 273.15)Describe() string— Retornar uma descrição formatada da temperatura
// Saída esperada:
// Temperatura: 100.00°C = 212.00°F = 373.15K
Exercício 2: Conta Bancária (com Histórico de Transações) ⭐⭐
Estender a struct Conta do Cenário 2 com os seguintes recursos:
- Histórico de transações (um slice armazenando o valor e hora de cada transação)
- Método
Historico()para imprimir todos os registros de transação - Método
Extrato()para gerar um extrato formatado da conta
// Saída esperada:
// === Extrato da Conta ===
// Conta: João Silva
// Histórico de transações:
// 1. +1000.00 (Depósito inicial)
// 2. -200.00 (Compra)
// 3. +500.00 (Salário)
// Saldo atual: 1300.00
Exercício 3: Sistema de Formas (Herança por Composição) ⭐⭐⭐
Criar um sistema de formas:
- Definir uma interface base
Formacom métodosArea() float64ePerimetro() float64 - Implementar structs
Retangulo,CirculoeTriangulo - Criar uma struct
Telaque pode conter múltiplas formas - Implementar
Tela.AreaTotal()para calcular a área total de todas as formas - Usar structs embutidas para criar
RetanguloColorido, que tem tanto os métodos de Retangulo quanto um campoCor
// Saída esperada:
// Tela tem 3 formas
// Área total: xxx.xx
// Retângulo vermelho: largura=10, altura=5, cor=vermelho
Próxima Lição
Lição 9: Interfaces — Aprenda sobre o sistema de interfaces do Go: implementação implícita, interface vazia, asserções de tipo, composição de interfaces e o mecanismo de polimorfismo mais poderoso do Go.



