Análise e teste de código
Utilização correta de tipos de dados e conversões de tipo
Processamento de campos de caracteres
Uso de push-down de código no ABAP SQL
Melhorar o desempenho da tabela interna
Implementar verificações de autorização
Projetando código efetivo orientado a objetos
Definir e trabalhar com classes de exceção
Adicionar documentação à codificação ABAP

Implementar métodos factory

Objective

After completing this lesson, you will be able to utilizar métodos factory.

Métodos factory

Às vezes, uma classe precisa manter o controle de suas instâncias. Isso pode ser para evitar que várias instâncias sejam criadas. Para isso, ele deve impedir que os próprios usuários criem instâncias. Você efetua isso no ABAP utilizando o suplemento CREATE PRIVATE na instrução CLASS DEFINITION. Qualquer tentativa de usar o operador NEW fora da classe levará agora a um erro de sintaxe. Note que outras linguagens de programação alcançam esse efeito alterando a visibilidade do método construtor. O ABAP não suporta isso.

Uma vez que os usuários da classe não podem criar suas próprias instâncias, a classe deve fornecer um método estático público que crie instâncias e retorne as mesmas ao usuário. Isso é chamado de método factory.

Teste: implementação de métodos factory

  1. Crie uma nova classe que implemente a interface IF_OO_ADT_CLASSRUN.
  2. Mude para a guia Tipos locais e copie o seguinte snippet de código para o editor:
    Code Snippet
    12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
    CLASS lcl_connection DEFINITION CREATE PRIVATE. PUBLIC SECTION. METHODS constructor IMPORTING airlineid TYPE /dmo/carrier_id connectionnumber TYPE /dmo/connection_id fromAirport TYPE /dmo/airport_from_id toAirport TYPE /dmo/airport_to_id. CLASS-METHODS get_connection IMPORTING airlineId TYPE /dmo/carrier_id connectionNumber TYPE /dmo/connection_id RETURNING VALUE(ro_connection) TYPE REF TO lcl_Connection. PRIVATE SECTION. DATA AirlineId TYPE /dmo/carrier_id. DATA ConnectionNumber TYPE /dmo/connection_id. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. ENDCLASS. CLASS lcl_connection IMPLEMENTATION. METHOD constructor. me->airlineid = airlineid. me->connectionnumber = connectionnumber. me->fromAirport = fromAirport. me->toAirport = toAirport. ENDMETHOD. METHOD get_connection. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. SELECT SINGLE FROM /dmo/connection FIELDS airport_from_id, airport_to_id WHERE carrier_id = @airlineid AND connection_id = @connectionnumber INTO ( @fromAirport, @toAirport ). ro_connection = NEW #( airlineid = airlineid connectionnumber = connectionnumber fromairport = fromairport toairport = toairport ). ENDMETHOD. ENDCLASS.
  3. Mude para a ficha Classe global e cole o seguinte snippet de código na implementação do método if_oo_adt_classrun~main( ):
    Code Snippet
    12345678910111213141516171819202122232425262728293031323334
    CLASS zcl_s4d401_factory DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. interfaces if_oo_adt_classrun. PROTECTED SECTION. PRIVATE SECTION. ENDCLASS. CLASS zcl_s4d401_factory IMPLEMENTATION. METHOD if_oo_adt_classrun~main. data connection type ref to lcl_connection. * Debug the method to show that the class returns objects, but that there are different * objects for the same combination of airline and flight number connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). ENDMETHOD. ENDCLASS.
  4. Ative a classe com Ctrl + F3.
  5. Jogue com a codificação. Em particular, depure o método main() e anote os números internos da instância atribuídos aos objetos retornados pelo método factory.

Padrão Singleton

O objetivo do modelo singleton é garantir que existe somente uma instância de uma classe em uma aplicação. Isso pode ser uma única instância da classe ou, no nosso caso, uma única instância para cada combinação de atributos-chave.

Assista aos vídeos a seguir para saber mais sobre o Padrão Singleton.

