Cheatsheet: Facade em Zig

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 AudioPlayer e também no MixerAudio cria 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

Continue aprendendo Zig

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