Cheatsheet: Observer em Zig

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

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.