Teste: implementação de modelo Singleton

  1. Crie uma nova classe que implemente a interface IF_OO_ADT_CLASSRUN.
  2. Mude para a guia Tipos locais e copie o seguinte snippet de código para o editor:
    Code Snippet
    1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
    CLASS lcl_connection DEFINITION CREATE PRIVATE. PUBLIC SECTION. METHODS constructor IMPORTING airlineid TYPE /dmo/carrier_id connectionnumber TYPE /dmo/connection_id fromAirport TYPE /dmo/airport_from_id toAirport TYPE /dmo/airport_to_id. CLASS-METHODS get_connection IMPORTING airlineId TYPE /dmo/carrier_id connectionNumber TYPE /dmo/connection_id RETURNING VALUE(ro_connection) TYPE REF TO lcl_Connection. PRIVATE SECTION. TYPES: BEGIN OF ts_instance, airlineId TYPE /dmo/carrier_id, connectionNumber TYPE /dmo/connection_id, object TYPE REF TO lcl_connection, END OF ts_instance, tt_instances TYPE HASHED TABLE OF ts_instance WITH UNIQUE KEY airlineId ConnectionNumber. DATA AirlineId TYPE /dmo/carrier_id. DATA ConnectionNumber TYPE /dmo/connection_id. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. CLASS-DATA connections TYPE tt_instances. ENDCLASS. CLASS lcl_connection IMPLEMENTATION. METHOD constructor. me->airlineid = airlineid. me->connectionnumber = connectionnumber. me->fromAirport = fromAirport. me->toAirport = toAirport. ENDMETHOD. METHOD get_connection. DATA fromAirport TYPE /dmo/airport_from_id. DATA toAirport TYPE /dmo/airport_to_id. IF NOT line_exists( connections[ airlineid = airlineid connectionnumber = connectionnumber ] ). SELECT SINGLE FROM /dmo/connection FIELDS airport_from_id, airport_to_id WHERE carrier_id = @airlineid AND connection_id = @connectionnumber INTO ( @fromAirport, @toAirport ). ro_connection = NEW #( airlineid = airlineid connectionnumber = connectionnumber fromairport = fromairport toairport = toairport ). DATA(new_instance) = VALUE ts_instance( airlineId = airlineId connectionnumber = connectionnumber object = ro_connection ). INSERT new_instance INTO TABLE connections. ELSE. ro_connection = connections[ airlineId = airlineId connectionnumber = connectionnumber ]-object. ENDIF. ENDMETHOD. ENDCLASS.
  3. Mude para a ficha Classe global e cole o seguinte snippet de código na implementação do método if_oo_adt_classrun~main( ):
    Code Snippet
    123456789101112131415161718192021222324252627282930313233
    CLASS zcl_s4d401_factory_singleton DEFINITION PUBLIC FINAL CREATE PUBLIC . PUBLIC SECTION. interfaces if_oo_adt_classrun. PROTECTED SECTION. PRIVATE SECTION. ENDCLASS. CLASS zcl_s4d401_factory_singleton IMPLEMENTATION. METHOD if_oo_adt_classrun~main. data connection type ref to lcl_connection. * Debug the method to show that the class always returns the same object * for the same combination of airline and flight number connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). connection = lcl_connection=>get_connection( airlineid = 'LH' connectionnumber = '0400' ). ENDMETHOD. ENDCLASS.
  4. Ative a classe com Ctrl + F3.
  5. Jogue com a codificação. Em particular, depure o método main() e anote os números internos da instância atribuídos aos objetos retornados pelo método factory.

Implementar um método factory

Você percebe que seu código, na sua forma atual, permite que várias instâncias de LCL_CARRIER existam, representando a mesma companhia aérea. Para corrigir isso, você definirá e implementará um método factory que garante que somente uma instância possa ser criada para um determinado valor de ID de transportadora.

Modelo:

  • /LRN/CL_S4D401_OOS_INTF_USAGE (classe global)

Solução:

  • /LRN/CL_S4D401_OOS_FACTORY (classe global)

Tarefa 1: Copiar modelo (opcional)

