Ir para o conteúdo

Factory Pattern (Padrão Fábrica)

📋 Visão Geral

O Factory Pattern é um padrão de criação que fornece uma interface para criar objetos sem especificar suas classes concretas. Centraliza a lógica de criação de objetos, tornando o código mais flexível e fácil de manter.


🎯 Quando Usar

  • Criação de objetos é complexa
  • Tipo de objeto depende de condições em runtime
  • Deseja desacoplar código cliente da implementação
  • Precisa de lógica centralizada de criação
  • Criar famílias de objetos relacionados

🔹 Simple Factory (Fábrica Simples)

" ========== INTERFACE ==========
INTERFACE lif_notificacao.
  METHODS: enviar IMPORTING iv_mensagem TYPE string.
ENDINTERFACE.

" ========== IMPLEMENTAÇÕES ==========
CLASS lcl_email DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_notificacao.
ENDCLASS.

CLASS lcl_email IMPLEMENTATION.
  METHOD lif_notificacao~enviar.
    WRITE: / |📧 Email: { iv_mensagem }|.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_sms DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_notificacao.
ENDCLASS.

CLASS lcl_sms IMPLEMENTATION.
  METHOD lif_notificacao~enviar.
    WRITE: / |📱 SMS: { iv_mensagem }|.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_push DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_notificacao.
ENDCLASS.

CLASS lcl_push IMPLEMENTATION.
  METHOD lif_notificacao~enviar.
    WRITE: / |🔔 Push: { iv_mensagem }|.
  ENDMETHOD.
ENDCLASS.

" ========== FACTORY ==========
CLASS lcl_notificacao_factory DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      criar
        IMPORTING iv_tipo TYPE string
        RETURNING VALUE(ro_notificacao) TYPE REF TO lif_notificacao
        RAISING cx_parameter_invalid.
ENDCLASS.

CLASS lcl_notificacao_factory IMPLEMENTATION.
  METHOD criar.
    CASE iv_tipo.
      WHEN 'EMAIL'.
        ro_notificacao = NEW lcl_email( ).
      WHEN 'SMS'.
        ro_notificacao = NEW lcl_sms( ).
      WHEN 'PUSH'.
        ro_notificacao = NEW lcl_push( ).
      WHEN OTHERS.
        RAISE EXCEPTION TYPE cx_parameter_invalid.
    ENDCASE.
  ENDMETHOD.
ENDCLASS.

" ========== USO ==========
START-OF-SELECTION.

  TRY.
      DATA(lo_notif1) = lcl_notificacao_factory=>criar( 'EMAIL' ).
      lo_notif1->enviar( 'Bem-vindo!' ).

      DATA(lo_notif2) = lcl_notificacao_factory=>criar( 'SMS' ).
      lo_notif2->enviar( 'Código: 1234' ).

      DATA(lo_notif3) = lcl_notificacao_factory=>criar( 'PUSH' ).
      lo_notif3->enviar( 'Nova mensagem' ).

    CATCH cx_parameter_invalid.
      WRITE: / 'Tipo de notificação inválido'.
  ENDTRY.

🔹 Factory Method (Método Fábrica)

" ========== PRODUTO ==========
INTERFACE lif_documento.
  METHODS:
    abrir,
    salvar,
    fechar.
ENDINTERFACE.

CLASS lcl_documento_pdf DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_documento.
ENDCLASS.

CLASS lcl_documento_pdf IMPLEMENTATION.
  METHOD lif_documento~abrir.
    WRITE: / '📄 Abrindo PDF...'.
  ENDMETHOD.

  METHOD lif_documento~salvar.
    WRITE: / '💾 Salvando PDF...'.
  ENDMETHOD.

  METHOD lif_documento~fechar.
    WRITE: / '❌ Fechando PDF...'.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_documento_word DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_documento.
ENDCLASS.

CLASS lcl_documento_word IMPLEMENTATION.
  METHOD lif_documento~abrir.
    WRITE: / '📝 Abrindo Word...'.
  ENDMETHOD.

  METHOD lif_documento~salvar.
    WRITE: / '💾 Salvando Word...'.
  ENDMETHOD.

  METHOD lif_documento~fechar.
    WRITE: / '❌ Fechando Word...'.
  ENDMETHOD.
ENDCLASS.

