コードの分析およびテスト
データ型およびデータ型変換の正しい使用
文字項目の処理
ABAP SQL でのコードプッシュダウンの使用
内部テーブルのパフォーマンスの改善
権限チェックの実装
効果的なオブジェクト指向コードの設計
例外クラスの定義と処理
ABAP コードへの文書の追加

ファクトリメソッドの実装

Objective

After completing this lesson, you will be able to ファクトリメソッドを使用します。

ファクトリメソッド

クラスがそのインスタンスを制御しなければならない場合があります。これは、複数のインスタンスが登録されないようにするためです。そのためには、ユーザが自身でインスタンスを作成できないようにする必要があります。これは、ABAP で CLASS DEFINITION 命令の CREATE PRIVATE オプションを使用して行います。クラス外で NEW 演算子を使用しようとすると、構文エラーが発生します。他のプログラミング言語では、コンストラクタメソッドの可視性を変更することによって、この効果が得られることに注意してください。ABAP では、これはサポートされていません。

クラスのユーザは独自のインスタンスを作成できないため、クラスはインスタンスを作成してユーザに返すパブリック静的メソッドを提供する必要があります。これはファクトリメソッドと呼ばれます。

試してみる: ファクトリメソッドの実装

  1. インタフェース IF_OO_ADT_CLASSRUN を実装する新しいクラスを登録します。
  2. Local Types タブに切り替えて、以下のコードスニペットをエディタにコピーします。
    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. Global Class タブに切り替えて、以下のコードスニペットをメソッド 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. Ctrl + F3 を使用してクラスを有効化します。
  5. コーディングを使用します。特に、メソッド main() をデバッグし、ファクトリメソッドによって返されるオブジェクトに割り当てられた内部インスタンス番号を書き留めます。

シングルトンパターン

シングルトンパターンの目的は、アプリケーション内にクラスのインスタンスが 1 つだけ存在するようにすることです。これは、クラスの単一インスタンスの場合もあれば、キー属性の組合せごとに 1 つのインスタンスの場合もあります。

シングルトンパターンの詳細については、以下のビデオを視聴してください。

試してみる: シングルトンパターン実装

  1. インタフェース IF_OO_ADT_CLASSRUN を実装する新しいクラスを登録します。
  2. Local Types タブに切り替えて、以下のコードスニペットをエディタにコピーします。
    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. Global Class タブに切り替えて、以下のコードスニペットをメソッド 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. Ctrl + F3 を使用してクラスを有効化します。
  5. コーディングを使用します。特に、メソッド main() をデバッグし、ファクトリメソッドによって返されるオブジェクトに割り当てられた内部インスタンス番号を書き留めます。

ファクトリメソッドの実装

現在の形式のコードで、同じ航空会社を表す LCL_CARRIER の複数のインスタンスが存在することが許可されていることがわかります。これを修正するには、特定の運送業者 ID 値に対して 1 つのインスタンスのみを作成できるようにするファクトリメソッドを定義および実装します。

テンプレート:

  • /LRN/CL_S4D401_OOS_INTF_USAGE (グローバルクラス)

ソリューション:

  • /LRN/CL_S4D401_OOS_FACTORY (グローバルクラス)

タスク 1: テンプレートのコピー (オプション)

テンプレートクラス /LRN/CL_S4D401_OOS_INTF_USAGE をコピーします。前の演習問題を完了した場合は、このタスクをスキップし、クラス ZCL_##_SOLUTION の編集を続行することができます。

