Fundamentos
Objeto, classe, instância, estado, identidade — e o que cada palavra significa em DataForge.
Antes da sintaxe, o vocabulário. Cada termo abaixo tem um nome próprio em DataForge, e saber qual é qual poupa a maior parte da confusão.
O que é orientação a objetos#
Um jeito de organizar programa juntando dados e o comportamento que age sobre eles. A alternativa — dados de um lado, funções de outro — funciona bem até o programa crescer; aí ninguém mais sabe quais funções podem mexer em quais dados.
OOP responde isso pondo os dois no mesmo lugar e controlando quem entra.
Classe e instância#
A classe é a forma; a instância é a peça. Em DataForge a classe é o blueprint — a planta — e a peça nasce com spawn:
blueprint Conta:
private saldo: Float := 0.0
action setup(titular):
self.titular := titular
action depositar(valor):
self.saldo += valor
yield self.saldo
get extrato():
yield $"{self.titular}: {self.saldo}"
// a planta é uma; as peças são duas, e independentes
a := spawn Conta("Ana")
b := spawn Conta("Bia")
a.depositar(100)
assert a.extrato is "Ana: 100.0"
assert b.extrato is "Bia: 0.0"Estado, comportamento, identidade#
Todo objeto tem três coisas, e elas se confundem com frequência:
| O que é | Em DataForge | |
|---|---|---|
| estado | os valores que ele guarda agora | os campos: self.saldo |
| comportamento | o que ele sabe fazer | os métodos: action depositar |
| identidade | o que faz ele ser ele, e não outro igual | a referência — dois spawn dão dois objetos |
A identidade é o que separa blueprint de record. Duas contas com o mesmo saldo não são a mesma conta; dois pontos (1,2) são o mesmo ponto:
record Ponto:
x: Integer
y: Integer
blueprint Caixa:
action setup(n):
self.n := n
// record: igualdade estrutural — o conteúdo decide
assert Ponto(1, 2) is Ponto(1, 2)
// blueprint: identidade — cada spawn é um objeto
assert (spawn Caixa(1)) isnt (spawn Caixa(1))Atributos e métodos#
Campos guardam; métodos agem. Ambos podem ser de instância (cada objeto tem o seu) ou estáticos (um só, da classe):
blueprint Contador:
static total: Integer := 0
valor: Integer := 0
action somar():
self.valor += 1
yield self.valor
static action zerar():
yield 0
a := spawn Contador()
b := spawn Contador()
a.somar()
a.somar()
b.somar()
assert a.valor is 2
assert b.valor is 1
assert Contador.zerar() is 0Ver métodos estáticos para quando usar cada um.
Construtor#
setup é o construtor: roda uma vez, no spawn, e é onde o objeto nasce válido. Há duas formas:
// forma explícita
blueprint Retangulo:
action setup(largura, altura):
given largura smaller_eq 0 or altura smaller_eq 0:
trigger "as medidas precisam ser positivas"
self.largura := largura
self.altura := altura
// forma inline — os parâmetros viram campos
blueprint Circulo(raio):
action area():
yield 3.14159 * self.raio ** 2
assert (spawn Retangulo(3, 4)) isnt void
assert round((spawn Circulo(1)).area(), 3) is 3.142`self`#
self é o objeto em que o método está rodando. Escrever `x` em vez de `self.x` lê a variável do escopo externo — é a armadilha mais comum de quem começa:
valor := "de fora"
blueprint Errado:
action setup():
self.valor := "de dentro"
action mostrar():
yield valor // lê o de FORA
blueprint Certo:
action setup():
self.valor := "de dentro"
action mostrar():
yield self.valor // lê o campo
assert (spawn Errado()).mostrar() is "de fora"
assert (spawn Certo()).mostrar() is "de dentro"Modificadores de acesso#
Três níveis, e eles valem de verdade — não por convenção de nome:
| Quem alcança | |
|---|---|
| (padrão) | qualquer um |
protected | o blueprint que declarou e seus herdeiros |
private | só o blueprint que declarou — nem o herdeiro |