🎯 Programação Orientada a Objetos

Construa classes, objetos, herança e interfaces em Sol!

📐

Conceitos Fundamentais

Sol não tem classes nativas, mas você pode simular OO completo usando tabelas e metamétodos:

Conceito OOEm SolDescrição
ClasseTabela com métodosTemplate/molde para criar objetos
Instância/ObjetoTabela com dadosCópia individual com seus próprios valores
Construtor__chameFunção que cria novas instâncias
Herança__índice encadeadoClasse filha herda de classe pai
InterfaceDuck typing"Se anda como pato e faz quack..."

Ponto (.) vs Dois Pontos (:)

Esta é uma das diferenças mais importantes em Sol:

SintaxeO que aconteceEquivalente
obj:metodo(arg) obj é passado automaticamente como 1º parâmetro obj.metodo(obj, arg)
obj.metodo(arg) Nada é passado automaticamente Apenas arg é passado

Exemplo Visual

local Pessoa = {}
Pessoa.__índice = Pessoa

função Pessoa.apresente(ego)  -- 'ego' é o primeiro parâmetro
    exiba("Olá, sou " .. ego.nome)
fim

local ana = defina_metatabela({nome = "Ana"}, Pessoa)

-- ✅ COM DOIS PONTOS: 'ana' é passado automaticamente como 'ego'
ana:apresente()
-- É o mesmo que: ana.apresente(ana)
-- Resultado: "Olá, sou Ana"

-- ❌ COM PONTO: nada é passado como 'ego'!
ana.apresente()
-- Resultado: ERRO! 'ego' é nulo, não tem .nome

-- ✅ COM PONTO + objeto manual: funciona
ana.apresente(ana)
-- Resultado: "Olá, sou Ana"
⚠️ Regra de Ouro:
• Use : para chamar métodos em objetos
• Use . para definir métodos na classe

Quando usar cada um

-- DEFININDO métodos: use PONTO
função Pessoa.apresente(ego)  -- .
    exiba("Olá, sou " .. ego.nome)
fim

função Pessoa.caminhe(ego, passos)  -- .
    exiba(ego.nome .. " caminhou " .. passos .. " passos")
fim

-- CHAMANDO métodos: use DOIS PONTOS
ana:apresente()       -- :
ana:caminhe(10)      -- : (ego=ana, passos=10)

Classe vs Instância

É importante entender a diferença:

-- CLASSE: é o "molde" (uma tabela com métodos)
local Pessoa = {}
Pessoa.__índice = Pessoa

função Pessoa.apresente(ego)
    exiba("Olá, sou " .. ego.nome)
fim

-- INSTÂNCIAS: são os objetos criados a partir do molde
local ana = defina_metatabela({nome = "Ana"}, Pessoa)
local joao = defina_metatabela({nome = "João"}, Pessoa)

-- Cada instância tem seus próprios dados
ana:apresente()   -- "Olá, sou Ana"
joao:apresente()  -- "Olá, sou João"

-- Mas compartilham os mesmos métodos da classe!
exiba(ana.apresente == joao.apresente)  -- verdadeiro
💡 Classe = tabela com funções (métodos)
💡 Instância = tabela com dados, ligada à classe via __índice

Construtor com __chame

Use __chame para criar um construtor elegante:

local Pessoa = {}
Pessoa.__índice = Pessoa

função Pessoa.apresente(ego)
    exiba("Olá, sou " .. ego.nome .. ", tenho " .. ego.idade .. " anos")
fim

-- Construtor: permite chamar Pessoa() como função
defina_metatabela(Pessoa, {
    __chame = função(classe, nome, idade)
        local ego = defina_metatabela({}, classe)
        ego.nome = nome
        ego.idade = idade
        retorne ego
    fim
})

-- Agora você pode criar instâncias assim:
local p1 = Pessoa("Ana", 25)     -- Cria instância
local p2 = Pessoa("João", 30)    -- Outra instância

p1:apresente()  -- "Olá, sou Ana, tenho 25 anos"
p2:apresente()  -- "Olá, sou João, tenho 30 anos"

Como funciona o construtor?

PassoCódigoO que acontece
1Pessoa("Ana", 25)__chame é invocado
2defina_metatabela({}, classe)Cria tabela vazia ligada à classe
3ego.nome = nomeDefine dados na nova instância
4retorne egoRetorna a instância pronta

Herança

Para criar uma classe que herda de outra:

-- ========== CLASSE BASE ==========
local Animal = {}
Animal.__índice = Animal

função Animal.fale(ego)
    exiba(ego.nome .. " faz algum som")
fim

função Animal.coma(ego)
    exiba(ego.nome .. " está comendo")
fim

defina_metatabela(Animal, {
    __chame = função(classe, nome)
        local ego = defina_metatabela({}, classe)
        ego.nome = nome
        retorne ego
    fim
})

-- ========== CLASSE DERIVADA ==========
local Cachorro = defina_metatabela({}, {__índice = Animal})  -- Herda de Animal
Cachorro.__índice = Cachorro

-- SOBRESCREVER método (override)
função Cachorro.fale(ego)
    exiba(ego.nome .. " late: Au au!")
fim

-- ADICIONAR novo método
função Cachorro.busque(ego)
    exiba(ego.nome .. " está buscando a bolinha!")
fim

