Design de software

Padrão Monostate em Swift: ele pode ser uma alternativa ao Singleton?

Para quem já se perguntou se está mesmo tudo bem usar Singleton desse jeito

4 min de leitura
Imagem de capa de Padrão Monostate em Swift: ele pode ser uma alternativa ao Singleton?

Para quem já se perguntou se está mesmo tudo bem usar Singleton desse jeito

Se você desenvolve para iOS, provavelmente nunca passou sem usar Singleton. Eu também não. SomeManager.shared Esse tipo de código aparecia várias vezes em praticamente todo projeto.

Mas, em algum momento, esse .shared começou a ficar desconfortável. O estado se misturava durante os testes, e os membros da equipe acessavam tudo livremente de vários pontos.

Foi então que conheci o padrão Monostate. Hoje vou compartilhar minha experiência usando Monostate no Swift e contar se ele pode realmente ser uma alternativa ao Singleton.

Indo direto à conclusão: Monostate é um padrão que permite compartilhar um único estado e criar várias instâncias. O objetivo é o mesmo do Singleton, mas a abordagem é exatamente oposta. Ele não resolve tudo; a escolha depende do contexto. Vamos detalhar isso a seguir.

Afinal, o que é o padrão Monostate?

O ponto central do Singleton é criar exatamente uma instância. O inicializador é bloqueado, e apenas shared é usado.

Monostate é o oposto. Você pode criar quantas instâncias quiser. Em compensação, todo o estado (dados) interno é compartilhado por meio de propriedades static.

Em outras palavras, tanto o objeto A quanto o B consultam os mesmos dados. Por fora parecem objetos comuns, mas por dentro estão conectados ao mesmo estado.

Em Swift, fica mais ou menos assim.

struct Settings {
    private static var _volume = 50   // compartilhar o estado static
    var volume: Int {                 // usar como uma instância
        get { Settings._volume }
        set { Settings._volume = newValue }
    }
}
// instâncias diferentes, mas um único estado
var a = Settings(); a.volume = 80
print(Settings().volume)  // 80

Mesmo criando um novo Settings(), volume continua 80. São várias instâncias, mas um único estado — isso é Monostate em poucas palavras.

São três instâncias, mas todas observam um único estado
São três instâncias, mas todas observam um único estado

Qual é a diferença em relação ao Singleton?

A maior diferença está no código que o utiliza.

Com Singleton, o código cliente precisa estar sempre ciente de .shared: “isso é um Singleton”. Com Monostate, basta criar Settings() e usá-lo como um objeto comum, sem conhecer a implementação interna.

A comparação fica assim.

Categoria Singleton Monostate
Quantidade de instâncias Exatamente 1 Várias permitidas
O que é compartilhado A própria instância O estado (dados static)
Código cliente Especifica .shared explicitamente Como um objeto comum
Herança Complicada Relativamente flexível

A herança é especialmente interessante. Herdar de Singleton é bastante complicado, enquanto Monostate é um tipo comum e, por isso, oferece mais flexibilidade para extensões.


Então ele pode ser uma alternativa ao Singleton?

Sendo sincero, às vezes sim e às vezes não.

Começando pelas vantagens: o código cliente fica mais limpo, e .shared não aparece espalhado pelo código como um ponto de acesso global. Como a interface é igual à de um objeto comum, mudar a estrutura depois também dá menos trabalho.

Monostate é um padrão que “esconde o estado global”. Parece conveniente, mas o estado global oculto é uma faca de dois gumes.

O problema é este: como parece um objeto comum, alguém da equipe pode criá-lo pensando “isso é só um objeto local”, mas descobrir depois que o estado é compartilhado globalmente. Isso pode ser ainda mais confuso.

Além disso, como o estado é compartilhado por static, você continua tendo que se preocupar com problemas de concorrência em ambientes multithread. Nesse aspecto, não há diferença em relação ao Singleton.

Pela minha experiência, no Swift atual, recomendo considerar primeiro a injeção de dependência em vez de Monostate. Os testes ficam mais fáceis e as dependências mais claras.

Em projetos Swift atuais, eu começo pela DI
Em projetos Swift atuais, eu começo pela DI

Então, quando vale a pena usar?

Resumindo, nestes casos.

  1. Quando há código existente demais baseado em Singleton e você quer removê-lo gradualmente
  2. Quando você quer manter a interface do código cliente limpa, como a de um objeto comum
  3. Quando precisa lidar com estado global que exige herança ou extensão

Por outro lado, se você está começando um projeto novo do zero, eu recomendaria considerar primeiro a injeção de dependência ou o gerenciamento explícito de estado.

Guardo os padrões na minha caixa de ferramentas e os uso quando a situação pede
Guardo os padrões na minha caixa de ferramentas e os uso quando a situação pede

No fim, Monostate não é “um superconjunto perfeito do Singleton”, mas “mais uma opção, com características diferentes”. Deixe-o na caixa de ferramentas e use-o quando fizer sentido.

Espero que o conteúdo de hoje seja uma pequena dica para quem está cansado do uso excessivo de .shared. Em vez de mudar sem pensar, reflita sobre o que faz mais sentido para o seu projeto.

Continue lendo