Facade em Zig
O padrão Facade fornece uma interface simplificada para um subsistema complexo. Em vez de o código cliente interagir com múltiplos módulos e configurações, a fachada oferece uma API coesa e fácil de usar que orquestra os componentes internos.
Quando Usar
- Subsistema com muitas classes/structs que o cliente não precisa conhecer
- Simplificar inicialização complexa com múltiplas etapas
- Fornecer API de alto nível sobre biblioteca de baixo nível
- Isolar código cliente de mudanças internas no subsistema
Implementação: Facade para Subsistema de Áudio
const std = @import("std");
// --- Subsistemas complexos internos ---
const DecoderAudio = struct {
formato: []const u8,
sample_rate: u32,
pub fn init(formato: []const u8) DecoderAudio {
return .{ .formato = formato, .sample_rate = 44100 };
}
pub fn decodificar(self: *const DecoderAudio, dados: []const u8) []const u8 {
_ = self;
return dados; // simplificado
}
};
const MixerAudio = struct {
canais: u8,
volume: f32,
pub fn init(canais: u8) MixerAudio {
return .{ .canais = canais, .volume = 1.0 };
}
pub fn setVolume(self: *MixerAudio, vol: f32) void {
self.volume = std.math.clamp(vol, 0.0, 1.0);
}
pub fn mixar(self: *const MixerAudio, buffer: []const u8) []const u8 {
_ = self;
return buffer;
}
};
const SaidaAudio = struct {
dispositivo: []const u8,
ativo: bool,
pub fn init(dispositivo: []const u8) SaidaAudio {
return .{ .dispositivo = dispositivo, .ativo = false };
}
pub fn abrir(self: *SaidaAudio) !void {
self.ativo = true;
}
pub fn reproduzir(self: *const SaidaAudio, buffer: []const u8) !void {
if (!self.ativo) return error.DispositivoFechado;
_ = buffer;
}
pub fn fechar(self: *SaidaAudio) void {
self.ativo = false;
}
};
// --- Facade: interface simples ---
const AudioPlayer = struct {
decoder: DecoderAudio,
mixer: MixerAudio,
saida: SaidaAudio,
pub fn init() !AudioPlayer {
var player = AudioPlayer{
.decoder = DecoderAudio.init("mp3"),
.mixer = MixerAudio.init(2),
.saida = SaidaAudio.init("default"),
};
try player.saida.abrir();
return player;
}
pub fn deinit(self: *AudioPlayer) void {
self.saida.fechar();
}
// API simples — esconde toda a complexidade interna
pub fn tocar(self: *AudioPlayer, arquivo: []const u8) !void {
std.debug.print("Tocando: {s}\n", .{arquivo});
const decodificado = self.decoder.decodificar(arquivo);
const mixado = self.mixer.mixar(decodificado);
try self.saida.reproduzir(mixado);
}
pub fn setVolume(self: *AudioPlayer, volume: f32) void {
self.mixer.setVolume(volume);
}
};
pub fn main() !void {
// Cliente usa apenas a Facade — simples e limpo
var player = try AudioPlayer.init();
defer player.deinit();
player.setVolume(0.8);
try player.tocar("musica.mp3");
}
Facade para Inicialização de Aplicação
const std = @import("std");
const App = struct {
allocator: std.mem.Allocator,
// subsistemas internos...
const Opcoes = struct {
porta: u16 = 8080,
workers: u8 = 4,
log_level: []const u8 = "info",
};
// Facade: uma única chamada configura tudo
pub fn init(allocator: std.mem.Allocator, opcoes: Opcoes) !App {
std.debug.print("Inicializando app na porta {d}...\n", .{opcoes.porta});
std.debug.print("Workers: {d}, Log: {s}\n", .{ opcoes.workers, opcoes.log_level });
return .{ .allocator = allocator };
}
pub fn deinit(self: *App) void {
_ = self;
std.debug.print("App finalizada\n", .{});
}
pub fn executar(self: *App) !void {
_ = self;
std.debug.print("App em execução...\n", .{});
}
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var app = try App.init(gpa.allocator(), .{
.porta = 3000,
.log_level = "debug",
});
defer app.deinit();
try app.executar();
}
Facade para Subsistema de Banco de Dados
Outro uso clássico é encapsular a complexidade de inicialização e gerenciamento de conexões com um banco de dados:
const std = @import("std");
const Database = struct {
allocator: std.mem.Allocator,
pool_size: u8,
conectado: bool = false,
// Facade: esconde pool de conexões, retry, TLS
pub fn conectar(allocator: std.mem.Allocator, url: []const u8) !Database {
_ = url; // em produção: parsear URL, inicializar TLS, etc.
std.debug.print("Conectando ao banco de dados...\n", .{});
return .{
.allocator = allocator,
.pool_size = 10,
.conectado = true,
};
}
pub fn desconectar(self: *Database) void {
self.conectado = false;
std.debug.print("Desconectado.\n", .{});
}
pub fn executar(self: *Database, sql: []const u8) !void {
if (!self.conectado) return error.NaoConectado;
std.debug.print("SQL: {s}\n", .{sql});
}
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var db = try Database.conectar(gpa.allocator(), "postgres://localhost/mydb");
defer db.desconectar();
try db.executar("SELECT * FROM usuarios WHERE ativo = true");
}
Considerações de Performance
- Inicialização lazy vs eager: a Facade pode inicializar subsistemas de forma preguiçosa (somente quando necessário) ou antecipada. Para subsistemas de alto custo de inicialização (conexões de rede, carregamento de arquivos grandes), inicialização lazy melhora o tempo de startup.
- Facade não adiciona overhead de runtime: em Zig, uma Facade é uma struct comum com métodos normais. Não há vtable, não há indireção — o compilador pode inlinar chamadas a subsistemas internos se os tipos forem concretos.
- Evite armazenar estado desnecessário na Facade: a Facade deve delegar para os subsistemas, não duplicar o estado deles. Armazenar uma cópia do volume no
AudioPlayere também noMixerAudiocria inconsistências.
Erros Comuns
Facade que expõe todos os métodos dos subsistemas: se a Facade tem 50 métodos porque cada subsistema tem 10, ela não está simplificando nada — está apenas adicionando uma camada. A Facade deve expor apenas os casos de uso mais comuns.
Não fechar/liberar subsistemas no deinit: a Facade encapsula a inicialização, portanto deve encapsular também a finalização. Implemente deinit que chama fechar, destruir e liberar em cada subsistema, na ordem correta (inversa da inicialização).
Depender da Facade dentro dos próprios subsistemas: os subsistemas internos não devem importar ou chamar a Facade — isso cria dependência circular. A Facade conhece os subsistemas, mas os subsistemas não conhecem a Facade.
Perguntas Frequentes
Facade é o mesmo que um módulo?
Conceitualmente, sim — um módulo bem projetado com uma API pública limitada é uma Facade. Em Zig, um arquivo .zig com funções e structs públicas bem selecionadas é a implementação natural do padrão Facade.
Posso ter múltiplas Facades para o mesmo subsistema? Sim, e é uma boa prática. Uma Facade para uso normal, uma simplificada para testes, e uma com mais controle para administração do sistema. Cada uma expõe apenas o que o cliente específico precisa.
Quando Evitar
- Sistemas simples que não precisam de abstração adicional
- Quando a facade esconde funcionalidade que o cliente precisa
- Se a facade se torna um “God object” com responsabilidades demais
Veja Também
- Adapter — Converter entre interfaces
- Builder — Construção passo a passo
- Decorator — Adicionar comportamento incremental
- Receitas — Exemplos de APIs simplificadas
- FAQ Produção — Arquitetura de aplicações