" ========== CREATOR (ABSTRATO) ==========
CLASS lcl_aplicacao DEFINITION ABSTRACT.
  PUBLIC SECTION.
    METHODS:
      criar_documento ABSTRACT
        RETURNING VALUE(ro_doc) TYPE REF TO lif_documento,
      novo_documento.
ENDCLASS.

CLASS lcl_aplicacao IMPLEMENTATION.
  METHOD novo_documento.
    DATA(lo_doc) = criar_documento( ).
    lo_doc->abrir( ).
  ENDMETHOD.
ENDCLASS.

" ========== CONCRETE CREATORS ==========
CLASS lcl_app_pdf DEFINITION INHERITING FROM lcl_aplicacao.
  PUBLIC SECTION.
    METHODS: criar_documento REDEFINITION.
ENDCLASS.

CLASS lcl_app_pdf IMPLEMENTATION.
  METHOD criar_documento.
    ro_doc = NEW lcl_documento_pdf( ).
  ENDMETHOD.
ENDCLASS.

CLASS lcl_app_word DEFINITION INHERITING FROM lcl_aplicacao.
  PUBLIC SECTION.
    METHODS: criar_documento REDEFINITION.
ENDCLASS.

CLASS lcl_app_word IMPLEMENTATION.
  METHOD criar_documento.
    ro_doc = NEW lcl_documento_word( ).
  ENDMETHOD.
ENDCLASS.

" ========== USO ==========
START-OF-SELECTION.

  DATA(lo_app_pdf) = NEW lcl_app_pdf( ).
  lo_app_pdf->novo_documento( ).

  SKIP.

  DATA(lo_app_word) = NEW lcl_app_word( ).
  lo_app_word->novo_documento( ).

🔹 Abstract Factory (Fábrica Abstrata)

Criar famílias de objetos relacionados.

" ========== PRODUTOS ==========
INTERFACE lif_botao.
  METHODS: renderizar.
ENDINTERFACE.

INTERFACE lif_checkbox.
  METHODS: renderizar.
ENDINTERFACE.

" ========== PRODUTOS WINDOWS ==========
CLASS lcl_botao_windows DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_botao.
ENDCLASS.

CLASS lcl_botao_windows IMPLEMENTATION.
  METHOD lif_botao~renderizar.
    WRITE: / '🖱️  [Botão Windows]'.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_checkbox_windows DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_checkbox.
ENDCLASS.

CLASS lcl_checkbox_windows IMPLEMENTATION.
  METHOD lif_checkbox~renderizar.
    WRITE: / '☑️  Checkbox Windows'.
  ENDMETHOD.
ENDCLASS.

" ========== PRODUTOS MAC ==========
CLASS lcl_botao_mac DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_botao.
ENDCLASS.

CLASS lcl_botao_mac IMPLEMENTATION.
  METHOD lif_botao~renderizar.
    WRITE: / '🖱️  [Botão Mac]'.
  ENDMETHOD.
ENDCLASS.

CLASS lcl_checkbox_mac DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_checkbox.
ENDCLASS.

CLASS lcl_checkbox_mac IMPLEMENTATION.
  METHOD lif_checkbox~renderizar.
    WRITE: / '☑️  Checkbox Mac'.
  ENDMETHOD.
ENDCLASS.

" ========== ABSTRACT FACTORY ==========
INTERFACE lif_ui_factory.
  METHODS:
    criar_botao RETURNING VALUE(ro_botao) TYPE REF TO lif_botao,
    criar_checkbox RETURNING VALUE(ro_checkbox) TYPE REF TO lif_checkbox.
ENDINTERFACE.

" ========== CONCRETE FACTORIES ==========
CLASS lcl_windows_factory DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_ui_factory.
ENDCLASS.

CLASS lcl_windows_factory IMPLEMENTATION.
  METHOD lif_ui_factory~criar_botao.
    ro_botao = NEW lcl_botao_windows( ).
  ENDMETHOD.

  METHOD lif_ui_factory~criar_checkbox.
    ro_checkbox = NEW lcl_checkbox_windows( ).
  ENDMETHOD.
ENDCLASS.

CLASS lcl_mac_factory DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_ui_factory.
ENDCLASS.

CLASS lcl_mac_factory IMPLEMENTATION.
  METHOD lif_ui_factory~criar_botao.
    ro_botao = NEW lcl_botao_mac( ).
  ENDMETHOD.

  METHOD lif_ui_factory~criar_checkbox.
    ro_checkbox = NEW lcl_checkbox_mac( ).
  ENDMETHOD.
