Singleton Pattern (Padrão Singleton)
📋 Visão Geral
O Singleton Pattern garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a essa instância. É útil quando exatamente um objeto é necessário para coordenar ações em todo o sistema.
🎯 Quando Usar
- Configurações globais do sistema
- Pool de conexões de base de dados
- Logger centralizado
- Cache compartilhado
- Gestor de recursos únicos
- Fábricas de objetos
🔹 Implementação Básica
CLASS lcl_singleton DEFINITION CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
get_instance
RETURNING VALUE(ro_instance) TYPE REF TO lcl_singleton.
METHODS:
fazer_algo.
PRIVATE SECTION.
CLASS-DATA: so_instance TYPE REF TO lcl_singleton.
METHODS: constructor.
ENDCLASS.
CLASS lcl_singleton IMPLEMENTATION.
METHOD get_instance.
" Criar instância apenas se não existir
IF so_instance IS NOT BOUND.
CREATE OBJECT so_instance.
ENDIF.
ro_instance = so_instance.
ENDMETHOD.
METHOD constructor.
WRITE: / 'Singleton criado pela primeira vez'.
ENDMETHOD.
METHOD fazer_algo.
WRITE: / 'Executando ação do singleton'.
ENDMETHOD.
ENDCLASS.
" ========== USO ==========
START-OF-SELECTION.
" Obter instância
DATA(lo_obj1) = lcl_singleton=>get_instance( ).
lo_obj1->fazer_algo( ).
" Obter novamente - mesma instância!
DATA(lo_obj2) = lcl_singleton=>get_instance( ).
lo_obj2->fazer_algo( ).
" Verificar que são a mesma instância
IF lo_obj1 = lo_obj2.
WRITE: / '✅ Mesma instância confirmada'.
ENDIF.
" ❌ ERRO: não se pode criar diretamente
" DATA(lo_obj3) = NEW lcl_singleton( ). " Erro de compilação!
🔹 Singleton para Configuração
CLASS lcl_configuracao DEFINITION CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
get_instance
RETURNING VALUE(ro_instance) TYPE REF TO lcl_configuracao.
METHODS:
set_parametro
IMPORTING iv_chave TYPE string
iv_valor TYPE string,
get_parametro
IMPORTING iv_chave TYPE string
RETURNING VALUE(rv_valor) TYPE string,
listar_parametros.
PRIVATE SECTION.
CLASS-DATA: so_instance TYPE REF TO lcl_configuracao.
TYPES: BEGIN OF ty_parametro,
chave TYPE string,
valor TYPE string,
END OF ty_parametro.
DATA: mt_parametros TYPE HASHED TABLE OF ty_parametro
WITH UNIQUE KEY chave.
METHODS:
constructor,
carregar_config_default.
ENDCLASS.
CLASS lcl_configuracao IMPLEMENTATION.
METHOD get_instance.
IF so_instance IS NOT BOUND.
CREATE OBJECT so_instance.
ENDIF.
ro_instance = so_instance.
ENDMETHOD.
METHOD constructor.
WRITE: / '⚙️ Inicializando configurações...'.
carregar_config_default( ).
ENDMETHOD.
METHOD carregar_config_default.
set_parametro( iv_chave = 'AMBIENTE' iv_valor = 'PRODUÇÃO' ).
set_parametro( iv_chave = 'DEBUG' iv_valor = 'FALSE' ).
set_parametro( iv_chave = 'TIMEOUT' iv_valor = '30' ).
set_parametro( iv_chave = 'MAX_REGISTROS' iv_valor = '1000' ).
ENDMETHOD.
METHOD set_parametro.
INSERT VALUE #( chave = iv_chave valor = iv_valor )
INTO TABLE mt_parametros.
IF sy-subrc <> 0.
MODIFY TABLE mt_parametros FROM VALUE #( chave = iv_chave valor = iv_valor ).
ENDIF.
ENDMETHOD.
METHOD get_parametro.
READ TABLE mt_parametros
WITH TABLE KEY chave = iv_chave
INTO DATA(ls_param).
IF sy-subrc = 0.
rv_valor = ls_param-valor.
ELSE.
rv_valor = ''.
ENDIF.
ENDMETHOD.
METHOD listar_parametros.
WRITE: / '═══ CONFIGURAÇÕES ═══'.
LOOP AT mt_parametros INTO DATA(ls_param).
WRITE: / |{ ls_param-chave WIDTH = 15 }: { ls_param-valor }|.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
" ========== USO ==========
START-OF-SELECTION.
" Usar configuração em diferentes partes do código
DATA(lo_config) = lcl_configuracao=>get_instance( ).
lo_config->listar_parametros( ).
SKIP.
" Alterar configuração
lo_config->set_parametro( iv_chave = 'DEBUG' iv_valor = 'TRUE' ).
" Em outro lugar do código, mesma instância
DATA(lo_config2) = lcl_configuracao=>get_instance( ).
DATA(lv_debug) = lo_config2->get_parametro( 'DEBUG' ).
WRITE: / |Debug está: { lv_debug }|. " TRUE
🔹 Singleton para Logger
CLASS lcl_logger DEFINITION CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
get_instance
RETURNING VALUE(ro_instance) TYPE REF TO lcl_logger.
METHODS:
log_info
IMPORTING iv_mensagem TYPE string,
log_warning
IMPORTING iv_mensagem TYPE string,
log_error
IMPORTING iv_mensagem TYPE string,
mostrar_logs,
limpar_logs.
PRIVATE SECTION.
CLASS-DATA: so_instance TYPE REF TO lcl_logger.
TYPES: BEGIN OF ty_log,
timestamp TYPE timestampl,
nivel TYPE string,
mensagem TYPE string,
END OF ty_log.
DATA: mt_logs TYPE TABLE OF ty_log.
METHODS:
adicionar_log
IMPORTING iv_nivel TYPE string
iv_mensagem TYPE string.
ENDCLASS.
CLASS lcl_logger IMPLEMENTATION.
METHOD get_instance.
IF so_instance IS NOT BOUND.
CREATE OBJECT so_instance.
ENDIF.
ro_instance = so_instance.
ENDMETHOD.
METHOD log_info.
adicionar_log( iv_nivel = 'INFO' iv_mensagem = iv_mensagem ).
ENDMETHOD.
METHOD log_warning.
adicionar_log( iv_nivel = 'WARNING' iv_mensagem = iv_mensagem ).
ENDMETHOD.
METHOD log_error.
adicionar_log( iv_nivel = 'ERROR' iv_mensagem = iv_mensagem ).
ENDMETHOD.
METHOD adicionar_log.
GET TIME STAMP FIELD DATA(lv_timestamp).
APPEND VALUE #(
timestamp = lv_timestamp
nivel = iv_nivel
mensagem = iv_mensagem
) TO mt_logs.
" Imprimir imediatamente (opcional)
DATA(lv_icone) = SWITCH string( iv_nivel
WHEN 'INFO' THEN 'ℹ️'
WHEN 'WARNING' THEN '⚠️'
WHEN 'ERROR' THEN '❌'
ELSE '📝'
).
WRITE: / |{ lv_icone } [{ iv_nivel WIDTH = 7 }] { iv_mensagem }|.
ENDMETHOD.
METHOD mostrar_logs.
WRITE: / '╔════════════════════════════════════════╗'.
WRITE: / '║ HISTÓRICO DE LOGS ║'.
WRITE: / '╚════════════════════════════════════════╝'.
LOOP AT mt_logs INTO DATA(ls_log).
WRITE: / |{ sy-tabix }. [{ ls_log-nivel }] { ls_log-mensagem }|.
ENDLOOP.
WRITE: / |Total de logs: { lines( mt_logs ) }|.
ENDMETHOD.
METHOD limpar_logs.
CLEAR mt_logs.
WRITE: / '🗑️ Logs limpos'.
ENDMETHOD.
ENDCLASS.
" ========== USO ==========
START-OF-SELECTION.
" Usar logger em diferentes partes
DATA(lo_log) = lcl_logger=>get_instance( ).
lo_log->log_info( 'Aplicação iniciada' ).
lo_log->log_info( 'Conectando à base de dados...' ).
lo_log->log_warning( 'Cache não encontrado, criando novo' ).
lo_log->log_error( 'Falha ao conectar ao servidor externo' ).
lo_log->log_info( 'Aplicação finalizada' ).
SKIP.
lo_log->mostrar_logs( ).
🔹 Singleton Thread-Safe (Class Constructor)
CLASS lcl_singleton_seguro DEFINITION CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
class_constructor,
get_instance
RETURNING VALUE(ro_instance) TYPE REF TO lcl_singleton_seguro.
METHODS:
processar.
PRIVATE SECTION.
CLASS-DATA: so_instance TYPE REF TO lcl_singleton_seguro.
ENDCLASS.
CLASS lcl_singleton_seguro IMPLEMENTATION.
METHOD class_constructor.
" Executado automaticamente na primeira utilização da classe
" Thread-safe por natureza em ABAP
CREATE OBJECT so_instance.
WRITE: / '🔒 Singleton criado de forma thread-safe'.
ENDMETHOD.
METHOD get_instance.
ro_instance = so_instance.
ENDMETHOD.
METHOD processar.
WRITE: / 'Processando...'.
ENDMETHOD.
ENDCLASS.
💡 Exemplo Completo: Cache Singleton
*&---------------------------------------------------------------------*
*& Report Z_OO_SINGLETON_CACHE
*&---------------------------------------------------------------------*
REPORT z_oo_singleton_cache.
" ========== CACHE SINGLETON ==========
CLASS lcl_cache DEFINITION CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS:
get_instance
RETURNING VALUE(ro_instance) TYPE REF TO lcl_cache.
METHODS:
put
IMPORTING iv_chave TYPE string
iv_valor TYPE any,
get
IMPORTING iv_chave TYPE string
RETURNING VALUE(rv_valor) TYPE string,
existe
IMPORTING iv_chave TYPE string
RETURNING VALUE(rv_existe) TYPE abap_bool,
remover
IMPORTING iv_chave TYPE string,
limpar,
estatisticas.
PRIVATE SECTION.
CLASS-DATA: so_instance TYPE REF TO lcl_cache.
TYPES: BEGIN OF ty_entrada_cache,
chave TYPE string,
valor TYPE string,
timestamp TYPE timestampl,
acessos TYPE i,
END OF ty_entrada_cache.
DATA: mt_cache TYPE HASHED TABLE OF ty_entrada_cache
WITH UNIQUE KEY chave.
METHODS:
constructor.
ENDCLASS.
CLASS lcl_cache IMPLEMENTATION.
METHOD get_instance.
IF so_instance IS NOT BOUND.
CREATE OBJECT so_instance.
ENDIF.
ro_instance = so_instance.
ENDMETHOD.
METHOD constructor.
WRITE: / '💾 Cache inicializado'.
ENDMETHOD.
METHOD put.
GET TIME STAMP FIELD DATA(lv_timestamp).
DATA(ls_entrada) = VALUE ty_entrada_cache(
chave = iv_chave
valor = CONV string( iv_valor )
timestamp = lv_timestamp
acessos = 0
).
INSERT ls_entrada INTO TABLE mt_cache.
IF sy-subrc <> 0.
MODIFY TABLE mt_cache FROM ls_entrada.
WRITE: / |📝 Cache atualizado: { iv_chave }|.
ELSE.
WRITE: / |➕ Cache adicionado: { iv_chave }|.
ENDIF.
ENDMETHOD.
METHOD get.
READ TABLE mt_cache
WITH TABLE KEY chave = iv_chave
ASSIGNING FIELD-SYMBOL(<ls_cache>).
IF sy-subrc = 0.
rv_valor = <ls_cache>-valor.
<ls_cache>-acessos = <ls_cache>-acessos + 1.
WRITE: / |✅ Cache hit: { iv_chave }|.
ELSE.
WRITE: / |❌ Cache miss: { iv_chave }|.
rv_valor = ''.
ENDIF.
ENDMETHOD.
METHOD existe.
READ TABLE mt_cache
WITH TABLE KEY chave = iv_chave
TRANSPORTING NO FIELDS.
rv_existe = COND #( WHEN sy-subrc = 0 THEN abap_true
ELSE abap_false ).
ENDMETHOD.
METHOD remover.
DELETE TABLE mt_cache WITH TABLE KEY chave = iv_chave.
IF sy-subrc = 0.
WRITE: / |🗑️ Removido do cache: { iv_chave }|.
ENDIF.
ENDMETHOD.
METHOD limpar.
DATA(lv_count) = lines( mt_cache ).
CLEAR mt_cache.
WRITE: / |🧹 Cache limpo ({ lv_count } entradas removidas)|.
ENDMETHOD.
METHOD estatisticas.
WRITE: / '╔════════════════════════════════════════╗'.
WRITE: / '║ ESTATÍSTICAS DO CACHE ║'.
WRITE: / '╚════════════════════════════════════════╝'.
WRITE: / |Total de entradas: { lines( mt_cache ) }|.
SKIP.
IF mt_cache IS NOT INITIAL.
WRITE: / 'Chave', 25 'Acessos', 40 'Timestamp'.
WRITE: / '─────────────────────────────────────────'.
LOOP AT mt_cache INTO DATA(ls_cache).
WRITE: / ls_cache-chave, 25 ls_cache-acessos, 40 ls_cache-timestamp.
ENDLOOP.
ELSE.
WRITE: / '(vazio)'.
ENDIF.
ENDMETHOD.
ENDCLASS.
" ========== SERVIÇO QUE USA CACHE ==========
CLASS lcl_servico_dados DEFINITION.
PUBLIC SECTION.
METHODS:
obter_usuario
IMPORTING iv_id TYPE string
RETURNING VALUE(rv_nome) TYPE string,
obter_produto
IMPORTING iv_id TYPE string
RETURNING VALUE(rv_desc) TYPE string.
PRIVATE SECTION.
DATA: mo_cache TYPE REF TO lcl_cache.
METHODS:
buscar_usuario_bd
IMPORTING iv_id TYPE string
RETURNING VALUE(rv_nome) TYPE string,
buscar_produto_bd
IMPORTING iv_id TYPE string
RETURNING VALUE(rv_desc) TYPE string.
ENDCLASS.
CLASS lcl_servico_dados IMPLEMENTATION.
METHOD obter_usuario.
" Usar cache singleton
mo_cache = lcl_cache=>get_instance( ).
DATA(lv_chave) = |USER_{ iv_id }|.
" Verificar se está em cache
IF mo_cache->existe( lv_chave ) = abap_true.
rv_nome = mo_cache->get( lv_chave ).
ELSE.
" Buscar na BD (simulado)
rv_nome = buscar_usuario_bd( iv_id ).
" Guardar em cache
mo_cache->put( iv_chave = lv_chave iv_valor = rv_nome ).
ENDIF.
ENDMETHOD.
METHOD buscar_usuario_bd.
WRITE: / |🔍 Buscando usuário { iv_id } na BD...|.
rv_nome = |Usuário_{ iv_id }|.
ENDMETHOD.
METHOD obter_produto.
mo_cache = lcl_cache=>get_instance( ).
DATA(lv_chave) = |PROD_{ iv_id }|.
IF mo_cache->existe( lv_chave ) = abap_true.
rv_desc = mo_cache->get( lv_chave ).
ELSE.
rv_desc = buscar_produto_bd( iv_id ).
mo_cache->put( iv_chave = lv_chave iv_valor = rv_desc ).
ENDIF.
ENDMETHOD.
METHOD buscar_produto_bd.
WRITE: / |🔍 Buscando produto { iv_id } na BD...|.
rv_desc = |Produto_{ iv_id }|.
ENDMETHOD.
ENDCLASS.
" ========== PROGRAMA PRINCIPAL ==========
START-OF-SELECTION.
WRITE: / '════════════════════════════════════════'.
WRITE: / ' DEMONSTRAÇÃO: CACHE SINGLETON'.
WRITE: / '════════════════════════════════════════'.
SKIP.
DATA(lo_servico) = NEW lcl_servico_dados( ).
" Primeira busca - vai à BD
WRITE: / '─── Primeira busca de usuário 001 ───'.
DATA(lv_user) = lo_servico->obter_usuario( '001' ).
WRITE: / |Resultado: { lv_user }|.
SKIP.
" Segunda busca - vem do cache
WRITE: / '─── Segunda busca de usuário 001 ───'.
lv_user = lo_servico->obter_usuario( '001' ).
WRITE: / |Resultado: { lv_user }|.
SKIP.
" Buscar produto
WRITE: / '─── Buscar produto 100 ───'.
DATA(lv_prod) = lo_servico->obter_produto( '100' ).
WRITE: / |Resultado: { lv_prod }|.
SKIP.
" Buscar mesmo produto novamente
WRITE: / '─── Buscar produto 100 novamente ───'.
lv_prod = lo_servico->obter_produto( '100' ).
WRITE: / |Resultado: { lv_prod }|.
SKIP.
" Mostrar estatísticas
DATA(lo_cache) = lcl_cache=>get_instance( ).
lo_cache->estatisticas( ).
SKIP.
" Limpar cache
WRITE: / '─── Limpar cache ───'.
lo_cache->limpar( ).
SKIP.
" Buscar novamente após limpar
WRITE: / '─── Buscar usuário 001 após limpar cache ───'.
lv_user = lo_servico->obter_usuario( '001' ).
WRITE: / |Resultado: { lv_user }|.
🎯 Vantagens e Desvantagens
✅ Vantagens
- Acesso controlado à única instância
- Ponto de acesso global
- Economia de memória
- Inicialização tardia (lazy initialization)
- Estado partilhado consistente
❌ Desvantagens
- Dificulta testes unitários
- Viola Single Responsibility Principle
- Pode esconder dependências
- Problemas com concorrência (em outras linguagens)
- Estado global pode causar acoplamento
💡 Boas Práticas
✅ Fazer
" 1. Usar CREATE PRIVATE
CLASS lcl_singleton DEFINITION CREATE PRIVATE.
" 2. Verificar se instância existe
IF so_instance IS NOT BOUND.
CREATE OBJECT so_instance.
ENDIF.
" 3. Usar class_constructor para thread-safety
CLASS-METHODS class_constructor.
" 4. Documentar que é singleton
" Singleton pattern - única instância global
❌ Evitar
" 1. Singleton para tudo
" Use apenas quando realmente necessário!
" 2. Estado mutável excessivo
" Mantenha estado mínimo e controlado
" 3. Lógica de negócio no singleton
" Singleton deve ser infraestrutura, não negócio
" 4. Dependências circulares
" Singleton A -> Singleton B -> Singleton A " ❌
🔗 Alternativas ao Singleton
- Dependency Injection: Passar dependências explicitamente
- Service Locator: Registro central de serviços
- Static Class: Se não precisa de estado
- Factory: Para criar instâncias controladas
🔗 Próximos Passos
- Factory Pattern - Criar objetos de forma controlada
- Interfaces - Contratos para singletons
- Performance - Cache e otimizações
Tags: #OO #DesignPatterns #Singleton #CreationalPatterns #Cache #ABAP