Design de software

Padrão Factory Method em Swift: por que usar em vez de init

As funções factory do Swift dão nomes intencionais à criação, flexibilizando o tipo retornado e as políticas de reutilização. Veja quando escolher static func em vez de init e a diferença em relação ao Factory Method de GoF.

4 min de leitura
Imagem de capa de Padrão Factory Method em Swift: por que usar em vez de init

Ao criar apps com Swift, você escreve código de criação de objetos dezenas de vezes por dia.

Mas, em algum momento, usar apenas init começa a ficar limitante.

É comum começar usando sempre init e procurar o padrão Factory Method conforme o projeto cresce.

Neste artigo, explico o que é o padrão Factory Method em Swift e por que usar uma factory em vez de init, com código real.

Padrão Factory Method em uma frase

Vamos começar pela resposta principal.

O Factory Method extrai a lógica de criação para um método separado fora de init, permite nomeá-lo e oferece liberdade para escolher o tipo retornado.

Em Swift, geralmente estamos falando de um método factory estático criado com static func.

Aqui tratamos da factory estática, que encapsula a criação em um método de tipo. Ela difere do padrão GoF, em que a subclasse decide o tipo criado; comparei essa distinção em Diferença entre Factory Method e Abstract Factory.

init só funciona dentro das regras definidas pela linguagem.

Não é possível dar outro nome a ele, e sempre é preciso criar uma nova instância do próprio tipo.

Os métodos factory removem essas limitações.

Veja um exemplo simples.

struct Color {
    let r, g, b: Double
    // Expresse a intenção de criação no nome
    static func rgb(_ r: Double, _ g: Double, _ b: Double) -> Color {
        Color(r: r, g: g, b: b)
    }
    static func gray(_ v: Double) -> Color {
        Color(r: v, g: v, b: v)
    }
}

Ao chamar como Color.gray(0.5), fica claro, só pelo código, que isso cria a cor cinza.


Por que usar uma factory em vez de init?

Com base na minha experiência, cheguei a quatro motivos principais.

Primeiro, você pode dar um nome.

Como todos os init têm o mesmo nome, a distinção depende apenas dos parâmetros.

Se houver vários inicializadores que recebem dois Double iguais, é fácil se confundir.

Os métodos factory expressam a intenção no nome, como from(hex:) e rgb(_:_:_:).

Segundo, não é preciso criar uma nova instância toda vez.

init sempre retorna um novo objeto, mas uma factory pode devolver um valor em cache ou um singleton.

Terceiro, o tipo retornado pode ser escolhido com flexibilidade.

Dependendo da situação, é possível retornar um subtipo ou outra implementação.

Quarto, o tratamento de falhas pode ser expresso com mais clareza.

Adicionar uma linha com static func já deixa o código muito mais legível
Adicionar uma linha com static func já deixa o código muito mais legível

Abaixo, um exemplo de factory usando cache.

final class IconCache {
    private static var store: [String: Icon] = [:]
    // Reutiliza o ícone já criado quando o nome é igual
    static func icon(named name: String) -> Icon {
        if let cached = store[name] { return cached }
        let icon = Icon(name: name)
        store[name] = icon
        return icon
    }
}

Ao solicitar o mesmo ícone várias vezes, você economiza memória.

Se existir, retorna; caso contrário, cria e armazena
Se existir, retorna; caso contrário, cria e armazena

Então não precisamos mais usar init?

Não, de jeito nenhum.

A factory não substitui init; ela é uma ferramenta para encapsulá-lo.

Mesmo dentro do método factory, no fim das contas você chama init para criar o objeto.

Para tipos por valor simples ou lógica de criação óbvia, init é melhor.

Encapsular tudo em uma factory sem necessidade só aumenta o código e dificulta a leitura.

A factory brilha em situações específicas:

  • Quando há várias formas de criação e você quer diferenciá-las pelos nomes
  • Quando é necessário reutilizar, com cache ou pooling
  • Quando você quer esconder um processo complexo de inicialização em um único lugar
  • Quando quer ocultar a implementação retornando um tipo de protocolo

Se nenhuma dessas condições se aplicar, usar init é a escolha certa.

A biblioteca padrão do Swift também faz essa distinção.

Array(repeating:count:) é um init, mas propriedades estáticas como UIColor.systemBlue são, na prática, primas das factories.


Critérios práticos para decidir

Organizei em uma tabela os critérios que uso quando fico em dúvida.

Situação Recomendação
Armazenar valores simples init
Há várias formas de criação Método factory
Reutilização ou cache de instâncias Método factory
Ocultar a implementação e retornar um protocolo Método factory
Inicialização termina em uma linha init
Quando fico em dúvida, sempre lembro desta tabela para decidir
Quando fico em dúvida, sempre lembro desta tabela para decidir

Em resumo, init cuida de “como criar”, enquanto a factory cuida de “o que e por que criar”.

Eles não competem; são parceiros com papéis diferentes.

Recomendo começar com init e migrar para uma factory quando a lógica de criação ficar complexa.

Basta lembrar os critérios de hoje para deixar o código mais limpo. Boa programação em Swift!

Leituras recomendadas