Pular para o conteúdo

Padrões de projeto

Os 23 padrões clássicos em DataForge: os que são a própria linguagem, e os que pedem mecanismo — Arcane.Padroes.

A maior parte do catálogo clássico já é a linguagem, e escrever biblioteca para esses seria cerimônia. O módulo Arcane.Padroes existe para os que pedem estado ou controle que ninguém deveria reescrever a cada projeto.

PadrãoEm DataForge
Template Methodabstract blueprint com abstract action
Strategycontract + implementações; Padroes.estrategias() quando a escolha vem de configuração
Decoratormark @decorador
Iteratorstream action + emit, ou __iter__
Factory Method / Static Factorystatic action criar(…); overload action setup para construtores nomeados
Abstract Factoryum contract de fábrica, injetado
SingletonPadroes.unico(fabrica), ou c.unico(Tipo) no contêiner
Object PoolPadroes.pool(fabrica, tamanho)
BuilderPadroes.construtor(Tipo, obrigatorios)
PrototypePadroes.prototipos(), com clonar_fundo
FlyweightPadroes.compartilhado(fabrica)
Adapterblueprint que embrulha, ou Padroes.adaptar(obj, mapa)
ProxyPadroes.proxy(alvo, interceptar), ou on_call numa metaclasse
CompositePadroes.composto()
Facade / Bridgeblueprint comum, com o implementador num campo tipado por contrato
CommandPadroes.comandos() — executar, desfazer, refazer
Chain of ResponsibilityPadroes.cadeia([manipuladores])
ObserverPadroes.observavel() — prioridade, filtro e 'parar'; Arcane.Eventos entre módulos
MediatorPadroes.mediador()
MementoPadroes.memento(obj) / Padroes.restaurar(obj, m)
StatePadroes.maquina(inicial, transicoes)
VisitorPadroes.visitar(obj, visitante)visitar_<Tipo> subindo a MRO
SpecificationPadroes.especificacao(predicado) com e, ou, nao

Comandos com desfazer#

dataforge
adopt Arcane.Padroes as P

blueprint Texto:
    conteudo := ""

blueprint Escrever(doc, trecho):
    action executar():
        self.doc.conteudo := self.doc.conteudo + self.trecho
    action desfazer():
        tamanho := len(self.doc.conteudo) - len(self.trecho)
        self.doc.conteudo := self.doc.conteudo.substring(0, tamanho)

doc := spawn Texto()
historico := P.comandos()
historico.executar(spawn Escrever(doc, "olá"))
historico.executar(spawn Escrever(doc, " mundo"))
historico.desfazer()
assert doc.conteudo is "olá"
historico.refazer()
assert doc.conteudo is "olá mundo"

Máquina de estados#

dataforge
adopt Arcane.Padroes as P

pedido := P.maquina("rascunho", {
    "enviar":   {"de": ["rascunho"], "para": "enviado"},
    "aprovar":  {"de": ["enviado"], "para": "aprovado"},
    "cancelar": {"de": ["rascunho", "enviado"], "para": "cancelado"},
})
pedido.ir("enviar")
assert pedido.eventos() is ["aprovar", "cancelar"]
monitor:
    pedido.ir("enviar")
    assert no
handle StateError as e:
    out e.message

Especificação e repositório#

dataforge
adopt Arcane.Padroes as P

record Produto:
    id: Integer
    preco: Float
    ativo: Boolean

repo := P.repositorio()
repo.salvar(Produto(1, 10.0, yes))
repo.salvar(Produto(2, 99.0, yes))
repo.salvar(Produto(3, 150.0, no))

caro := P.especificacao(lambda p => p.preco bigger 50, "caro")
ativo := P.especificacao(lambda p => p.ativo, "ativo")
assert len(repo.filtrar(caro.e(ativo))) is 1

Arquitetura: portas e adaptadores#

Hexagonal, Clean e Onion dizem a mesma coisa com desenhos diferentes: o domínio não conhece banco, HTTP nem fila. Em DataForge, a porta é um contract, o adaptador é um blueprint with ele, e o contêiner liga os dois na borda do programa.

dataforge
adopt Arcane.Injecao as DI
adopt Arcane.Padroes as P

// ── domínio: não adota nada de fora ──
contract Pagamentos:
    action cobrar(valor: Float) -> Boolean

blueprint Checkout(pagamentos: Pagamentos):
    action finalizar(total: Float):
        expects total bigger 0
        yield "pago" given self.pagamentos.cobrar(total) otherwise "recusado"

// ── adaptadores: a borda ──
blueprint PagamentoFalso with Pagamentos:
    action cobrar(valor: Float) -> Boolean:
        yield valor smaller 1000

// ── composição: um lugar só ──
c := DI.conteiner()
c.unico(Pagamentos, PagamentoFalso)
checkout := c.resolver(Checkout)
assert checkout.finalizar(50.0) is "pago"
assert checkout.finalizar(5000.0) is "recusado"