Observer em Zig
O padrão Observer define uma dependência um-para-muitos entre objetos, de modo que quando um objeto (o sujeito) muda de estado, todos os seus dependentes (observadores) são notificados automaticamente. Em Zig, esse padrão é implementado de forma leve usando slices de ponteiros de função ou interfaces via comptime.
Quando Usar
- Sistemas de eventos e callbacks
- Notificação de mudanças de estado (UI, dados, config)
- Logging e monitoramento desacoplados
- Comunicação entre módulos sem acoplamento direto
Implementação com Ponteiros de Função
const std = @import("std");
fn EventEmitter(comptime EventType: type) type {
return struct {
const Self = @This();
const Callback = *const fn (EventType) void;
listeners: std.ArrayList(Callback),
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.listeners = std.ArrayList(Callback).init(allocator),
};
}
pub fn deinit(self: *Self) void {
self.listeners.deinit();
}
pub fn on(self: *Self, callback: Callback) !void {
try self.listeners.append(callback);
}
pub fn off(self: *Self, callback: Callback) void {
for (self.listeners.items, 0..) |cb, i| {
if (cb == callback) {
_ = self.listeners.orderedRemove(i);
return;
}
}
}
pub fn emit(self: *const Self, evento: EventType) void {
for (self.listeners.items) |callback| {
callback(evento);
}
}
};
}
// Eventos do sistema
const Evento = union(enum) {
usuario_logou: []const u8,
usuario_saiu: []const u8,
erro: []const u8,
};
fn logHandler(evento: Evento) void {
switch (evento) {
.usuario_logou => |nome| std.debug.print("[LOG] {s} entrou\n", .{nome}),
.usuario_saiu => |nome| std.debug.print("[LOG] {s} saiu\n", .{nome}),
.erro => |msg| std.debug.print("[ERRO] {s}\n", .{msg}),
}
}
fn metricasHandler(evento: Evento) void {
switch (evento) {
.usuario_logou => std.debug.print("[METRICS] +1 usuário online\n", .{}),
.usuario_saiu => std.debug.print("[METRICS] -1 usuário online\n", .{}),
.erro => std.debug.print("[METRICS] +1 erro registrado\n", .{}),
}
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var emitter = EventEmitter(Evento).init(gpa.allocator());
defer emitter.deinit();
try emitter.on(logHandler);
try emitter.on(metricasHandler);
emitter.emit(.{ .usuario_logou = "Ana" });
emitter.emit(.{ .usuario_saiu = "Carlos" });
emitter.emit(.{ .erro = "Conexão perdida" });
}
Observer com Contexto
const std = @import("std");
const Observador = struct {
ptr: *anyopaque,
notificarFn: *const fn (*anyopaque, []const u8) void,
pub fn notificar(self: Observador, mensagem: []const u8) void {
self.notificarFn(self.ptr, mensagem);
}
};
const Sujeito = struct {
observadores: std.ArrayList(Observador),
estado: []const u8,
pub fn init(allocator: std.mem.Allocator) Sujeito {
return .{
.observadores = std.ArrayList(Observador).init(allocator),
.estado = "",
};
}
pub fn deinit(self: *Sujeito) void {
self.observadores.deinit();
}
pub fn registrar(self: *Sujeito, obs: Observador) !void {
try self.observadores.append(obs);
}
pub fn setEstado(self: *Sujeito, novo_estado: []const u8) void {
self.estado = novo_estado;
self.notificarTodos();
}
fn notificarTodos(self: *const Sujeito) void {
for (self.observadores.items) |obs| {
obs.notificar(self.estado);
}
}
};
Quando Evitar
- Quando há apenas um listener (callback direto é mais simples)
- Em código que precisa de performance extrema no hot path
- Quando a ordem de notificação importa e é difícil de controlar
- Riscos de referência circular entre observer e subject
Veja Também
- Strategy — Trocar comportamento em runtime
- Command — Encapsular ações como objetos
- Producer-Consumer — Comunicação assíncrona
- Concorrência — Observer thread-safe
- Receitas — Exemplos práticos de eventos