Padrão Singleton
Parte da seção Programação Orientada a Objetos do Journey de Java da Coddy — lição 65 de 87.
O Padrão Singleton é um padrão de projeto criacional que garante que uma classe tenha apenas uma instância em toda a sua aplicação, ao mesmo tempo que fornece um ponto de acesso global a essa instância. Isso é útil para recursos que devem ser compartilhados, como gerenciadores de configuração, pools de conexão ou serviços de log.
O padrão funciona tornando o construtor privado, impedindo a instanciação direta. Em vez disso, um método estático controla o acesso à instância única:
public class DatabaseConnection {
private static DatabaseConnection instance;
// O construtor privado impede a instanciação direta
private DatabaseConnection() {
System.out.println("Connection created");
}
// Ponto de acesso global
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public void query(String sql) {
System.out.println("Executing: " + sql);
}
}Cada chamada para getInstance() retorna o mesmo objeto. A instância é criada apenas quando solicitada pela primeira vez—isso é chamado de inicialização preguiçosa:
DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();
System.out.println(db1 == db2); // true - mesma instânciaOs componentes principais de um Singleton são: um campo private static que contém a instância, um construtor private e um método public static que retorna a instância. Esta combinação garante que, não importa quantas vezes você solicite o objeto, você sempre obterá o mesmo.
Desafio
FácilVamos construir um gerenciador de configuração para um aplicativo usando o Singleton Pattern! Seu gerenciador de configuração garantirá que todas as partes do seu aplicativo compartilhem as mesmas configurações, não importa quantas vezes elas solicitem acesso a ele.
Você organizará seu código em dois arquivos:
AppConfig.java: Crie uma classe Singleton que gerencia as configurações do aplicativo. SeuAppConfigdeve ter:Um campo estático privado para manter a instância única.
Um campo String privado chamado
appNamee um campo int privado chamadomaxUsers.Um construtor privado que inicializa
appNamecomo"MyApplication"emaxUserscomo100, e então imprimeAppConfig initialized.Um método estático público
getInstance()que cria a instância apenas se ela ainda não existir (lazy initialization) e, em seguida, a retorna.Um método
setAppName(String name)que atualiza o nome do aplicativo.Um método
setMaxUsers(int max)que atualiza o número máximo de usuários.Um método
displaySettings()que imprimeApp: [appName], Max Users: [maxUsers].Main.java: Demonstre que seu Singleton funciona corretamente! Você receberá duas entradas: um novo nome de aplicativo (String) e um novo valor de máximo de usuários (inteiro).Primeiro, obtenha uma instância de
AppConfige armazene-a em uma variável chamadaconfig1. ChamedisplaySettings()para mostrar os valores padrão.Em seguida, atualize as configurações usando os valores de entrada chamando
setAppNameesetMaxUsersemconfig1.Obtenha outra instância e armazene-a em
config2. ChamedisplaySettings()emconfig2para provar que ela reflete as alterações feitas através deconfig1.Finalmente, imprima se ambas as referências apontam para o mesmo objeto:
Same instance: [true/false]comparandoconfig1 == config2.
Você receberá duas entradas em ordem: o novo nome do aplicativo (String) e o novo máximo de usuários (inteiro).
Este desafio demonstra o benefício central do Singleton Pattern: não importa quantas vezes você solicite a configuração, você sempre obtém a mesma instância com o mesmo estado compartilhado!
Folha de consulta
O Singleton Pattern garante que uma classe tenha apenas uma instância em toda a aplicação, fornecendo um ponto de acesso global a essa instância.
Componentes Chave:
- Um campo
private staticque contém a instância única - Um construtor
privatepara evitar a instanciação direta - Um método
public staticque retorna a instância
Implementação Básica:
public class DatabaseConnection {
private static DatabaseConnection instance;
// Construtor privado evita a instanciação direta
private DatabaseConnection() {
System.out.println("Connection created");
}
// Ponto de acesso global
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public void query(String sql) {
System.out.println("Executing: " + sql);
}
}Uso:
DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();
System.out.println(db1 == db2); // true - mesma instânciaA instância é criada apenas quando solicitada pela primeira vez—isso é chamado de lazy initialization. Cada chamada para getInstance() retorna o mesmo objeto, garantindo um estado compartilhado em toda a aplicação.
Experimente você mesmo
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Ler entradas
String newAppName = scanner.nextLine();
int newMaxUsers = scanner.nextInt();
// TODO: Obter a primeira instância de AppConfig e armazenar em config1
// TODO: Chamar displaySettings() em config1 para mostrar os valores padrão
// TODO: Atualizar as configurações usando setAppName e setMaxUsers em config1
// TODO: Obter outra instância e armazenar em config2
// TODO: Chamar displaySettings() em config2 para provar que as alterações são compartilhadas
// TODO: Imprimir se ambas as referências apontam para o mesmo objeto:
// "Same instance: [true/false]" usando config1 == config2
}
}
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 POO
Arquivos ExternosIntrodução à POOClasses vs ObjetosA Palavra-chave thisMétodosCampos (Atributos)Método ConstrutorSobrecarga de ConstrutorRecapitulação - Calculadora Simples4Herança
Herança Básica (extends)A Palavra-chave superSobrescrita de Método (@Override)Encadeamento de ConstrutoresA Classe ObjectHerança Simples e MultinívelPor que não há Herança Múltipla de ClassesRecapitulação - Hierarquia de Funcionários7Métodos Especiais e a Classe Object
Método toString()equals() e hashCode()Método clone()compareTo() e ComparableInterface ComparatorRecapitulação - Ordenação Personalizada2Modificadores de Acesso e Encapsulamento
Visão Geral dos Níveis de AcessoMétodos Getter e SetterOcultação de InformaçõesA Palavra-chave finalRecapitulação - Gerenciador de Conta Bancária5Polimorfismo
Fundamentos de Sobrecarga de MétodosSobrescrita de Métodos (Tempo de Execução)Upcasting e DowncastingO Operador instanceofClasses e Métodos AbstratosRecapitulação - Calculadora de Formas8Conceitos Avançados de OOP
Composição vs HerançaAgregação vs ComposiçãoClasses Internas, Aninhadas e AnônimasEnums e Métodos de EnumRecords (Java 16+)Classes Seladas (Java 17+)11Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão BuilderPadrão ObserverPadrão Strategy3Propriedades de Classe e Membros Estáticos
Variáveis de Instância vs EstáticasMétodos EstáticosBlocos EstáticosConstantes (static final)Recapitulação - Contador e Utilitário6Interfaces e Classes Abstratas
Introdução às InterfacesImplementando InterfacesImplementação de Múltiplas InterfacesDefault e Static em InterfacesClasses Abstratas vs InterfacesInterfaces FuncionaisRecapitulação - Sistema de Pagamento9Generics
Introdução a GenericsClasses GenéricasMétodos GenéricosParâmetros de Tipos DelimitadosWildcards (?, extends, super)Recapitulação - Container Genérico12Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositePadrão Iterator