defina_metatabela(Cachorro, {
    __chame = função(classe, nome, raca)
        local ego = Animal(nome)       -- Chama construtor pai
        defina_metatabela(ego, classe)  -- Muda para classe filha
        ego.raca = raca
        retorne ego
    fim
})

-- ========== USANDO ==========
local rex = Cachorro("Rex", "Labrador")

rex:fale()    -- "Rex late: Au au!" (método sobrescrito)
rex:coma()    -- "Rex está comendo" (herdado de Animal)
rex:busque()  -- "Rex está buscando a bolinha!" (novo método)
💡 Cadeia de herança:
rexCachorroAnimal

Quando você chama rex:coma(), Sol procura em rex, não acha, procura em Cachorro, não acha, procura em Animal e encontra!

Interfaces (Duck Typing)

Sol usa duck typing: "Se anda como pato e faz quack como pato, então é um pato!"

Em vez de declarar interfaces formalmente, você apenas espera que um objeto tenha certos métodos:

-- Função que espera algo que tenha método "desenhe"
função renderize(forma)
    forma:desenhe()  -- Funciona se 'forma' tiver método 'desenhe'
fim

-- Classe Circulo
local Circulo = {}
Circulo.__índice = Circulo

função Circulo.desenhe(ego)
    exiba("Desenhando círculo com raio " .. ego.raio)
fim

defina_metatabela(Circulo, {
    __chame = função(classe, raio)
        retorne defina_metatabela({raio = raio}, classe)
    fim
})

-- Classe Retangulo
local Retangulo = {}
Retangulo.__índice = Retangulo

função Retangulo.desenhe(ego)
    exiba("Desenhando retângulo " .. ego.largura .. "x" .. ego.altura)
fim

defina_metatabela(Retangulo, {
    __chame = função(classe, largura, altura)
        retorne defina_metatabela({largura = largura, altura = altura}, classe)
    fim
})

-- Ambos funcionam com renderize() - polimorfismo!
local c = Circulo(5)
local r = Retangulo(10, 20)

renderize(c)  -- "Desenhando círculo com raio 5"
renderize(r)  -- "Desenhando retângulo 10x20"

Verificando se objeto implementa "interface"

função pode_desenhar(obj)
    retorne obtenha_tipo(obj) == "tabela" e obtenha_tipo(obj.desenhe) == "função"
fim

função renderize_seguro(forma)
    se pode_desenhar(forma) então
        forma:desenhe()
    fim

    se não pode_desenhar(forma) então
        exiba("Erro: objeto não pode ser desenhado")
    fim
fim

Encapsulamento (Membros Privados)

Use closures para criar membros verdadeiramente privados:

função ContaBancaria(saldo_inicial)
    -- Variável PRIVADA (não acessível de fora)
    local saldo = saldo_inicial ou 0

    -- Objeto PÚBLICO
    local conta = {}

    função conta.deposite(valor)
        se valor > 0 então
            saldo = saldo + valor
            retorne verdadeiro
        fim
        retorne falso
    fim

    função conta.saque(valor)
        se valor > 0 e valor <= saldo então
            saldo = saldo - valor
            retorne verdadeiro
        fim
        retorne falso
    fim

    função conta.obtenha_saldo()
        retorne saldo
    fim

    retorne conta
fim

-- Usando
local minha_conta = ContaBancaria(1000)
minha_conta.deposite(500)
minha_conta.saque(200)
exiba(minha_conta.obtenha_saldo())  -- 1300

-- Não dá para acessar 'saldo' diretamente!
exiba(minha_conta.saldo)  -- nulo (não existe)

Operadores Customizados

Use metamétodos para sobrecarregar operadores:

local Vetor = {}
Vetor.__índice = Vetor

-- Operador + (soma de vetores)
função Vetor.__soma(a, b)
    retorne Vetor(a.x + b.x, a.y + b.y)
fim

-- Operador == (igualdade)
função Vetor.__igualdade(a, b)
    retorne a.x == b.x e a.y == b.y
fim

-- Conversão para texto (usado por exiba)
função Vetor.__converta_para_texto(ego)
    retorne "(" .. ego.x .. ", " .. ego.y .. ")"
fim

defina_metatabela(Vetor, {
    __chame = função(classe, x, y)
        retorne defina_metatabela({x = x, y = y}, classe)
    fim
})

-- Usando
local v1 = Vetor(3, 4)
local v2 = Vetor(1, 2)
local v3 = v1 + v2

exiba(v3)           -- "(4, 6)"
exiba(v1 == v2)     -- falso
exiba(v1 == Vetor(3, 4))  -- verdadeiro

Resumo: Receita para Criar uma Classe

-- 1. Criar tabela da classe
local MinhaClasse = {}
MinhaClasse.__índice = MinhaClasse

-- 2. Definir métodos
função MinhaClasse.meu_metodo(ego, ...)
    -- código do método
fim

-- 3. Definir construtor com __chame
defina_metatabela(MinhaClasse, {
    __chame = função(classe, ...)
        local ego = defina_metatabela({}, classe)
        -- inicializar campos
        retorne ego
    fim
})

-- 4. Usar!
local obj = MinhaClasse(...)
obj:meu_metodo(...)
⚠️ Lembre-se: Use objeto:metodo() (com dois pontos) para chamar métodos. Isso passa automaticamente ego como primeiro parâmetro!
🎉 Agora você domina OO em Sol! Classes, instâncias, herança, interfaces e encapsulamento - tudo usando tabelas e metamétodos.
```