Decorator em Zig
O padrão Decorator permite adicionar comportamento a objetos individualmente sem afetar outros objetos da mesma classe. Em Zig, este padrão é naturalmente implementado com composição e wrapping, especialmente visível no design de readers/writers da biblioteca padrão que são compostos em camadas.
Quando Usar
- Adicionar logging, métricas ou caching a operações existentes
- Compor camadas de I/O (buffering, compressão, criptografia)
- Middleware em servidores web
- Adicionar validação ou transformação sem alterar o código original
Writer Decorado
const std = @import("std");
fn LoggingWriter(comptime WriterType: type) type {
return struct {
const Self = @This();
writer_interno: WriterType,
bytes_escritos: *usize,
pub const Error = WriterType.Error;
pub const Writer = std.io.Writer(*Self, Error, write);
pub fn writer(self: *Self) Writer {
return .{ .context = self };
}
fn write(self: *Self, bytes: []const u8) Error!usize {
const n = try self.writer_interno.write(bytes);
self.bytes_escritos.* += n;
return n;
}
};
}
fn loggingWriter(inner_writer: anytype, bytes_counter: *usize) LoggingWriter(@TypeOf(inner_writer)) {
return .{
.writer_interno = inner_writer,
.bytes_escritos = bytes_counter,
};
}
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
var bytes_total: usize = 0;
var logging = loggingWriter(stdout, &bytes_total);
const writer = logging.writer();
try writer.writeAll("Olá, mundo!\n");
try writer.print("Número: {d}\n", .{42});
// O decorator contou os bytes sem alterar o comportamento do writer
std.debug.print("Total de bytes escritos: {d}\n", .{bytes_total});
}
Decorator de Função
const std = @import("std");
fn comTiming(comptime func: anytype) @TypeOf(func) {
return struct {
fn wrapper(args: anytype) @typeInfo(@TypeOf(func)).@"fn".return_type.? {
var timer = std.time.Timer.start() catch unreachable;
const resultado = @call(.auto, func, args);
const elapsed = timer.read();
std.debug.print("[TIMING] {d}ms\n", .{elapsed / std.time.ns_per_ms});
return resultado;
}
}.wrapper;
}
fn operacaoLenta() void {
std.time.sleep(50 * std.time.ns_per_ms);
}
// const operacaoComTiming = comTiming(operacaoLenta);
Middleware Pattern
const std = @import("std");
const Requisicao = struct {
path: []const u8,
metodo: []const u8,
headers: std.StringHashMap([]const u8),
};
const Resposta = struct {
status: u16 = 200,
corpo: []const u8 = "",
};
const Handler = *const fn (*Requisicao) Resposta;
const Middleware = *const fn (*Requisicao, Handler) Resposta;
fn comLog(req: *Requisicao, proximo: Handler) Resposta {
std.debug.print("[LOG] {s} {s}\n", .{ req.metodo, req.path });
const resp = proximo(req);
std.debug.print("[LOG] Status: {d}\n", .{resp.status});
return resp;
}
fn comAuth(req: *Requisicao, proximo: Handler) Resposta {
if (!req.headers.contains("Authorization")) {
return .{ .status = 401, .corpo = "Não autorizado" };
}
return proximo(req);
}
fn handlerFinal(req: *Requisicao) Resposta {
_ = req;
return .{ .status = 200, .corpo = "OK" };
}
Quando Evitar
- Quando apenas uma variante é necessária (composição direta é mais simples)
- Muitas camadas de decoração que dificultam depuração
- Quando performance é crítica e cada camada adiciona overhead
Veja Também
- Adapter — Adaptar interfaces incompatíveis
- Facade — Simplificar interface complexa
- Pipeline — Processamento em estágios
- Strategy — Trocar algoritmo inteiro
- Operações I/O — Writers/Readers composíveis