ENDCLASS.

" ========== CLIENTE ==========
CLASS lcl_aplicacao_ui DEFINITION.
  PUBLIC SECTION.
    METHODS:
      constructor IMPORTING io_factory TYPE REF TO lif_ui_factory,
      renderizar_interface.

  PRIVATE SECTION.
    DATA: mo_factory TYPE REF TO lif_ui_factory.
ENDCLASS.

CLASS lcl_aplicacao_ui IMPLEMENTATION.
  METHOD constructor.
    mo_factory = io_factory.
  ENDMETHOD.

  METHOD renderizar_interface.
    DATA(lo_botao) = mo_factory->criar_botao( ).
    DATA(lo_check) = mo_factory->criar_checkbox( ).

    lo_botao->renderizar( ).
    lo_check->renderizar( ).
  ENDMETHOD.
ENDCLASS.

" ========== USO ==========
START-OF-SELECTION.

  WRITE: / '=== Interface Windows ==='.
  DATA(lo_app_win) = NEW lcl_aplicacao_ui( NEW lcl_windows_factory( ) ).
  lo_app_win->renderizar_interface( ).

  SKIP.

  WRITE: / '=== Interface Mac ==='.
  DATA(lo_app_mac) = NEW lcl_aplicacao_ui( NEW lcl_mac_factory( ) ).
  lo_app_mac->renderizar_interface( ).

💡 Exemplo Completo: Sistema de Exportação

*&---------------------------------------------------------------------*
*& Report Z_OO_FACTORY_EXPORTACAO
*&---------------------------------------------------------------------*
REPORT z_oo_factory_exportacao.

" ========== INTERFACE DO EXPORTADOR ==========
INTERFACE lif_exportador.
  METHODS:
    exportar
      IMPORTING it_dados TYPE STANDARD TABLE
      RETURNING VALUE(rv_resultado) TYPE string,
    validar_dados
      IMPORTING it_dados TYPE STANDARD TABLE
      RETURNING VALUE(rv_valido) TYPE abap_bool,
    obter_extensao
      RETURNING VALUE(rv_ext) TYPE string.
ENDINTERFACE.

" ========== EXPORTADOR CSV ==========
CLASS lcl_exportador_csv DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_exportador.
ENDCLASS.

CLASS lcl_exportador_csv IMPLEMENTATION.
  METHOD lif_exportador~exportar.
    CHECK validar_dados( it_dados ) = abap_true.

    rv_resultado = 'Nome,Idade,Email' && cl_abap_char_utilities=>newline.

    LOOP AT it_dados INTO DATA(ls_linha).
      rv_resultado = rv_resultado && |Dados CSV linha { sy-tabix }| 
                     && cl_abap_char_utilities=>newline.
    ENDLOOP.

    WRITE: / |✅ Exportado { lines( it_dados ) } linhas para CSV|.
  ENDMETHOD.

  METHOD lif_exportador~validar_dados.
    rv_valido = COND #( WHEN it_dados IS NOT INITIAL THEN abap_true
                        ELSE abap_false ).
  ENDMETHOD.

  METHOD lif_exportador~obter_extensao.
    rv_ext = 'csv'.
  ENDMETHOD.
ENDCLASS.

" ========== EXPORTADOR JSON ==========
CLASS lcl_exportador_json DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_exportador.
ENDCLASS.

CLASS lcl_exportador_json IMPLEMENTATION.
  METHOD lif_exportador~exportar.
    CHECK validar_dados( it_dados ) = abap_true.

    rv_resultado = '[' && cl_abap_char_utilities=>newline.

    LOOP AT it_dados INTO DATA(ls_linha).
      rv_resultado = rv_resultado && 
                     |  {{ "linha": { sy-tabix } }}| &&
                     cl_abap_char_utilities=>newline.
    ENDLOOP.

    rv_resultado = rv_resultado && ']'.

    WRITE: / |✅ Exportado { lines( it_dados ) } objetos para JSON|.
  ENDMETHOD.

  METHOD lif_exportador~validar_dados.
    rv_valido = COND #( WHEN it_dados IS NOT INITIAL THEN abap_true
                        ELSE abap_false ).
  ENDMETHOD.

  METHOD lif_exportador~obter_extensao.
    rv_ext = 'json'.
  ENDMETHOD.
ENDCLASS.

" ========== EXPORTADOR XML ==========
CLASS lcl_exportador_xml DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_exportador.
ENDCLASS.

