Classes, objetos, métodos e herança
Antes: Tratamento de erros e exceções. Depois: Concorrência, assincronia e canais. Referência técnica: Sistema de tipos, Catálogo nativo.
O que você vai aprender aqui?
À medida que uma aplicação cresce, ter variáveis espalhadas de um lado e funções soltas de outro pode se tornar caótico:
- Você pode ter uma variável $usuario_nombre, outra $usuario_email, outra $usuario_rol.
- Se você tiver 100 usuários, como mantém unidos os dados de cada um com as operações que lhes correspondem (como autenticar-se, alterar senha ou enviar notificação)?
A Programação Orientada a Objetos (POO) resolve esse problema empacotando dados e comportamentos relacionados em uma única unidade conceitual.
Neste guia você aprenderá:
1. O que é uma classe (a planta de projeto) e o que é um objeto ou instância (a entidade real).
2. Como definir propriedades (atributos) e métodos (funções da classe).
3. Como inicializar objetos com construtores e Init.
4. O papel da variável especial $this.
5. Os níveis de visibilidade e encapsulamento: public, protected e private.
6. Membros estáticos e o operador de resolução de escopo (::).
7. Como reutilizar e especializar código mediante herança (extends), interfaces (implements) e classes abstratas (abstract).
8. Classes genéricas (<T>), record imutáveis e o operador de navegação segura contra nulos (?->).
1. Classes e Objetos: A planta e a casa
Para entender a orientação a objetos, a melhor analogia é a arquitetura: - Uma classe é a planta arquitetônica: descreve quais cômodos a casa terá, quantas portas e quais funções possui. A planta não ocupa um terreno físico nem você pode morar nela. - Um objeto (ou instância) é a casa física real construída em um terreno a partir dessa planta. Você pode construir dez casas a partir da mesma planta; pintar uma casa de azul não muda a cor das demais.
Vejamos um exemplo mínimo com um contador:
public class Contador {
public int $valor = 0
public func incrementar(): int {
$this->valor = $this->valor + 1
return $this->valor
}
}
var $contador = new Contador()
print($contador->incrementar())
print($contador->incrementar())
Saída no terminal:
1
2
Quais elementos compõem este código?
public class Contador: Declara uma classe pública chamadaContador. No Joss, as classes em nível de arquivo exigem um modificador de visibilidade (publicouprivate).public int $valor = 0: É uma propriedade (um dado que cada instância deContadorlembrará).public func incrementar(): int: É um método (uma função que pertence à classe e que pode manipular suas propriedades).$this: É uma palavra reservada que significa “este objeto em particular”. Quando você executa$this->valor, está acessando a propriedade$valorda instância que está executando o método.new Contador(): A palavra-chavenewcria uma nova instância real na memória.$contador->incrementar(): O operador seta->é utilizado para acessar propriedades e métodos de uma instância.
2. Inicialização de objetos: Construtores
Quando você cria um objeto, quase sempre precisa configurá-lo com dados iniciais (por exemplo, o nome de uma pessoa ou as credenciais de um banco de dados).
No Joss você pode definir um método public func constructor(...) ou um bloco Init(...):
public class Persona {
private string $nombre = ""
public func constructor(string $nombre) {
$this->nombre = $nombre
}
public func saludar(): string {
return "Hola, " . $this->nombre
}
}
var $persona = new Persona("Ada")
print($persona->saludar())
Saída no terminal:
Hola, Ada
Ao escrever new Persona("Ada"), o Joss chama automaticamente o construtor entregando-lhe o argumento "Ada", o qual fica guardado de forma segura dentro da propriedade privada $this->nombre.
[!NOTE] O Joss também admite a sintaxe de bloco
Init(string $nombre) { ... }. Os blocosInitnão levam modificadores de visibilidade (publicnemprivate). A forma histórica combinadaInit constructor(...)continua sendo aceita por compatibilidade, mas emite o aviso de transiçãoJOSS-DECL-008; prefirapublic func constructor(...)ouInit(...).
Promoção de propriedades no construtor (Constructor Property Promotion)
Para evitar ter que declarar a propriedade, receber o parâmetro e escrever $this->prop = $prop manualmente, o Joss permite declarar a visibilidade (public, protected ou private) e constância (const) diretamente nos parâmetros de Init ou do constructor. O Joss criará e atribuirá a propriedade automaticamente:
public class Usuario {
Init(
public string $nombre,
public int $edad = 30
) {}
}
var $u = new Usuario("Ada")
print($u->nombre)
print($u->edad)
Saída no terminal:
Ada
30
Também é possível declarar propriedades constantes promovidas com public const Tipo $campo para protegê-las contra reatribuições posteriores.
3. Encapsulamento e modificadores de visibilidade
O encapsulamento é o princípio de proteger os dados internos de um objeto para evitar que código externo os modifique de forma incorreta ou corrompida.
O Joss oferece três modificadores de visibilidade explícitos:
| Modificador | Onde pode ser acessado | Uso recomendado |
|---|---|---|
public |
De qualquer parte do programa (dentro da classe, em subclasses e a partir de código externo). | Para a API pública do objeto: métodos que os usuários da sua classe precisam invocar. |
protected |
Apenas dentro da própria classe e dentro das subclasses que herdem dela com extends. |
Para métodos e propriedades internas que as classes filhas precisam especializar ou consultar. |
private |
Unicamente dentro da classe exata onde foi declarada. Ninguém mais pode vê-la nem modificá-la. | Para detalhes íntimos de implementação (senhas, conexões brutas, flags de estado). |
public class CuentaBancaria {
private decimal $saldo = 0.0m
public func depositar(decimal $monto): void {
($monto > 0.0m) ? {
$this->saldo = $this->saldo + $monto
}
}
public func obtenerSaldo(): decimal {
return $this->saldo
}
}
var $cuenta = new CuentaBancaria()
$cuenta->depositar(150.50m)
print($cuenta->obtenerSaldo())
Saída no terminal:
150.5
Ao tornar $saldo privado, ninguém pode escrever $cuenta->saldo = -5000.0m de fora, garantindo que o dinheiro só seja modificado sob as regras do método depositar.
4. Membros estáticos e o operador ::
Nem todas as propriedades ou métodos pertencem a uma casa individual; algumas operações pertencem ao conceito geral da classe ou não exigem criar uma instância com new.
Esses elementos são chamados de estáticos e são declarados com a palavra static (lembre-se de que static sempre acompanha um modificador de visibilidade como public static):
public class Utilidades {
public static func limpiarTexto(string $t): string {
return trim($t)
}
}
var $limpio = Utilidades::limpiarTexto(" hola ")
print($limpio)
Saída no terminal:
hola
Para invocar um método estático ou ler uma propriedade estática, não se utiliza ->, mas sim o operador de dois-pontos duplos ::. No Joss, as classes nativas do sistema (como Auth::user(), GranDB::table(), Router::get(), Cache::put()) são fachadas invocadas habitualmente mediante ::.
5. Herança com extends
A herança permite criar uma classe nova baseada em uma classe existente, reutilizando todos os seus métodos e propriedades públicas e protegidas sem ter que reescrevê-los:
public class Mensaje {
public func texto(): string { return "hola" }
}
public class Aviso extends Mensaje {}
var $aviso = new Aviso()
print($aviso->texto())
Saída no terminal:
hola
- A classe
Mensajeé a classe base (ou superclasse). - A classe
Avisoé a classe derivada (ou subclasse). Avisoherda automaticamente o métodotexto()deMensaje.
[!TIP] Quando usar herança vs quando usar composição: Use herança apenas quando existir uma relação estrita do tipo “é um” (por exemplo,
Gato extends AnimalouAdminUser extends User). Se você só quer reutilizar uma função utilitária, não use herança; use funções ou injete uma classe de serviço.
6. Interfaces e Polimorfismo (interface e implements)
Quando você trabalha em aplicações modulares ou arquitetura limpa, muitas vezes quer definir o que um componente deve fazer sem se prender a como ele faz.
Uma interface é um contrato formal:
- Declara apenas os protótipos dos métodos públicos (nome, parâmetros tipados e tipo de retorno) sem corpo.
- Uma interface não pode ser instanciada diretamente com new.
- Qualquer classe que declare implements NombreInterfaz é obrigada pelo analisador semântico e pelo runtime a implementar todos os métodos prometidos com assinaturas compatíveis.
- Uma classe pode herdar de uma classe base e ao mesmo tempo implementar múltiplas interfaces separadas por vírgulas: public class MiClase extends Base implements I1, I2.
- Uma interface pode estender uma ou várias interfaces: public interface IDerivada extends IBase1, IBase2.
Vejamos um exemplo de polimorfismo executável:
public interface IFigura {
public func calcularArea(): int;
}
public class Rectangulo implements IFigura {
public int $ancho = 0
public int $alto = 0
public func constructor(int $ancho, int $alto) {
$this->ancho = $ancho
$this->alto = $alto
}
public func calcularArea(): int {
return $this->ancho * $this->alto
}
}
public class Cuadrado implements IFigura {
public int $lado = 0
public func constructor(int $lado) {
$this->lado = $lado
}
public func calcularArea(): int {
return $this->lado * $this->lado
}
}
public func imprimirArea(IFigura $figura): int {
return $figura->calcularArea()
}
var $r = new Rectangulo(5, 10)
var $c = new Cuadrado(6)
print(imprimirArea($r))
print(imprimirArea($c))
Saída no terminal:
50
36
Vantagens do Polimorfismo com Interfaces:
- Desacoplamento: A função
imprimirArea(IFigura $figura)não precisa saber se recebe umRectangulo, umCuadradoou qualquer figura futura; ela apenas confia que cumpre o contratoIFigura. - Validação estática exaustiva: Se você esquecer de implementar um método da interface em uma classe ou sua assinatura não coincidir, o analisador semântico emite
JOSS-DECL-004(ouJOSS-SEM-001se um método implementado não forpublic).
7. Classes e métodos abstratos (abstract)
Uma classe abstrata (public abstract class) serve como modelo base para outras classes, mas não pode ser instanciada diretamente com new (tentar fazer isso emite o diagnóstico JOSS-DECL-006).
As classes abstratas podem conter:
- Propriedades e métodos completos com implementação para serem herdados.
- Métodos abstratos (public abstract func nombre(...): Tipo) que não possuem corpo e obrigam as subclasses concretas a implementá-los (omiti-los emite JOSS-DECL-005).
public abstract class Animal {
public abstract func hablar(): string
}
public class Perro extends Animal {
public func hablar(): string {
return "Guau!"
}
}
var $perro = new Perro()
print($perro->hablar())
Saída no terminal:
Guau!
8. Verificação de tipos e instâncias: is e instanceof
Para verificar em tempo de execução se um objeto pertence a uma classe concreta, herda de uma classe base ou implementa uma interface, utilize os operadores equivalentes is ou instanceof:
public interface IMovible {}
public class Auto implements IMovible {}
var $auto = new Auto()
print($auto is Auto)
print($auto is IMovible)
print($auto instanceof string)
Saída no terminal:
true
true
false
Você também pode usar is com tipos primitivos como int, string, bool, etc. (ex.: $x is int).
9. Navegação segura contra nulos (?->)
Se uma variável puder conter uma instância ou ser null (tipo Persona?), tentar acessar um método com -> sobre um valor nulo produziria uma falha.
O Joss inclui o operador null-safe (?->):
public class Perfil {
public func saludo(): string {
return "Hola"
}
}
Perfil? $usuario = null
var $nombre = $usuario?->saludo()
print($nombre ?? "Sin usuario")
Saída no terminal:
Sin usuario
Se $usuario for null, a chamada é cancelada de forma silenciosa e segura, e $nombre receberá simplesmente null sem interromper o programa.
10. Classes genéricas (class Contenedor<T>) e Records imutáveis (record)
Classes genéricas
O Joss admite parâmetros formais de tipo <T> na declaração de classes:
public class Caja<T> {
public T $contenido
public func constructor(T $valor) {
$this->contenido = $valor
}
}
O analisador semântico valida nominalmente os parâmetros genéricos garantindo que Caja<int> não seja atribuível a Caja<string>.
Records (public record Nombre(campos...))
Um record é uma declaração concisa de classe orientada a dados imutáveis com construtor e campos automáticos:
public record Coordenada(int $x, int $y)
var $c = new Coordenada(10, 20)
print($c->x)
print($c->y)
Saída no terminal:
10
20
Os campos declarados em um record ficam selados como constantes (const) após a inicialização do objeto, impedindo qualquer reatribuição acidental (JOSS-CONST-001).
11. Ciclo de vida e autodestruição inteligente por proteção
No Joss, as classes são escritas de forma padrão sem sintaxe complicada. Internamente, o motor acopla um finalizador de ciclo de vida a cada instância criada com new.
Quando um objeto esgota seu ciclo de vida e fica sem referências no programa:
1. Destrutor opcional: Se a classe definir um método destructor(), destroy() ou __destruct(), o motor o executa automaticamente de forma isolada e segura.
2. Fechamento de recursos nativos: Se a instância retinha recursos do sistema (arquivos, canais de comunicação, streams), eles são fechados automaticamente prevenindo vazamentos de descritores.
3. Expurgo de memória (Zeroization): Os campos internos da instância são esvaziados e sanitizados para evitar que dados sensíveis (tokens, senhas) persistam desnecessariamente na memória RAM.
4. Proteção contra acessos zumbis: A instância fica marcada como destruída. Se algum ponteiro residual tentar ler ou modificar seus membros, o motor lança um SecurityError protegendo a integridade do sistema.
public class SesionSegura {
public string $token = "tok_12345"
public func constructor() {
print("Conexión activa")
}
public func destructor() {
print("Cerrando sesión de forma segura...")
}
}
var $s = new SesionSegura()
$s->destructor()
Saída no terminal:
Conexión activa
Cerrando sesión de forma segura...
12. Erros comuns em POO com Joss
| Erro | Causa | Solução |
|---|---|---|
Confundir -> com :: |
Escrever $objeto::metodo() ou Clase->metodo(). |
Use -> para instâncias reais criadas com new e :: para chamadas estáticas a classes. |
| Tentar acessar um membro privado | $cuenta->saldo quando é private. |
Crie um método público getter (como obtenerSaldo()) para consultar o valor. |
Esquecer new ao instanciar |
var $p = Persona() em vez de var $p = new Persona(). |
A criação de instâncias exige a palavra new. |
| Confundir uma instância com um map | Tratar um objeto como array associativo ($objeto["campo"]). |
Os objetos usam seta ($objeto->campo), os maps usam colchetes ($mapa["campo"]). |
| Descumprir contrato de interface | A classe declara implements mas falta um método ou seus parâmetros não coincidem. |
Implementar todos os métodos da interface com visibilidade public e tipos compatíveis (JOSS-DECL-004). |
| Instanciar uma classe abstrata | Fazer new ClaseAbstracta() diretamente. |
Estenda a classe abstrata em uma subclasse concreta e instancie a subclasse (JOSS-DECL-006). |
| Acesso a objeto destruído | Tentar ler ou escrever em um objeto após executar seu ciclo de destruição. | Criar uma nova instância válida em vez de reutilizar um objeto já invalidado (SecurityError). |
13. Exercícios práticos
Nível 1 — Teste rápido
Declare um public record Producto(string $nombre, decimal $precio). Instancie um produto "Teclado" com preço 45.50m e imprima seu nome e seu preço usando ->.
Nível 2 — Exercício guiado
- Crie uma classe
public class Vehiculocom uma propriedade protegidaprotected string $marca = ""inicializada empublic func constructor(string $marca)e um métodopublic func obtenerMarca(): string. - Crie uma classe derivada
public class Auto extends Vehiculoque tenha uma propriedadepublic int $puertas = 4. - Instancie um
Auto("Toyota"), e imprima sua marca (usandoobtenerMarca()) e seu número de portas no console.
Nível 3 — Desafio
- Defina uma interface
public interface INotificadorcom o métodopublic func enviar(string $mensaje): string;. - Crie duas classes que implementem
INotificador:NotificadorEmail: devolve"[EMAIL] " . $mensaje.NotificadorSMS: devolve"[SMS] " . $mensaje.
- Escreva uma função
public func despacharAlerta(INotificador $canal, string $texto): voidque imprima o resultado de$canal->enviar($texto). - Teste-a passando uma instância de
NotificadorEmaile outra deNotificadorSMS.
Próximo passo
Agora que você domina classes, contratos de interfaces e objetos, está pronto para descobrir uma das capacidades mais poderosas do Joss: executar tarefas em paralelo, delegar operações para segundo plano e comunicar processos mediante canais seguros.
Continue com: Concorrência, operações assíncronas, Future e Canais.