É importante distinguir aqui entre instâncias únicas e o padrão de design Singleton .
Instâncias únicas são simplesmente uma realidade. A maioria dos aplicativos é projetada para funcionar apenas com uma configuração por vez, uma interface do usuário por vez, um sistema de arquivos por vez e assim por diante. Se houver muitos estados ou dados a serem mantidos, certamente você desejaria ter apenas uma instância e mantê-los ativos o maior tempo possível.
O padrão de design Singleton é um tipo muito específico de instância única, especificamente uma que é:
- Acessível através de um campo de instância estático global;
- Criado na inicialização do programa ou no primeiro acesso;
- Nenhum construtor público (não pode instanciar diretamente);
- Nunca liberado explicitamente (liberado implicitamente na finalização do programa).
É por causa dessa escolha de design específico que o padrão apresenta vários problemas em potencial a longo prazo:
- Incapacidade de usar classes abstratas ou de interface;
- Incapacidade de subclasse;
- Alto acoplamento em todo o aplicativo (difícil de modificar);
- Difícil de testar (não pode falsificar / zombar em testes de unidade);
- Difícil de paralelizar no caso de estado mutável (requer travamento extenso);
- e assim por diante.
Na verdade, nenhum desses sintomas é endêmico para instâncias únicas, apenas o padrão Singleton.
O que você pode fazer? Simplesmente não use o padrão Singleton.
Citando a pergunta:
A idéia era ter esse lugar no aplicativo que mantém os dados armazenados e sincronizados e, em seguida, qualquer nova tela aberta pode apenas consultar a maior parte do que precisa a partir daí, sem fazer pedidos repetidos de vários dados de suporte do servidor. Solicitar constantemente ao servidor exigiria muita largura de banda - e eu estou falando milhares de dólares em contas extras da Internet por semana, o que era inaceitável.
Esse conceito tem um nome, como você sugere, mas parece incerto. É chamado de cache . Se você quiser se divertir, pode chamá-lo de "cache offline" ou apenas uma cópia offline de dados remotos.
Um cache não precisa ser um singleton. Pode ser necessário que seja uma única instância se você deseja evitar a busca dos mesmos dados para várias instâncias de cache; mas isso não significa que você realmente exponha tudo a todos .
A primeira coisa que eu faria é separar as diferentes áreas funcionais do cache em interfaces separadas. Por exemplo, digamos que você estivesse criando o pior clone do YouTube do mundo com base no Microsoft Access:
MSAccessCache
▲
|
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
Aqui você tem várias interfaces que descrevem os tipos específicos de dados aos quais uma determinada classe pode precisar acessar - mídia, perfis de usuário e páginas estáticas (como a primeira página). Tudo isso é implementado por um mega cache, mas você projeta suas classes individuais para aceitar as interfaces, para que elas não se importem com o tipo de instância que possuem. Você inicializa a instância física uma vez, quando o programa é iniciado e, em seguida, começa a passar pelas instâncias (convertidas para um tipo de interface específico) por meio de construtores e propriedades públicas.
Isso é chamado de injeção de dependência , a propósito; você não precisa usar o Spring ou qualquer contêiner IoC especial, desde que o design de sua classe geral aceite suas dependências do chamador, em vez de instanciá-las por conta própria ou fazer referência ao estado global .
Por que você deve usar o design baseado em interface? Três razões:
Facilita a leitura do código; você pode entender claramente pelas interfaces exatamente de quais dados as classes dependentes dependem.
Se e quando você perceber que o Microsoft Access não era a melhor opção para um back-end de dados, poderá substituí-lo por algo melhor - digamos, SQL Server.
Se e quando você perceber que o SQL Server não é a melhor opção especificamente para a mídia , poderá interromper sua implementação sem afetar nenhuma outra parte do sistema . É aí que entra o verdadeiro poder da abstração.
Se você quiser dar um passo adiante, use um contêiner IoC (estrutura DI) como Spring (Java) ou Unity (.NET). Quase toda estrutura de DI fará seu próprio gerenciamento vitalício e permitirá especificamente que você defina um serviço específico como uma instância única (geralmente denominando "singleton", mas isso é apenas para familiaridade). Basicamente, essas estruturas economizam a maior parte do trabalho do macaco de passar manualmente as instâncias, mas elas não são estritamente necessárias. Você não precisa de nenhuma ferramenta especial para implementar esse design.
Por uma questão de integridade, devo salientar que o design acima também não é ideal. Ao lidar com um cache (como você é), você deve realmente ter uma camada totalmente separada . Em outras palavras, um design como este:
+ - IMediaRepository
|
Cache (Genérico) --------------- + - IProfileRepository
▲
| + - IPageRepository
+ ----------------- + ----------------- +
| | |
IMediaCache IProfileCache IPageCache
| | |
| | |
VideoPage MyAccountPage MostPopularPage
O benefício disso é que você nunca precisará interromper sua Cache
instância se decidir refatorar; você pode alterar como a Mídia é armazenada simplesmente alimentando uma implementação alternativa de IMediaRepository
. Se você pensar em como isso se encaixa, verá que ele ainda cria apenas uma instância física de um cache, para que você nunca precise buscar os mesmos dados duas vezes.
Nada disso significa dizer que todo software do mundo precisa ser arquitetado para esses padrões exigentes de alta coesão e acoplamento flexível; depende do tamanho e escopo do projeto, sua equipe, seu orçamento, prazos etc. Mas se você está perguntando qual é o melhor design (para usar no lugar de um singleton), então é isso.
PS Como outros já declararam, provavelmente não é a melhor idéia para as classes dependentes estarem cientes de que estão usando um cache - que é um detalhe de implementação com o qual simplesmente nunca deveriam se preocupar. Dito isto, a arquitetura geral ainda seria muito parecida com a mostrada acima, você simplesmente não se referiria às interfaces individuais como Caches . Em vez disso, você os chamaria Serviços ou algo semelhante.