Modelagem
Record ou blueprint, herança ou composição — as escolhas que importam.
A sintaxe é a parte fácil. O que custa é escolher entre as formas — e o custo aparece meses depois, quando mudar a escolha significa reescrever quem usa.
Record ou blueprint#
Dois desses, com os mesmos valores, são a mesma coisa?
record | blueprint | |
|---|---|---|
| Igualdade | estrutural | identidade |
| Mutação | imutável; with copia | estado evolui |
| Exemplo | ponto, dinheiro, data, cor | conta, sessão, conexão, carrinho |
record Ponto:
x: Integer
y: Integer
action distancia_ate(outro):
yield sqrt((self.x - outro.x) ** 2 + (self.y - outro.y) ** 2)
// dois pontos com as mesmas coordenadas SAO o mesmo ponto
a := Ponto(1, 2)
b := Ponto(1, 2)
assert a is b, "records com os mesmos valores sao iguais"
assert a.distancia_ate(Ponto(4, 6)) is 5.0, "e tem comportamento"
// e nao mudam: 'with' cria outro
movido := a with {"x": 10}
assert a.x is 1, "o original nao mudou"
assert movido.x is 10, "a copia tem o valor novo"
monitor:
a.x := 99
assert no, "deveria ter recusado"
handle e:
out "record e imutavel"Usar blueprint para valor força a implementar operator == à mão e a lembrar de copiar antes de passar adiante. Usar record para entidade obriga a recriar o objeto a cada mudança, e duas contas iguais viram uma.
Herança ou composição#
blueprint Carro:
modelo: String := ""
action setup(modelo, motor):
self.modelo := modelo
self.motor := motor // o carro TEM um motor
action dar_partida():
yield $"{self.modelo}: {self.motor.ligar()}"
action ficha():
yield $"{self.modelo} — {round(self.motor.potencia(), 1)} kW"Composição permite trocar a peça: carro.motor := spawn MotorCombustao(100) é o mesmo carro com outro motor. Com herança, seria outro tipo.
O custo é uma indireção — self.motor.ligar() em vez de self.ligar(). Para uma relação que realmente é "é um", herança é mais direta.
Polimorfismo#
O ganho não é evitar given. É que o código que percorre a lista não muda quando chega um tipo novo:
formatos := [spawn ComoJson(dados), spawn ComoCsv(dados), spawn ComoTexto(dados)]
// este laco nao sabe quantos formatos existem, nem quais
cycle f in formatos:
out $"--- .{f.extensao()} ---"
out f.exportar()Compare com a alternativa: um given/orif que cresce toda vez que chega um formato. Acrescentar tipo vira adição, não edição — e é essa propriedade que faz o código envelhecer bem.
Quando não vale#
- Polimorfismo com dois casos que nunca vão crescer é cerimônia — um
givenresolve e se lê melhor - Se cada implementação precisa de um parâmetro diferente, a interface comum não existe de verdade
- Tornar tudo privado e criar
get/setpara cada campo devolve o problema ao ponto de partida: exponha comportamento, não estado
Praticar#
Os dez exercícios do módulo 21 percorrem essas decisões, e os projetos mostram as escolhas em programas completos.