Strategy em Zig
O padrão Strategy define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis. Em Zig, existem três formas principais de implementar: comptime (zero custo em runtime), ponteiros de função (flexível em runtime) e tagged unions (type-safe com exaustividade verificada pelo compilador).
Quando Usar
- Diferentes algoritmos de ordenação, compressão, criptografia
- Estratégias de retry, cache ou roteamento
- Formatação de saída (JSON, CSV, XML)
- Validação com regras configuráveis
Strategy com comptime (Custo Zero)
const std = @import("std");
fn Compressor(comptime estrategia: enum { gzip, lz4, nenhuma }) type {
return struct {
pub fn comprimir(dados: []const u8) []const u8 {
return switch (estrategia) {
.gzip => {
// lógica de compressão gzip
_ = dados;
return "dados_gzip";
},
.lz4 => {
_ = dados;
return "dados_lz4";
},
.nenhuma => dados,
};
}
};
}
// Tipo resolvido em compilação — zero overhead
const CompressorGzip = Compressor(.gzip);
const CompressorLz4 = Compressor(.lz4);
Strategy com Ponteiros de Função
const std = @import("std");
const EstrategiaOrdenacao = *const fn ([]i32) void;
fn bubbleSort(dados: []i32) void {
for (0..dados.len) |_| {
for (0..dados.len - 1) |j| {
if (dados[j] > dados[j + 1]) {
const temp = dados[j];
dados[j] = dados[j + 1];
dados[j + 1] = temp;
}
}
}
}
fn insertionSort(dados: []i32) void {
for (1..dados.len) |i| {
const chave = dados[i];
var j: usize = i;
while (j > 0 and dados[j - 1] > chave) {
dados[j] = dados[j - 1];
j -= 1;
}
dados[j] = chave;
}
}
const Ordenador = struct {
estrategia: EstrategiaOrdenacao,
pub fn ordenar(self: *const Ordenador, dados: []i32) void {
self.estrategia(dados);
}
pub fn setEstrategia(self: *Ordenador, nova: EstrategiaOrdenacao) void {
self.estrategia = nova;
}
};
pub fn main() void {
var dados = [_]i32{ 5, 2, 8, 1, 9, 3 };
var ordenador = Ordenador{ .estrategia = bubbleSort };
ordenador.ordenar(&dados);
// Trocar estratégia em runtime
ordenador.setEstrategia(insertionSort);
}
Strategy com Tagged Union
const std = @import("std");
const FormatoSaida = union(enum) {
json,
csv: struct { separador: u8 = ',' },
texto: struct { largura: u16 = 80 },
pub fn formatar(self: FormatoSaida, dados: anytype, writer: anytype) !void {
switch (self) {
.json => {
try std.json.stringify(dados, .{}, writer);
try writer.writeAll("\n");
},
.csv => |opts| {
_ = opts;
// formatação CSV...
try writer.writeAll("dados,csv\n");
},
.texto => |opts| {
_ = opts;
try writer.writeAll("Saída texto\n");
},
}
}
};
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
const formato = FormatoSaida.json;
try formato.formatar(.{ .nome = "Zig" }, stdout);
}
Quando Evitar
- Quando só existe uma estratégia (e não se planeja extensão)
- Se comptime resolve o problema sem necessidade de troca em runtime
- Poucas variantes simples — um
switchdireto pode ser mais claro
Veja Também
- Factory — Criar a estratégia certa baseada em config
- Observer — Notificar sobre mudança de estratégia
- Type Erasure — Interfaces genéricas em runtime
- Comptime — Strategy resolvido na compilação
- Enums e Unions — Tagged unions para estratégias