Padrão Singleton
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 91 de 104.
O padrão Singleton garante que uma classe tenha apenas uma instância em todo o programa e fornece um ponto de acesso global a ela. Isso é útil para recursos que devem ser compartilhados, como um gerenciador de configurações, logger ou pool de conexões de banco de dados.
A ideia principal é tornar o construtor privado para que ninguém possa criar instâncias diretamente, e então fornecer um método estático que retorne a instância única:
#include <iostream>
#include <string>
class Logger {
private:
std::string logFile;
// Construtor privado - evita a instanciação direta
Logger() : logFile("app.log") {}
// Deleta o construtor de cópia e o operador de atribuição
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
public:
// Método estático para acessar a instância única
static Logger& getInstance() {
static Logger instance; // Criada uma vez, vive até o fim do programa
return instance;
}
void log(const std::string& message) {
std::cout << "[LOG] " << message << "\n";
}
};
int main() {
Logger::getInstance().log("Application started");
Logger& logger = Logger::getInstance();
logger.log("Processing data");
}A static Logger instance dentro de getInstance() é um recurso do C++11 chamado "magic statics" - é thread-safe e tem a garantia de ser inicializado apenas uma vez. Ao deletar o construtor de cópia e o operador de atribuição, impedimos que qualquer pessoa crie cópias do nosso singleton.
Use o Singleton com moderação - ele introduz um estado global que pode tornar os testes mais difíceis. É mais adequado para recursos verdadeiramente compartilhados, onde ter múltiplas instâncias causaria problemas.
Desafio
FácilVamos construir um singleton GameSettings que gerencia a configuração de uma aplicação de jogo. Este é um caso de uso perfeito para o padrão Singleton — você deseja exatamente um gerenciador de configurações que todas as partes do seu jogo possam acessar, garantindo uma configuração consistente em todo o sistema.
Você organizará seu código em três arquivos:
GameSettings.h: Defina sua classe singleton que gerencia a configuração do jogo.Sua classe
GameSettingsdeve armazenar três configurações como membros privados:difficulty(string),volume(inteiro de 0-100) efullscreen(booleano). Inicialize-os com valores padrão:"Normal",50efalse.Implemente o padrão Singleton corretamente:
- Torne o construtor privado
- Delete o construtor de cópia e o operador de atribuição
- Forneça um método estático
getInstance()que retorne uma referência para a instância única
Adicione estes métodos públicos para interagir com as configurações:
setDifficulty(const std::string& diff)— atualiza a dificuldadesetVolume(int vol)— atualiza o volumesetFullscreen(bool fs)— atualiza o modo tela cheiadisplaySettings()— imprime todas as configurações atuais
GameModule.h: Crie um módulo separado que acessa o singleton.Defina uma função chamada
applyGamePresetque recebe um nome de preset (string). Esta função deve acessar o singletonGameSettingse configurá-lo com base no preset:"casual"— define a dificuldade para"Easy", volume para70, fullscreen parafalse"competitive"— define a dificuldade para"Hard", volume para30, fullscreen paratrue- Qualquer outro preset — mantém as configurações padrão inalteradas
Isso demonstra como diferentes partes de um programa podem acessar e modificar a mesma instância singleton.
main.cpp: Reúna tudo para mostrar o singleton em ação.Leia duas entradas:
- Um nome de preset (string)
- Um nível de volume personalizado (inteiro)
Primeiro, exiba as configurações padrão iniciais chamando
displaySettings()no singleton. Imprima uma linha em branco depois.Em seguida, aplique o preset usando sua função
applyGamePresete exiba as configurações novamente. Imprima uma linha em branco depois.Finalmente, sobrescreva apenas o volume com o valor de entrada personalizado e exiba as configurações finais.
O método displaySettings() deve produzir a saída exatamente neste formato:
Difficulty: [value]
Volume: [value]
Fullscreen: [yes/no]Por exemplo, com as entradas casual e 85:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Easy
Volume: 70
Fullscreen: no
Difficulty: Easy
Volume: 85
Fullscreen: noCom as entradas competitive e 20:
Difficulty: Normal
Volume: 50
Fullscreen: no
Difficulty: Hard
Volume: 30
Fullscreen: yes
Difficulty: Hard
Volume: 20
Fullscreen: yesObserve como tanto o main.cpp quanto o GameModule.h acessam a mesma instância singleton — as alterações feitas em um lugar são visíveis em todos os lugares. Este é o poder do padrão Singleton para recursos compartilhados como as configurações de um jogo.
Folha de consulta
O padrão Singleton garante que uma classe tenha apenas uma instância em todo o programa e fornece acesso global a ela. É útil para recursos compartilhados, como gerenciadores de configuração, loggers ou pools de conexão de banco de dados.
Passos principais de implementação:
- Torne o construtor privado para evitar a instanciação direta
- Exclua o construtor de cópia e o operador de atribuição para evitar cópias
- Forneça um método estático que retorne uma referência para a instância única
class Logger {
private:
std::string logFile;
// Construtor privado
Logger() : logFile("app.log") {}
// Excluir construtor de cópia e operador de atribuição
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
public:
// Método estático para acessar a instância única
static Logger& getInstance() {
static Logger instance; // Criada uma vez, vive até o fim do programa
return instance;
}
void log(const std::string& message) {
std::cout << "[LOG] " << message << "\n";
}
};
// Uso
Logger::getInstance().log("Application started");
Logger& logger = Logger::getInstance();
logger.log("Processing data");O static Logger instance dentro de getInstance() usa "magic statics" do C++11 — é thread-safe e garantido ser inicializado apenas uma vez.
Use o Singleton com moderação, pois ele introduz um estado global que pode dificultar os testes. É mais adequado para recursos verdadeiramente compartilhados, onde múltiplas instâncias causariam problemas.
Experimente você mesmo
#include <iostream>
#include <string>
#include "GameSettings.h"
#include "GameModule.h"
using namespace std;
int main() {
// Ler as entradas
string presetName;
int customVolume;
cin >> presetName;
cin >> customVolume;
// TODO: Obter a instância singleton de GameSettings
// TODO: Exibir as configurações padrão iniciais
// TODO: Imprimir uma linha em branco
// TODO: Aplicar o preset usando a função applyGamePreset
// TODO: Exibir as configurações após aplicar o preset
// TODO: Imprimir uma linha em branco
// TODO: Sobrescrever o volume com customVolume
// TODO: Exibir as configurações finais
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários10Visão Geral da STL
Visão Geral e Filosofia da STLContainers da STLIteradoresAlgoritmos da STLFunctors e Expressões LambdaRecapitulação - Frequência de Palavras13Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico