Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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 GameSettings deve armazenar três configurações como membros privados: difficulty (string), volume (inteiro de 0-100) e fullscreen (booleano). Inicialize-os com valores padrão: "Normal", 50 e false.

    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 dificuldade
    • setVolume(int vol) — atualiza o volume
    • setFullscreen(bool fs) — atualiza o modo tela cheia
    • displaySettings() — imprime todas as configurações atuais
  • GameModule.h: Crie um módulo separado que acessa o singleton.

    Defina uma função chamada applyGamePreset que recebe um nome de preset (string). Esta função deve acessar o singleton GameSettings e configurá-lo com base no preset:

    • "casual" — define a dificuldade para "Easy", volume para 70, fullscreen para false
    • "competitive" — define a dificuldade para "Hard", volume para 30, fullscreen para true
    • 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:

    1. Um nome de preset (string)
    2. 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 applyGamePreset e 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: no

Com as entradas competitive e 20:

Difficulty: Normal
Volume: 50
Fullscreen: no

Difficulty: Hard
Volume: 30
Fullscreen: yes

Difficulty: Hard
Volume: 20
Fullscreen: yes

Observe 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;
}
quiz iconTeste seus conhecimentos

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