Design de software

Fábricas estáticas em Swift: por que usar make em vez de init

Os métodos de fábrica estáticos do Swift expressam a intenção de criação pelos nomes e controlam tipo de retorno, cache e reutilização de instâncias. Este resumo aborda as vantagens e desvantagens de escolher static func make em vez de init e suas diferenças em relação ao padrão GoF.

4 min de leitura
Imagem de capa de Fábricas estáticas em Swift: por que usar make em vez de init

Ao desenvolver para iOS, talvez você já tenha encontrado init(...) em vez de static func make(...) no código de outra pessoa e ficado em dúvida.

Vamos começar pela conclusão.

Os métodos de fábrica estáticos do Swift nomeiam o processo de criação, permitem tipos de retorno flexíveis e controlam a reutilização de objetos — uma opção quando usar apenas init é limitante.

Hoje vou explicar, sob a perspectiva prática, por que usar static func make em vez de init.

A fábrica estática deste artigo só se parece no nome com o Factory Method Pattern do GoF, no qual subclasses decidem a criação. Mais exemplos de Swift estão em Como separar a lógica de criação com funções de fábrica, e os limites do padrão GoF são discutidos em Factory Method vs. Abstract Factory.


O que exatamente é um método de fábrica estático?

O nome parece grandioso, mas o conceito é simples.

É um método static que cria e retorna um objeto. Em vez de chamar o construtor diretamente, você coloca a criação dentro de uma função.

Ele se parece com isto.

struct Button {
    let title: String
    let style: Style

    // init envolvido por um método de fábrica estático
    static func makePrimary(title: String) -> Button {
        Button(title: title, style: .primary)
    }
}

A chamada fica assim: Button.makePrimary(title: "확인").

O resultado é igual ao de chamar Button(title:style:) diretamente, mas o nome deixa claro “qual botão está sendo criado”.

Essa pequena diferença tem bastante impacto no código de produção.


4 motivos para usar make em vez de init

Estes são os benefícios que percebi em projetos reais.

1. Expresse a intenção pelo nome

Todos os construtores têm o mesmo nome: init. Eles só se diferenciam pelas combinações de parâmetros.

Por isso, vários init semelhantes confundem. Nomes como make(fromJSON:) e make(withDefaults:) deixam claro o que está sendo criado.

2. Você não precisa criar um objeto novo toda vez

init sempre cria uma nova instância quando é chamado.

Já um método de fábrica pode retornar um objeto em cache ou um singleton existente. Para valores fixos, como verdadeiro e falso em Bool, reutilizar é muito mais eficiente.

3. O tipo de retorno pode ser flexível

Pessoalmente, considero este o benefício mais poderoso.

Um método de fábrica pode retornar um subtipo do tipo declarado ou uma implementação de protocolo. Quem chama não precisa conhecer o tipo concreto.

protocol Shape { func area() -> Double }

enum ShapeFactory {
    // retornar implementações diferentes conforme a condição
    static func make(sides: Int) -> Shape {
        sides == 4 ? Square() : Triangle()
    }
}

make(sides:) tem apenas Shape como tipo de retorno, mas na prática retorna a implementação adequada.

Parece um único Shape, mas contém um tipo diferente conforme a situação
Parece um único Shape, mas contém um tipo diferente conforme a situação

4. Falhas podem ser tratadas com suavidade

Você poderia usar init? ou throws, mas uma fábrica facilita retornar um opcional ou envolver o resultado em Result. Quando a criação é complexa, o fluxo fica mais limpo.

Bastou uma linha com make para a chamada ficar muito mais clara
Bastou uma linha com make para a chamada ficar muito mais clara

O que muda em relação a init? Comparação rápida

Para evitar confusão, organizei tudo em uma tabela.

Categoria init (construtor) static func make (fábrica estática)
Nome Sempre init Escolha livre
Nova instância Criação obrigatória sempre Reutilização e cache possíveis
Tipo de retorno Somente o próprio tipo Subtipo ou protocolo possível
Tratamento de falhas init? / throws Opcional, Result etc. com flexibilidade
Desvantagem Nomes não diferenciam os casos Restrições de subclassificação; menor descobribilidade

Também vale falar com franqueza sobre as desvantagens.

Se houver apenas um método de fábrica e nenhum init público, fica difícil estender o tipo por herança.

Construtores aparecem direto no autocompletar, mas make é mais fácil de encontrar quando você conhece o nome, então a descobribilidade é menor. Recomendo nomes convencionais como make, create e from.


Então, quando usar?

Na prática, eu decido assim.

  1. Só precisa preencher propriedades armazenadas → use init
  2. Há vários caminhos de criação e você quer diferenciá-los por nome → make
  3. Precisa retornar tipos diferentes conforme a condição → make
  4. Quer reutilizar ou armazenar objetos em cache → make
Uso esta tabela para decidir entre os dois
Uso esta tabela para decidir entre os dois

Em resumo, as fábricas brilham quando criar deixa de ser apenas “preencher valores” e vira um processo de decisão.

Por outro lado, envolver toda criação em make deixa o código prolixo. O importante é usar apenas quando necessário.

No começo pode parecer estranho, mas, quando a intenção fica clara, ler o código muda. Na próxima situação parecida, experimente make; ele certamente ajudará 🙂

Leituras recomendadas