CLASS lcl_exportador_xml IMPLEMENTATION.
  METHOD lif_exportador~exportar.
    CHECK validar_dados( it_dados ) = abap_true.

    rv_resultado = '<?xml version="1.0"?>' && cl_abap_char_utilities=>newline.
    rv_resultado = rv_resultado && '<dados>' && cl_abap_char_utilities=>newline.

    LOOP AT it_dados INTO DATA(ls_linha).
      rv_resultado = rv_resultado && 
                     |  <linha id="{ sy-tabix }" />| &&
                     cl_abap_char_utilities=>newline.
    ENDLOOP.

    rv_resultado = rv_resultado && '</dados>'.

    WRITE: / |✅ Exportado { lines( it_dados ) } elementos para XML|.
  ENDMETHOD.

  METHOD lif_exportador~validar_dados.
    rv_valido = COND #( WHEN it_dados IS NOT INITIAL THEN abap_true
                        ELSE abap_false ).
  ENDMETHOD.

  METHOD lif_exportador~obter_extensao.
    rv_ext = 'xml'.
  ENDMETHOD.
ENDCLASS.

" ========== EXPORTADOR EXCEL ==========
CLASS lcl_exportador_excel DEFINITION.
  PUBLIC SECTION.
    INTERFACES lif_exportador.
ENDCLASS.

CLASS lcl_exportador_excel IMPLEMENTATION.
  METHOD lif_exportador~exportar.
    CHECK validar_dados( it_dados ) = abap_true.

    rv_resultado = |Excel Workbook com { lines( it_dados ) } linhas|.

    WRITE: / |✅ Exportado { lines( it_dados ) } linhas para Excel|.
  ENDMETHOD.

  METHOD lif_exportador~validar_dados.
    rv_valido = COND #( WHEN it_dados IS NOT INITIAL THEN abap_true
                        ELSE abap_false ).
  ENDMETHOD.

  METHOD lif_exportador~obter_extensao.
    rv_ext = 'xlsx'.
  ENDMETHOD.
ENDCLASS.

" ========== FACTORY ==========
CLASS lcl_exportador_factory DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      criar
        IMPORTING iv_formato TYPE string
        RETURNING VALUE(ro_exportador) TYPE REF TO lif_exportador
        RAISING cx_parameter_invalid,
      formatos_disponiveis
        RETURNING VALUE(rt_formatos) TYPE string_table.
ENDCLASS.

CLASS lcl_exportador_factory IMPLEMENTATION.
  METHOD criar.
    CASE to_upper( iv_formato ).
      WHEN 'CSV'.
        ro_exportador = NEW lcl_exportador_csv( ).

      WHEN 'JSON'.
        ro_exportador = NEW lcl_exportador_json( ).

      WHEN 'XML'.
        ro_exportador = NEW lcl_exportador_xml( ).

      WHEN 'EXCEL' OR 'XLSX'.
        ro_exportador = NEW lcl_exportador_excel( ).

      WHEN OTHERS.
        RAISE EXCEPTION TYPE cx_parameter_invalid.
    ENDCASE.
  ENDMETHOD.

  METHOD formatos_disponiveis.
    rt_formatos = VALUE #(
      ( |CSV| )
      ( |JSON| )
      ( |XML| )
      ( |EXCEL| )
    ).
  ENDMETHOD.
ENDCLASS.

" ========== SERVIÇO DE EXPORTAÇÃO ==========
CLASS lcl_servico_exportacao DEFINITION.
  PUBLIC SECTION.
    METHODS:
      exportar_dados
        IMPORTING it_dados TYPE STANDARD TABLE
                  iv_formato TYPE string.
ENDCLASS.

CLASS lcl_servico_exportacao IMPLEMENTATION.
  METHOD exportar_dados.
    TRY.
        " Factory cria o exportador apropriado
        DATA(lo_exportador) = lcl_exportador_factory=>criar( iv_formato ).

        " Usar o exportador
        DATA(lv_resultado) = lo_exportador->exportar( it_dados ).
        DATA(lv_extensao) = lo_exportador->obter_extensao( ).

        WRITE: / |Arquivo: dados.{ lv_extensao }|.

      CATCH cx_parameter_invalid.
        WRITE: / |❌ Formato inválido: { iv_formato }|.
        WRITE: / 'Formatos disponíveis:'.
        LOOP AT lcl_exportador_factory=>formatos_disponiveis( ) INTO DATA(lv_fmt).
          WRITE: / |  - { lv_fmt }|.
        ENDLOOP.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

