🎯 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 OO | Em Sol | Descrição |
|---|---|---|
| Classe | Tabela com métodos | Template/molde para criar objetos |
| Instância/Objeto | Tabela com dados | Cópia individual com seus próprios valores |
| Construtor | __chame | Função que cria novas instâncias |
| Herança | __índice encadeado | Classe filha herda de classe pai |
| Interface | Duck typing | "Se anda como pato e faz quack..." |
Ponto (.) vs Dois Pontos (:)
Esta é uma das diferenças mais importantes em Sol:
| Sintaxe | O que acontece | Equivalente |
|---|---|---|
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
• Use
• 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
💡 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?
| Passo | Código | O que acontece |
|---|---|---|
| 1 | Pessoa("Ana", 25) | __chame é invocado |
| 2 | defina_metatabela({}, classe) | Cria tabela vazia ligada à classe |
| 3 | ego.nome = nome | Define dados na nova instância |
| 4 | retorne ego | Retorna 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:
Quando você chama
rex → Cachorro → AnimalQuando 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.