Classes e Interfaces
Soyuz suporta OOP com uma restrição deliberada: sem herança entre classes. Comportamento compartilhado é expresso exclusivamente via interfaces e composição.
Interfaces
Section titled “Interfaces”Interfaces definem contratos. São um conjunto de assinaturas de método que uma classe deve implementar.
interface Persistivel { fn salvar(self) -> Result[Unit] fn deletar(self, id: Int) -> Result[Unit]}
interface Serializavel { fn toJson(self) -> String fn fromJson(json: String) -> Result[Self]}
interface Auditavel { fn log(self) -> String}Interfaces não têm implementação — apenas assinaturas. Isso as mantém como contratos puros.
Interfaces genéricas
Section titled “Interfaces genéricas”interface Repositorio[T] { fn buscar(self, id: Int) -> Option[T] fn salvarTodos(self, items: List[T]) -> Result[Unit]}
interface Comparavel[T] { fn comparar(self, outro: T) -> Int // -1, 0, ou 1}Classes
Section titled “Classes”Classes combinam dados e comportamento. Podem implementar múltiplas interfaces, mas não herdam de outras classes.
class Usuario : Persistivel, Serializavel { val id: Int val nome: String var idade: Int var email: String
fn salvar(self) -> Result[Unit] { // implementação return Ok(()) }
fn deletar(self, id: Int) -> Result[Unit] { return Ok(()) }
fn toJson(self) -> String { return "{\"id\": $(self.id), \"nome\": \"$(self.nome)\"}" }
fn fromJson(json: String) -> Result[Usuario] { // implementação return Err(ParseErro { input: json }) }}Criando instâncias
Section titled “Criando instâncias”Classes são instanciadas com a sintaxe de record literal:
val u = Usuario { id: 1, nome: "Vand", idade: 28, email: "vand@soyuz.dev"}Campos
Section titled “Campos”Classes podem ter campos val (imutáveis) e var (mutáveis):
class Produto { val id: Int // imutável após criação val nome: String // imutável após criação var preco: Float // pode ser alterado var estoque: Int // pode ser alterado}Mutabilidade de self
Section titled “Mutabilidade de self”Quando um método modifica um campo var, o self é mutável implicitamente. Não há mut self:
class ContaBancaria { val titular: String var saldo: Float
fn depositar(self, valor: Float) -> Result[Float] { if valor <= 0.0 { return Err(ValorInvalidoErro { valor: valor }) } self.saldo = self.saldo + valor // OK — saldo é var return Ok(self.saldo) }
fn titular(self) -> String { return self.titular // OK — acesso somente leitura }}Métodos
Section titled “Métodos”Todos os métodos recebem self explicitamente como primeiro parâmetro.
Visibilidade de métodos
Section titled “Visibilidade de métodos”Por padrão, métodos são privados ao módulo — só podem ser chamados por código no mesmo arquivo. Use pub para torná-los acessíveis de fora da classe:
class Retangulo { val largura: Float val altura: Float
pub fn area(self) -> Float { // acessível de fora return self.largura * self.altura }
pub fn perimetro(self) -> Float { // acessível de fora return 2.0 * (self.largura + self.altura) }
pub fn escalar(self, fator: Float) -> Retangulo { // acessível de fora return Retangulo { largura: self.largura * fator, altura: self.altura * fator } }
fn calcularDiagonal(self) -> Float { // privado — helper interno return self.largura * self.largura + self.altura * self.altura }}Overloading de métodos
Section titled “Overloading de métodos”Assim como funções globais, métodos em classes podem ser sobrecarregados:
class Usuario { fn promover(self) { ... } fn promover(self, nivel: Int) { ... }}Pattern matching em métodos
Section titled “Pattern matching em métodos”Métodos também suportam pattern matching em parâmetros:
class Parser { fn parsear(self, Some(token)) -> Result[Node] = self.parseToken(token) fn parsear(self, None) -> Result[Node] = Err(TokenEsperadoErro {})}Sem herança — e por quê
Section titled “Sem herança — e por quê”Soyuz deliberadamente não tem herança. Essa decisão tem razões concretas:
Herança cria acoplamento frágil. Mudanças na classe pai quebram silenciosamente as filhas. Com interfaces, o contrato é explícito e estável.
Hierarquias profundas são difíceis de raciocinar. Quando você lê objeto.metodo(), precisa navegar pela árvore de herança para entender o que executa. Com interfaces, você sabe exatamente qual classe implementa o quê.
Composição é mais flexível. Se você precisa reutilizar comportamento, componha:
// Ao invés de herdar de Loggerclass ServicoDeUsuario : Persistivel { val logger: Logger // composição val repositorio: UsuarioRepositorio // composição
fn criar(self, dados: NovosUsuarioDados) -> Result[Usuario] { self.logger.info("Criando usuário: $(dados.nome)") return self.repositorio.salvar(dados) }}Classes genéricas
Section titled “Classes genéricas”class Repositorio[T : Entidade] : Store[T] { var items: List[T] = []
fn adicionar(self, item: T) -> Result[Unit] { self.items.add(item) return Ok(()) }
fn buscar(self, id: Int) -> Option[T] { return self.items.find(fn(i: T) => i.id == id) }
fn todos(self) -> List[T] { return self.items }}
// Usoval repos = Repositorio[Usuario] { items: [] }Múltiplas interfaces
Section titled “Múltiplas interfaces”Uma classe pode implementar quantas interfaces forem necessárias:
class EventoAuditavel : Persistivel, Serializavel, Auditavel, Comparavel[EventoAuditavel] { val id: Int val tipo: String val timestamp: Int var processado: Bool
fn salvar(self) -> Result[Unit] { return Ok(()) } fn deletar(self, id: Int) -> Result[Unit] { return Ok(()) } fn toJson(self) -> String { return "{}" } fn fromJson(json: String) -> Result[EventoAuditavel] { return Err(ParseErro { input: json }) } fn log(self) -> String { return "Evento $(self.id): $(self.tipo)" } fn comparar(self, outro: EventoAuditavel) -> Int { if self.timestamp < outro.timestamp { return -1 } if self.timestamp > outro.timestamp { return 1 } return 0 }}Diferença entre record e class
Section titled “Diferença entre record e class”record | class | |
|---|---|---|
| Campos mutáveis | Nunca | var campos |
| Métodos | Não | Sim |
| Implementa interfaces | Não | Sim |
| Passagem | Por referência readonly | Por referência |
| Igualdade | Deep equality automática | Definida pela classe |
| Uso típico | DTOs, dados puros | Serviços, entidades com comportamento |
// Record — dados puros, sem comportamentorecord EnderecoDTO { rua: String cidade: String cep: String}
// Class — entidade com comportamento e estadoclass Pedido : Persistivel { val id: Int var status: StatusPedido var itens: List[Item]
fn confirmar(self) -> Result[Unit] { if self.itens.isEmpty() { return Err(PedidoVazioErro {}) } self.status = StatusPedido.Confirmado return Ok(()) }
fn salvar(self) -> Result[Unit] { return Ok(()) }}