" ========== PROGRAMA PRINCIPAL ==========
START-OF-SELECTION.

  " Dados de exemplo
  DATA: lt_dados TYPE TABLE OF string.
  lt_dados = VALUE #( ( |Linha 1| ) ( |Linha 2| ) ( |Linha 3| ) ).

  DATA(lo_servico) = NEW lcl_servico_exportacao( ).

  " Exportar para diferentes formatos
  WRITE: / '╔═══════════════════════════════════╗'.
  WRITE: / '║  SISTEMA DE EXPORTAÇÃO            ║'.
  WRITE: / '╚═══════════════════════════════════╝'.
  SKIP.

  WRITE: / '─── Exportar para CSV ───'.
  lo_servico->exportar_dados(
    it_dados = lt_dados
    iv_formato = 'CSV'
  ).
  SKIP.

  WRITE: / '─── Exportar para JSON ───'.
  lo_servico->exportar_dados(
    it_dados = lt_dados
    iv_formato = 'JSON'
  ).
  SKIP.

  WRITE: / '─── Exportar para XML ───'.
  lo_servico->exportar_dados(
    it_dados = lt_dados
    iv_formato = 'XML'
  ).
  SKIP.

  WRITE: / '─── Exportar para Excel ───'.
  lo_servico->exportar_dados(
    it_dados = lt_dados
    iv_formato = 'EXCEL'
  ).
  SKIP.

  WRITE: / '─── Formato Inválido ───'.
  lo_servico->exportar_dados(
    it_dados = lt_dados
    iv_formato = 'PDF'
  ).

🎯 Vantagens do Factory Pattern

Vantagem Descrição
Desacoplamento Cliente não conhece classes concretas
Centralização Lógica de criação num único lugar
Flexibilidade Fácil adicionar novos tipos
Manutenção Mudanças isoladas na factory
Testabilidade Fácil mockar objetos

🔹 Variações do Padrão

1. Static Factory Method

CLASS lcl_pessoa DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      criar_adulto RETURNING VALUE(ro_pessoa) TYPE REF TO lcl_pessoa,
      criar_crianca RETURNING VALUE(ro_pessoa) TYPE REF TO lcl_pessoa.
ENDCLASS.

2. Factory com Parâmetros

CLASS lcl_factory DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      criar
        IMPORTING iv_tipo TYPE string
                  iv_config TYPE any OPTIONAL
        RETURNING VALUE(ro_obj) TYPE REF TO lif_interface.
ENDCLASS.

3. Factory com Registro

CLASS lcl_factory DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS:
      registrar IMPORTING iv_tipo TYPE string
                          io_criador TYPE REF TO object,
      criar IMPORTING iv_tipo TYPE string
            RETURNING VALUE(ro_obj) TYPE REF TO object.

  PRIVATE SECTION.
    CLASS-DATA: st_registro TYPE HASHED TABLE OF ... WITH UNIQUE KEY tipo.
ENDCLASS.

💡 Boas Práticas

✅ Fazer

" 1. Retornar interfaces, não classes concretas
CLASS-METHODS criar RETURNING VALUE(ro_obj) TYPE REF TO lif_interface.

" 2. Lançar exceções para tipos inválidos
RAISE EXCEPTION TYPE cx_parameter_invalid.

" 3. Usar métodos estáticos para factories simples
CLASS-METHODS criar_pdf RETURNING VALUE(ro_doc) TYPE REF TO lif_documento.

" 4. Validar parâmetros
IF iv_tipo IS INITIAL.
  RAISE EXCEPTION TYPE cx_parameter_invalid.
ENDIF.

❌ Evitar

" 1. Factory que retorna tipos concretos
CLASS-METHODS criar RETURNING VALUE(ro_obj) TYPE REF TO lcl_classe_concreta.  " ❌

" 2. Factory sem tratamento de erros
CASE iv_tipo.
  WHEN 'A'. ro_obj = NEW lcl_a( ).
  " E se não for 'A'?  " ❌
ENDCASE.

" 3. Lógica de negócio na factory
" Factory deve APENAS criar objetos

" 4. Factory muito complexa
" Se ficou complexa demais, refatore

🔗 Próximos Passos


Tags: #OO #DesignPatterns #Factory #CreationalPatterns #ABAP