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 durante 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, um logger ou um 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 única instância:

#include <iostream>
#include <string>

class Logger {
private:
    std::string logFile;
    
    // Construtor privado - impede a instanciação direta
    Logger() : logFile("app.log") {}
    
    // Exclui 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 única instância
    static Logger& getInstance() {
        static Logger instance;  // Criado 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");
}

O static Logger instance dentro de getInstance() é um recurso do C++11 chamado de "estáticos mágicos" — ele é seguro para threads e tem a garantia de ser inicializado apenas uma vez. Ao excluir o construtor de cópia e o operador de atribuição, impedimos que alguém crie cópias do nosso singleton.

Use Singleton com moderação — ele introduz um estado global, o que pode dificultar os testes. Ele é mais adequado para recursos realmente compartilhados, nos quais ter várias instâncias causaria problemas.

challenge icon

Desafio

Fácil

Vamos criar um singleton GameSettings que gerencia a configuração de um aplicativo de jogo. Este é um caso de uso perfeito para o padrão Singleton. Você deseja exatamente um gerenciador de configurações ao qual todas as partes do seu jogo possam ter acesso, garantindo uma configuração consistente em todo o projeto.

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-as com os valores padrão: "Normal", 50 e false.

    Implemente o padrão Singleton corretamente:

    • Torne o construtor privado
    • Exclua o construtor de cópia e o operador de atribuição
    • Forneça um método estático getInstance() que retorne uma referência à única instância

    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 de tela cheia
    • displaySettings(): exibe todas as configurações atuais
  • GameModule.h: Crie um módulo separado que acesse o singleton.

    Defina uma função chamada applyGamePreset que receba um nome de predefinição (string). Essa função deve acessar o singleton GameSettings e configurá-lo com base na predefinição:

    • "casual": define a dificuldade como "Easy", o volume como 70 e a tela cheia como false
    • "competitive": define a dificuldade como "Hard", o volume como 30 e a tela cheia como true
    • Qualquer outra predefinição: 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 predefinição (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 a predefinição usando sua função applyGamePreset e exiba as configurações novamente. Imprima uma linha em branco depois.

    Por fim, substitua apenas o volume pelo valor de entrada personalizado e exiba as configurações finais.

O método displaySettings() deve produzir exatamente este 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 main.cpp quanto GameModule.h acessam a mesma instância singleton: as alterações feitas em um lugar ficam visíveis em todos os outros. Esse é o poder do padrão Singleton para recursos compartilhados, como as configurações do jogo.

Experimente você mesmo

#include <iostream>
#include <string>
#include "GameSettings.h"
#include "GameModule.h"

using namespace std;

int main() {
    // Ler 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

Pratique por conta própria: Compilador de C++ online