Copie a classe modelo /LRN/CL_S4D401_OOS_INTF_USAGE. Se você tiver concluído o exercício anterior, pode ignorar esta tarefa e continuar processando sua classe ZCL_##_SOLUTION.

Etapas

  1. Copie a classe /LRN/CL_S4D401_OOS_INTF_USAGE para uma classe em seu próprio pacote (nome sugerido: ZCL_##_SOLUTION, onde ## representa seu número de grupo).

    1. No Explorador de projetos, clique com o botão direito do mouse na classe /LRN/CL_S4D401_OOS_INTF_USAGE para abrir o menu de contexto.

    2. No menu de contexto, selecione Duplicar....

    3. Insira o nome do seu pacote no campo Pacote. No campo Nome, insira ZCL_##_SOLUTION, em que ## representa seu número de grupo.

    4. Selecione Avançar.

    5. Confirme a ordem de transporte e selecione Concluir.

  2. Ative a cópia.

    1. Pressione Ctrl + F3 para ativar a classe.

Tarefa 2: Definir um método factory

Utilize uma correção rápida para definir um método factory para a classe local LCL_CARRIER (nome proposto: GET_INSTANCE). No método factory, execute a verificação de existência e a verificação de autorização antes de criar realmente uma instância. Em seguida, restrinja a criação de instância para esta classe para forçar a utilização do método factory.

Etapas

  1. Na classe local LCL_CARRIER, utilize uma solução rápida para definir um método factory.

    Dica

    Os parâmetros do construtor são adicionados à assinatura do método factory por padrão. Por isso, não selecione explicitamente o atributo carrier_id como atributo a ser inicializado.
    1. Navegue para a definição de LCL_CARRIER.

    2. Na instrução CLASS … DEFINITION, posicione o cursor em lcl_carrier e pressione Ctrl + 1 para chamar as correções rápidas disponíveis.

    3. A partir da lista de soluções rápidas disponíveis, selecione Gerar criação de método de fábrica.

    4. No campo Nome, insira GET_INSTANCE.

    5. Na seção Selecionar atributos para inicializar, selecione Desmarcar tudo e, em seguida, selecione Concluir.

    6. A correção rápida deve ter adicionado a seguinte definição de método:

      ABAP
      12345
      CLASS-METHODS get_instance IMPORTING i_carrier_id TYPE /dmo/carrier_id RETURNING value(r_result) TYPE REF TO lcl_carrier.

      … e a seguinte implementação:

      ABAP
      123
      METHOD get_instance. r_result = NEW #( i_carrier_id = i_carrier_id ). ENDMETHOD.
  2. Desloque a instrução SELECT e a instrução AUTHORITY-CHECK do CONSTRUCTOR para a implementação do método factory. Certifique-se de executar as verificações antes da instanciação.

    1. Navegue para a implementação do método CONSTRUCTOR da classe local LCL_CARRIER.

    2. Selecione a instrução SELECT e a instrução AUTHORITY-CHECK, incluindo a avaliação do campo do sistema SY-SUBRC e o acionamento da exceção, e pressione Ctrl + C para copiar o snippet de código para o clipboard.

    3. Pressione Ctrl + < para adicionar um sinal de comentário na frente de cada linha de código selecionada.

    4. Navegue para a implementação do método GET_INSTANCE, posicione o cursor antes da atribuição de valor para o parâmetro r_result e pressione Ctrl + V para inserir o snippet de código.

    5. O código deve ter o seguinte aspecto:

      ABAP
      12345678910111213141516171819202122
      METHOD get_instance. SELECT SINGLE FROM /lrn/carrier FIELDS concat_with_space( carrier_id, name, 1 ), currency_code WHERE carrier_id = @i_carrier_id INTO ( @me->name, @me->currency_code ). IF sy-subrc <> 0. RAISE EXCEPTION TYPE cx_abap_invalid_value. ENDIF. AUTHORITY-CHECK OBJECT '/LRN/CARR' ID '/LRN/CARR' FIELD i_carrier_id ID 'ACTVT' FIELD '03'. IF sy-subrc <> 0. RAISE EXCEPTION TYPE cx_abap_auth_check_exception. ENDIF. r_result = NEW #( i_carrier_id = i_carrier_id ). ENDMETHOD.
  3. Uma vez que você removeu as instruções RAISE EXCEPTION da implementação do método CONSTRUCTOR, elimine agora os dois suplementos RAISING correspondentes na assinatura da definição de método CONSTRUCTOR.

    1. Navegue para a definição do método CONSTRUCTOR em LCL_CARRIER.

    2. Ajuste a definição do método da seguinte forma:

      ABAP
      123456
      METHODS constructor IMPORTING i_carrier_id TYPE /dmo/carrier_id * RAISING cx_abap_invalid_value * cx_abap_auth_check_exception .
  4. Volte para a implementação do método GET_INSTANCE. Na instrução SELECT, ajuste a cláusula INTO. Armazena o resultado em uma estrutura declarada inline (nome sugerido: detalhes) em vez de nome de atributos de instância e currency_code.

    1. Ajuste o código da seguinte forma:

      ABAP
      1234567
      SELECT SINGLE FROM /lrn/carrier FIELDS concat_with_space( carrier_id, name, 1 ) AS name, currency_code WHERE carrier_id = @i_carrier_id * INTO ( @me->name, @me->currency_code ). INTO @DATA(details).
  5. Após a instanciação do objeto de suporte, atribua os valores da estrutura detalhada aos atributos de instância correspondentes da nova instância.

    1. Ajuste o código da seguinte forma:

      ABAP
      12345
      r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. ENDMETHOD.
  6. Utilize uma correção rápida para adicionar declarações de aumento para as duas exceções.

    1. Selecione o ícone de advertência com a lâmpada no lado esquerdo da primeira instrução RAISE EXCEPTION para chamar as soluções rápidas disponíveis.

    2. Na lista de soluções rápidas disponíveis, selecione Adicionar declaração de criação.

    3. Repita isso para a outra instrução RAISE EXCEPTION.

    4. A correção rápida deve ter ajustado o código da seguinte forma:

      ABAP
      12345678
      CLASS-METHODS get_instance IMPORTING i_carrier_id TYPE /dmo/carrier_id RETURNING value(r_result) TYPE REF TO lcl_carrier RAISING cx_abap_invalid_value cx_abap_auth_check_exception.
  7. Restrinja a instanciação da classe local LCL_CARRIER. Certifique-se de que a instanciação só é permitida dentro da própria classe.

    1. Navegue para a definição de LCL_CARRIER.

    2. Ajuste o código da seguinte forma:

      ABAP
      1
      CLASS lcl_carrier DEFINITION CREATE PRIVATE.
  8. Desloque a definição do método CONSTRUCTOR para a seção privada.

    Nota

    Isso é opcional, mas deve ser feito para melhorar a legibilidade.
    1. Copie e cole a instrução METHODS para o método CONSTRUCTOR da seção pública para a seção privada.

  9. No método IF_OO_ADT_CLASSRUN~MAIN da classe global, substitua a expressão NEW por uma chamada do método factory GET_INSTANCE.

    1. Ajuste o código da seguinte forma:

      ABAP
      12
      * DATA(carrier) = NEW lcl_carrier( i_carrier_id = c_carrier_id ). DATA(carrier) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
  10. No método CLASS_SETUP da classe de teste local LTCL_FIND_FLIGHTS, substitua a expressão NEW por uma chamada do método factory GET_INSTANCE.

    1. Ajuste o código da seguinte forma:

      ABAP
      12
      * the_carrier = NEW lcl_carrier( i_carrier_id = some_flight_data-carrier_id ). the_carrier = lcl_carrier=>get_instance( i_carrier_id = some_flight_data-carrier_id ).
  11. Ative e teste sua classe global como aplicativo de console. Depurar a execução do método GET_INSTANCE.

    1. Pressione Ctrl + F3.

    2. Pressione F9.

Tarefa 3: Ampliar o método factory

Na classe local LCL_CARRIER, defina um atributo estático no qual você pode armazenar referências às instâncias de LCL_CARRIER (nome sugerido: instâncias). Em seguida, estenda a implementação do método factory: após a criação de uma instância, armazene a referência à nova instância em instâncias de atributo. Crie uma nova instância somente se as instâncias ainda não referenciarem um objeto para o ID de agente de frete solicitado.

Etapas

  1. Na classe local LCL_CARRIER, utilize uma correção rápida para criar um tipo de tabela para esta classe (nome proposto: tt_carrier).

    1. Navegue para a definição da classe local LC_CARRIER.

    2. Na instrução CLASS … DEFINITION, posicione o cursor em lcl_carrier e pressione Ctrl + 1 para chamar as correções rápidas disponíveis.

    3. A partir da lista de soluções rápidas disponíveis, selecione Gerar tipo de tabela para lcl_carrier.

    4. Na nova instrução TYPES, substitua a ficha de registro por tt_carrier.

    5. A instrução TYPES deve ter o seguinte aspecto:

      ABAP
      12
      TYPES: tt_carriers TYPE STANDARD TABLE OF REF TO lcl_carrier WITH DEFAULT KEY.
  2. Defina um atributo estático privado com base no tipo de tabela recém-criado (nome sugerido: instâncias).

    1. Role para baixo até a instrução PRIVATE SECTION.

    2. Ajuste o código da seguinte forma:

      ABAP
      12
      PRIVATE SECTION. CLASS-DATA instances TYPE tt_carriers.
  3. Ajuste a implementação do método factory GET_INSTANCE. Após a instanciação, adicione uma referência à nova instância ao atributo de instâncias.

    1. Ajuste o código da seguinte forma:

      ABAP
      123456
      r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. APPEND r_result TO instances. ENDMETHOD.
  4. Antes da instanciação, mas após as verificações de existência e de autorização, implemente um registro individual lido a partir de instâncias de atributo. Leia a instância na qual o valor do atributo carrier_id é igual ao valor do parâmetro de importação i_carrier_id. Arquive o resultado no parâmetro de retorno r_result.

    Dica

    Lembre-se de que, em tabelas internas com tipo de linha não estruturado, você aborda os dados usando pseudocomponente table_line.
    1. Ajuste o código da seguinte forma:

      ABAP
      1234567891011
      IF sy-subrc <> 0. RAISE EXCEPTION TYPE cx_abap_auth_check_exception. ENDIF. r_result = instances[ table_line->carrier_id = i_carrier_id ]. r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. APPEND r_result TO instances.
  5. Envolver o acesso de leitura de registro individual, a instanciação, as avaliações dos atributos e a instrução APPEND com um TRY... Estrutura de controle ENDTRY.

    1. Ajuste o código da seguinte forma:

      ABAP
      123456789
      TRY. r_result = instances[ table_line->carrier_id = i_carrier_id ]. r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. APPEND r_result TO instances. ENDTRY.
  6. Certifique-se de que você só cria uma instância e a adiciona a instâncias de atributo, se a exceção de sistema CX_SY_ITAB_LINE_NOT_FOUND for acionada.

    1. Ajuste o código da seguinte forma:

      ABAP
      123456789
      TRY. r_result = instances[ table_line->carrier_id = i_carrier_id ]. CATCH cx_sy_itab_line_not_found. r_result = NEW #( i_carrier_id = i_carrier_id ). r_result->name = details-name. r_result->currency_code = details-currency_code. APPEND r_result TO instances. ENDTRY.
  7. Na sua classe global, ajuste a implementação do método IF_OO_ADT_CLASSRUN~MAIN: implemente uma segunda chamada para o método factory GET_INSTANCE com a mesma entrada que a chamada existente, mas uma variável de referência diferente para o resultado (nome proposto: carrier2).

    1. Ajuste o código da seguinte forma:

      ABAP
      12
      DATA(carrier) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ). DATA(carrier2) = lcl_carrier=>get_instance( i_carrier_id = c_carrier_id ).
  8. Ative e teste sua classe global como aplicativo de console. Depurar o código para garantir que as duas chamadas do método factory retornam a mesma instância de LCL_CARRIER.

    1. Pressione Ctrl + F3.

    2. Pressione F9.