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:

GO
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

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

GO
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

GO
// 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

GO
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!
}
💡 Dica: Nomes de métodos devem ser concisos e significativos Nomes de métodos devem ser um verbo ou frase verbal descrevendo a operação realizada. Exemplos: Area(), Escalar(), Salvar(), EhValido().

💡 Dica: Mantenha nomes de receptores curtos Variáveis de receptor tipicamente são a primeira letra ou as duas primeiras letras do nome do tipo. Por exemplo, func (r Retangulo) em vez de func (ret Retangulo). Esta é a convenção do Go.

💡 Dica: Princípio de consistência Todos os métodos do mesmo tipo devem usar o mesmo tipo de receptor — ou todos receptores por valor ou todos receptores por ponteiro (quando modificações são necessárias). Não os misture.

💡 Dica: Quando usar receptores por ponteiro Sempre use receptores por ponteiro quando a struct contém campos não copiáveis (como 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.

GO
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())
}
▶ Experimente

Saída:

TEXT
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.

GO
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())
}
▶ Experimente

Saída:

TEXT
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.

GO
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
}
▶ Experimente

Saída:

TEXT
=== 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.

GO
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:

TEXT
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.

GO
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:

TEXT
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:

  1. Precisa modificar campos do receptor → Receptor por ponteiro
  2. Receptor é uma struct grande (muitos campos ou arrays grandes) → Receptor por ponteiro (evita sobrecarga de cópia)
  3. Struct contém campos não copiáveis como sync.Mutex → Deve usar receptor por ponteiro
  4. Operação somente leitura e struct é pequena → Receptor por valor é suficiente
  5. Em caso de dúvida → Prefira receptor por ponteiro
GO
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.

GO
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.

GO
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.

GO
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:

  1. Definição de método: Vincular funções a tipos através de receptores
  2. Receptor por valor vs receptor por ponteiro: Receptores por valor operam em cópias; receptores por ponteiro podem modificar valores originais
  3. 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
  4. Composição em vez de herança: Alcançar reutilização e sobrescrita de métodos através de structs embutidas
  5. 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:

GO
// 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:

GO
// 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:

  1. Definir uma interface base Forma com métodos Area() float64 e Perimetro() float64
  2. Implementar structs Retangulo, Circulo e Triangulo
  3. Criar uma struct Tela que pode conter múltiplas formas
  4. Implementar Tela.AreaTotal() para calcular a área total de todas as formas
  5. Usar structs embutidas para criar RetanguloColorido, que tem tanto os métodos de Retangulo quanto um campo Cor
GO
// 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.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%