ステップ

  1. クラス /LRN/CL_S4D401_OOS_INTF_USAGE を独自のパッケージのクラスにコピーします (推奨名: ZCL_##_SOLUTION、## はグループ番号)。

    1. Project Explorer で、クラス /LRN/CL_S4D401_OOS_INTF_USAGE を右クリックしてコンテキストメニューを開きます。

    2. コンテキストメニューから Duplicate.... を選択します。

    3. Package 項目にパッケージの名称を入力します。Name 項目に、ZCL_##_SOLUTION (## はグループ番号) と入力します。

    4. Next を選択します。

    5. 移送依頼を確認し、Finish を選択します。

  2. コピーを有効化します。

    1. Ctrl + F3 を押してクラスを有効化します。

タスク 2: ファクトリメソッドの定義

クイックフィックスを使用して、ローカルクラス LCL_CARRIER のファクトリメソッドを定義します (推奨名: GET_INSTANCE)。ファクトリメソッドでは、インスタンスを実際に作成する前に、存在チェックおよび権限チェックを実行します。次に、このクラスのインスタンス作成を制限して、ファクトリメソッドの使用を強制します。

ステップ

  1. ローカルクラス LCL_CARRIER で、クイックフィックスを使用してファクトリメソッドを定義します。

    ヒント

    コンストラクタパラメータは、デフォルトでファクトリメソッドの署名に追加されます。そのため、carrier_id 属性を初期化対象の属性として明示的に選択しないでください。
    1. LCL_CARRIER の定義にナビゲートします。

    2. CLASS … DEFINITION 命令で、lcl_carrier にカーソルを置き、Ctrl + 1 を押して利用可能なクイックフィックスを呼び出します。

    3. 利用可能なクイックフィックスの一覧から、Generate factory method create を選択します。

    4. 名称項目に GET_INSTANCE と入力します。

    5. 初期化する属性を選択セクションで、全選択解除を選択し、終了を選択します。

    6. クイックフィックスで以下のメソッド定義を追加しておく必要があります。

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

      ... および以下の実装:

      ABAP
      123
      METHOD get_instance. r_result = NEW #( i_carrier_id = i_carrier_id ). ENDMETHOD.
  2. SELECT 命令と AUTHORITY-CHECK 命令を CONSTRUCTOR からファクトリメソッドの実装に移動します。インスタンス化前に必ずチェックを実行してください。

    1. ローカルクラス LCL_CARRIER のメソッド CONSTRUCTOR の実装にナビゲートします。

    2. SELECT 命令と AUTHORITY-CHECK 命令 (システム項目 SY-SUBRC の評価と例外発生を含む) を選択し、Ctrl + C を押してコードスニペットをクリップボードにコピーします。

    3. Ctrl + < を押して、選択した各コード行の前にコメント記号を追加します。

    4. メソッド GET_INSTANCE の実装にナビゲートし、パラメータ r_result の値割当の前にカーソルを置き、Ctrl + V を押してコードスニペットを挿入します。

    5. コードは以下のようになります。

      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. CONSTRUCTOR メソッドの実装から RAISE EXCEPTION 命令を削除したため、CONSTRUCTOR メソッド定義の署名にある 2 つの対応する RAISING オプションを削除します。

    1. LCL_CARRIERCONSTRUCTOR メソッドの定義にナビゲートします。

    2. 以下のようにメソッド定義を調整します。

      ABAP
      123456
      METHODS constructor IMPORTING i_carrier_id TYPE /dmo/carrier_id * RAISING cx_abap_invalid_value * cx_abap_auth_check_exception .
  4. GET_INSTANCE メソッドの実装に戻ります。SELECT 文で、INTO 句を調整します。インスタンス属性の名前および currency_code ではなく、インライン宣言構造体 (推奨名: details) に結果を格納します。

    1. コードを以下のように調整します。

      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. 運送業者オブジェクトのインスタンス化後、詳細構造の値を新規インスタンスの対応するインスタンス属性に割り当てます。

    1. コードを以下のように調整します。

      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. クイックフィックスを使用して、2 つの例外の発生宣言を追加します。

    1. 最初の RAISE EXCEPTION 命令の左側にある電球の警告アイコンを選択して、利用可能なクイックフィックスを呼び出します。

    2. 利用可能なクイックフィックスの一覧から、Add raising declaration を選択します。

    3. 他の RAISE EXCEPTION 命令に対してこれを繰り返します。

    4. クイックフィックスによって、コードが以下のように調整されている必要があります。

      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. ローカルクラス LCL_CARRIER のインスタンス化を制限します。インスタンス化がクラス自体内でのみ許可されることを確認してください。

    1. LCL_CARRIER の定義にナビゲートします。

    2. コードを以下のように調整します。

      ABAP
      1
      CLASS lcl_carrier DEFINITION CREATE PRIVATE.
  8. CONSTRUCTOR メソッドの定義をプライベートセクションに移動します。

    注記

    これはオプションですが、読みやすくするために実行する必要があります。
    1. メソッド CONSTRUCTOR の METHODS 命令を public セクションから private セクションにコピー & ペーストします。

  9. グローバルクラスの IF_OO_ADT_CLASSRUN~MAIN メソッドで、NEW 式をファクトリメソッド GET_INSTANCE の呼出に置き換えます。

    1. コードを以下のように調整します。

      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. ローカルテストクラス LTCL_FIND_FLIGHTS のメソッド CLASS_SETUP で、NEW 式をファクトリメソッド GET_INSTANCE の呼出に置き換えます。

    1. コードを以下のように調整します。

      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. グローバルクラスをコンソールアプリとして有効化してテストします。GET_INSTANCE メソッドの実行をデバッグします。

    1. Ctrl + F3 を押します。

    2. F9 を押します。

タスク 3: ファクトリメソッドの拡張

ローカルクラス LCL_CARRIER で、LCL_CARRIER (推奨名: instances) のインスタンスへの参照を保存できる静的属性を定義します。次に、ファクトリメソッドの実装を拡張します。インスタンスの作成後、新しいインスタンスへの参照を属性インスタンスに保存します。要求された運送業者 ID のオブジェクトがまだインスタンスで参照されていない場合にのみ、新しいインスタンスを作成します。

ステップ

  1. ローカルクラス LCL_CARRIER で、クイックフィックスを使用して、このクラスのテーブルデータ型を登録します (推奨名: tt_carriers)。

    1. ローカルクラス LC_CARRIER の定義にナビゲートします。

    2. CLASS … DEFINITION 命令で、lcl_carrier にカーソルを置き、Ctrl + 1 を押して利用可能なクイックフィックスを呼び出します。

    3. 利用可能なクイックフィックスの一覧から、Generate table type for lcl_carrier を選択します。

    4. 新しい TYPES 命令で、tabtt_carriers に置き換えます。

    5. TYPES 命令は以下のようになります。

      ABAP
      12
      TYPES: tt_carriers TYPE STANDARD TABLE OF REF TO lcl_carrier WITH DEFAULT KEY.
  2. 先ほど作成したテーブルデータ型に基づいてプライベート静的属性を定義します (推奨名: instances)。

    1. PRIVATE SECTION 命令まで下にスクロールします。

    2. コードを以下のように調整します。

      ABAP
      12
      PRIVATE SECTION. CLASS-DATA instances TYPE tt_carriers.
  3. ファクトリメソッド GET_INSTANCE の実装を調整します。インスタンス化の後、新しいインスタンスへの参照をインスタンス属性に追加します。

    1. コードを以下のように調整します。

      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. インスタンス化の、ただし存在チェックおよび権限チェック後に、属性インスタンスから読み込まれた単一レコードを実装します。属性 carrier_id の値がインポートパラメータ i_carrier_id の値と等しいインスタンスを読み込みます。リターンパラメータ r_result に結果を保存します。

    ヒント

    非構造化行データ型の内部テーブルでは、擬似コンポーネント table_line を使用してデータをアドレス指定することに注意してください。
    1. コードを以下のように調整します。

      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. 単一レコード読込アクセス、インスタンス化、属性への値割当、および TRY...ENDTRY 制御構造。

    1. コードを以下のように調整します。

      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. システム例外 CX_SY_ITAB_LINE_NOT_FOUND が発生した場合にのみ、インスタンスを登録し、それを属性インスタンスに追加してください。

    1. コードを以下のように調整します。

      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. グローバルクラスで、メソッド IF_OO_ADT_CLASSRUN~MAIN の実装を調整します。GET_INSTANCE ファクトリメソッドへの 2 回目の呼出を、既存の呼出と同じ入力で、結果に対して別の参照変数を使用して実装します (推奨名: carrier2)。

    1. コードを以下のように調整します。

      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. グローバルクラスをコンソールアプリとして有効化してテストします。コードをデバッグして、ファクトリメソッドの 2 つの呼出で LCL_CARRIER の同じインスタンスが返されるようにします。

    1. Ctrl + F3 を押します。

    2. F9 を押します。