Pular para o conteúdo

Encapsulamento

Esconder o detalhe para poder mudá-lo — e as validações que só existem porque ele existe.

Encapsular não é esconder por esconder. É separar o que os outros dependem do que você pode mudar amanhã.

Um campo público faz parte do contrato: mudá-lo quebra quem o usa. Um campo privado é seu — você troca a implementação e ninguém percebe.

O problema, concretamente#

dataforge
// Sem encapsulamento: qualquer um mexe no saldo
blueprint ContaAberta:
    saldo: Float := 0.0

c := spawn ContaAberta()
c.saldo := -1000        // ninguém impediu
assert c.saldo is -1000.0
dataforge
// Com encapsulamento: a regra mora com o dado
blueprint Conta:
    private saldo: Float := 0.0

    action depositar(valor):
        given valor smaller_eq 0:
            trigger "depósito precisa ser positivo"
        self.saldo += valor
        yield self.saldo

    action sacar(valor):
        given valor bigger self.saldo:
            trigger "saldo insuficiente"
        self.saldo -= valor
        yield self.saldo

    get extrato():
        yield self.saldo

c := spawn Conta()
c.depositar(100)
c.sacar(30)
assert c.extrato is 70.0

monitor:
    c.sacar(1000)
handle e:
    assert e.message is "saldo insuficiente"

O saldo nunca fica negativo, e não porque quem usa lembrou de conferir — porque não há caminho até ele que não passe pela regra.

`private` alcança quem?#

Só o blueprint que declarou o membro. Nem o herdeiro entra:

dataforge
blueprint Base:
    private segredo: Integer := 42
    protected compartilhado: Integer := 7

    action ler_proprio():
        yield self.segredo

blueprint Filho extends Base:
    action ler_protegido():
        yield self.compartilhado

// um método de Base lê o private de Base, mesmo numa instância de Filho
assert (spawn Filho()).ler_proprio() is 42
assert (spawn Filho()).ler_protegido() is 7

monitor:
    out (spawn Base()).segredo
handle e:
    assert "private" in e.message

Getters e setters, com propósito#

Um get/set que só devolve e atribui o campo não encapsula nada — é um campo público com três linhas a mais. O valor aparece quando há regra ou derivação:

dataforge
blueprint Temperatura:
    private _celsius: Float := 0.0

    // derivada: não há campo 'fahrenheit', ele é calculado
    get fahrenheit():
        yield self._celsius * 9 / 5 + 32

    set fahrenheit(valor):
        self._celsius := (valor - 32) * 5 / 9

    get celsius():
        yield self._celsius

    set celsius(valor):
        given valor smaller -273.15:
            trigger "abaixo do zero absoluto"
        self._celsius := valor

t := spawn Temperatura()
t.celsius := 100
assert t.fahrenheit is 212.0
t.fahrenheit := 32
assert t.celsius is 0.0

Ver propriedades para a sintaxe completa.

Imutabilidade#

A forma mais forte de encapsulamento: um valor que não muda não precisa de proteção. record é imutável, e alterar produz um valor novo:

dataforge
record Dinheiro:
    centavos: Integer
    moeda: String

    action somar(outro):
        given outro.moeda isnt self.moeda:
            trigger "moedas diferentes"
        yield Dinheiro(self.centavos + outro.centavos, self.moeda)

a := Dinheiro(1050, "BRL")
b := a.somar(Dinheiro(250, "BRL"))

assert a.centavos is 1050       // o original não mudou
assert b.centavos is 1300

Ocultação de informação#

O princípio geral, de David Parnas: um módulo deve esconder uma decisão de projeto. Se a decisão mudar, só ele muda.

dataforge
blueprint Cache:
    private itens: Vault := {}
    private ordem: Cluster := []
    private limite: Integer := 100

    action guardar(chave, valor):
        given not self.itens.has(chave):
            self.ordem.append(chave)
        self.itens[chave] := valor
        given len(self.ordem) bigger self.limite:
            velha := self.ordem.pop(0)
            delete self.itens[velha]
        yield valor

    action pegar(chave):
        yield self.itens.get(chave, void)

c := spawn Cache()
c.guardar("a", 1)
assert c.pegar("a") is 1
assert c.pegar("z") is void

A decisão escondida aqui é como o cache decide o que descartar. Trocar por LRU de verdade, ou por tempo de vida, não muda uma linha de quem usa.