効果的なオブジェクト指向コードの設計

継承の使用

Objective

After completing this lesson, you will be able to 継承を使用します。

アップキャストおよびダウンキャスト

サブクラスをインスタンス化するには、関連するクラスの型を使用して参照変数を宣言します。次に、演算子 NEW を使用してインスタンスを作成します。コンストラクタのすべての必須パラメータに値を渡す必要があります。

スーパークラス参照の使用

スーパークラスのデータ型を持つ参照変数 (この場合は lcl_plane) は、サブクラスのデータ型を持つオブジェクトへの参照を保持することもできます。旅客機または貨物機は航空機であるため、これは理にかなっています。

ただし、スーパークラスのデータ型を持つ参照変数は、スーパークラスで定義されているコンポーネントしか認識しません。その結果、この型を持つ参照を使用してサブクラスのインスタンスを管理する場合、参照が指している実際のオブジェクトがサブクラスのインスタンスであっても、スーパークラスからのコンポーネントしかアドレスできません。

オブジェクト参照を異なる型の参照変数に割り当てることを、キャストといいます。サブクラスを指す参照をスーパークラスの型の参照変数に割り当てることをアップキャストといいます。その理由は、継承階層において 1 つ上にある型に割り当てを行うからです。

アップキャスト後のメソッドの呼出

ref to lcl_plane 型の参照変数を使用して旅客機インスタンスを管理する場合、その参照変数を使用して旅客機クラスに属するコンポーネントにアクセスすることはできません。これは、航空機クラスではそれらが存在することが認識されず、構文チェックでは実行時に参照が実際にどのタイプのオブジェクトを指すかを認識できないためです。

ただし、参照を使用して、元の定義がクラスに含まれているメソッドにアクセスすることができます。このことは、図内の例の get_attributes メソッドに当てはまります。それでは、メソッドのどの実装が呼び出されるのでしょうか。この場合、実行時には参照変数が指しているオブジェクトの型が調べられ、対応するクラスから実装が呼び出されます。つまり、旅客機クラスのインスタンスを管理するために航空機参照を使用しているものの、呼び出されるのはやはり旅客機クラスのメソッド実装である。

特定データ型へのジェネリック参照の割当

サブクラスの型を持つ参照変数を、スーパークラスの型を持つ参照変数へと常に割り当てることができます。たとえば、常に旅客機参照を航空機参照に割り当てることができます。これまで見てきたように、これは旅客機が飛行機だからです。ただし、逆方向の参照変数の割当は難しいです。これは、航空機参照に旅客機クラス以外のクラスへの参照を含めることができるためです。つまり、すべての旅客機が飛行機であり、すべての飛行機が旅客機であるとは限りません。

lcl_pane および passenger_plane のデータ型が ref to lcl_passenger_plane の plane を参照するオブジェクトを指定すると、plane = passenger_plane を書き込むことが常に許可されます。ただし、割当 passenger_plane = plane では、構文チェックで構文エラーが発生します。これは、その時点で、航空機参照が実際に旅客機オブジェクトへの参照を保持していることを十分に保証できないためです。

CAST 演算子

構文チェックでは、スーパークラス参照をサブクラス参照に直接割り当てることは許容されません。ただし、CAST 演算子を使用すれば実行できます。キャストとは、オブジェクトの型が異なるかのように見ることを意味します。この例では、"航空機" を旅客機クラスのインスタンスであるかのように扱うようにシステムに指示します。これは、式の右側にある "平面" が左側の "パッセンシャル" と同じタイプであると偽装しているため、構文的に正しいです。ただし、航空機に実際に旅客機参照が含まれている保証はなく、実行時に航空機に参照が含まれないことが判明した場合は、割当を行うことができません。保護されないままとなり、この割当により実行時エラーが発生する可能性があります。

ダウンキャストの保護

参照変数が指しているオブジェクトが実際に正しいサブクラスのインスタンスであるかどうか確信がない場合は、論理式 IS INSTANCE OF を使用してチェックすることができます。この例では、航空機が実際にクラス lcl_passenger_plane のインスタンスを指している場合にのみ、旅客機参照への割当が処理されます。

試してみる:アップキャストとダウンキャスト

  1. インタフェース IF_OO_ADT_CLASSRUN を実装する新しいクラスを登録します。
  2. Local Types タブに切り替えて、以下のコードスニペットをエディタにコピーします。
    Code Snippet
    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
    CLASS lcl_plane DEFINITION. PUBLIC SECTION. TYPES: BEGIN OF ts_attributes, name TYPE string, value TYPE string, END OF ts_attributes, * declare table - do not allow the same attribute to be used more than once tt_attributes TYPE SORTED TABLE OF ts_attributes WITH UNIQUE KEY name. METHODS constructor IMPORTING iv_manufacturer TYPE string iv_type TYPE string. METHODS: get_Attributes RETURNING VALUE(rt_Attributes) TYPE tt_attributes. PROTECTED SECTION. DATA manufacturer TYPE string. DATA type TYPE string. PRIVATE SECTION. ENDCLASS. CLASS lcl_plane IMPLEMENTATION. METHOD constructor. manufacturer = iv_manufacturer. type = iv_type. ENDMETHOD. METHOD get_attributes. rt_attributes = VALUE #( ( name = 'MANUFACTURER' value = manufacturer ) ( name = 'TYPE' value = type ) ) . ENDMETHOD. ENDCLASS. CLASS lcl_cargo_plane DEFINITION INHERITING FROM lcl_plane. PUBLIC SECTION. METHODS constructor IMPORTING iv_manufacturer TYPE string iv_type TYPE string iv_cargo TYPE i. METHODS get_attributes REDEFINITION. private section. data cargo type i. ENDCLASS. CLASS lcl_cargo_plane IMPLEMENTATION. METHOD constructor. super->constructor( iv_manufacturer = iv_manufacturer iv_type = iv_type ). cargo = iv_cargo. ENDMETHOD. METHOD get_attributes. * method uses protected attributes of superclass rt_attributes = value #( ( name = 'MANUFACTURER' value = manufacturer ) ( name = 'TYPE' value = type ) ( name ='CARGO' value = cargo ) ). ENDMETHOD. ENDCLASS. CLASS lcl_passenger_plane DEFINITION INHERITING FROM lcl_Plane. PUBLIC SECTION. METHODS constructor IMPORTING iv_manufacturer TYPE string iv_type TYPE string iv_seats TYPE i. METHODS get_Attributes REDEFINITION. private section. data seats type i. ENDCLASS. CLASS lcl_passenger_plane IMPLEMENTATION. METHOD constructor. super->constructor( iv_manufacturer = iv_manufacturer iv_type = iv_type ). ENDMETHOD. METHOD get_attributes. * Redefinition uses call of superclass implementation rt_attributes = super->get_attributes( ). rt_Attributes = value #( base rt_attributes ( name = 'SEATS' value = seats ) ). ENDMETHOD. ENDCLASS.
  3. Global Class タブに切り替えて、以下のコードスニペットをメソッド if_oo_adt_classrun~main( ) の実装にペーストします。
    Code Snippet
    12345678910111213141516171819202122232425262728293031323334353637383940414243444546
    DATA passenger TYPE REF TO lcl_passenger_Plane. DATA cargo TYPE REF TO lcl_cargo_plane. DATA plane TYPE REF TO lcl_Plane. passenger = NEW #( iv_manufacturer = 'BOEING' iv_type = '737-800' iv_seats = 130 ) . cargo = NEW #( iv_manufacturer = 'AIRBUS' iv_type = 'A340' iv_cargo = 60000 ). out->write( 'Output using passenger plane object reference' ). out->write( passenger->Get_attributes( ) ). plane = passenger. out->write( 'Output using superclass object reference' ). out->write( plane->get_attributes( ) ). * Can't use the superclass reference to get the number of seats of the * passenger plane, because the relevent method isn't declared in the superclass. * plane->get_seats( ). * Can't assign the plane directly to a passenger plane reference variable, because * not every plane is a passenger plane. *passenger = plane. * Make sure the plane is actually a passenger plane, then force the cast. IF plane IS INSTANCE OF lcl_passenger_plane. passenger = CAST #( plane ). ENDIF. ENDMETHOD.
  4. CTRL + F3 を選択してクラスを有効化し、F9 を選択してコンソールアプリとして実行します。
  5. コンセプトを理解するために、ソースコードを使用します。

スーパークラス参照の使用

前の演習問題では、クラス LCL_PASSENGER_FLIGHT および LCL_CARGO_FLIGHT の共通スーパークラス LCL_FLIGHT を登録しました。次に、ローカルクラス LCL_CARRIER を変更して、継承関係を利用します。スーパークラスのデータ型にパラメータとして使用される参照変数を入力し、2 つの個別のインスタンス属性 passenger_flights および cargo_flights の代わりに、乗客または貨物フライトに関係なく、すべてのフライトインスタンスへの参照を保存できる単一のインスタンス属性フライトを定義します。

テンプレート:

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

ソリューション:

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

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

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

ステップ

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

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

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

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

    4. Next を選択します。

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

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

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

タスク 2: 汎用パラメータ

ローカルクラス LCL_CARRIER で、メソッド FIND_PASSENGER_FLIGHT および FIND_CARGO_FLIGHT の定義を調整します。どちらのメソッドでも、ローカルクラス LCL_FLIGHT に基づいて、エクスポートパラメータ e_flight を参照変数として入力します。

ステップ

  1. ローカルクラス LCL_CARRIER で、メソッド FIND_CARGO_FLIGHT の定義を調整します。リターンパラメータ e_flight に LCL_CARGO_FLIGHT ではなく LCL_FLIGHT を入力します。必ず変更を保存してください。

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

      ABAP
      123456789
      METHODS find_cargo_flight IMPORTING i_airport_from_id TYPE /dmo/airport_from_id i_airport_to_id TYPE /dmo/airport_to_id i_from_date TYPE /dmo/flight_date i_cargo TYPE /lrn/plane_actual_load EXPORTING e_flight TYPE REF TO lcl_flight e_days_later TYPE i.
  2. メソッド FIND_CARGO_FLIGHT の実装で、ローカル変数フライトの使用を分析します。

    1. 参照変数 flight のデータ型を照会するには、変数名にカーソルを合わせ、F2 を押します。

  3. グローバルクラスのメソッド IF_OO_ADT_CLASSRUN~MAIN のメソッド FIND_CARGO_FLIGHT の呼出を分析します。

    1. 参照変数 cargo_flight のデータ型を表示するには、変数名にカーソルを合わせて F2 を押します。

  4. 同様に、メソッド FIND_PASSENGER_FLIGHT の定義を調整します。リターンパラメータ e_flightLCL_PASSENGER_FLIGHT ではなく LCL_FLIGHT を入力します。

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

      ABAP
      123456789
      METHODS find_passenger_flight IMPORTING i_airport_from_id TYPE /dmo/airport_from_id i_airport_to_id TYPE /dmo/airport_to_id i_from_date TYPE /dmo/flight_date i_seats TYPE i EXPORTING e_flight TYPE REF TO lcl_flight e_days_later TYPE i.
  5. コードを有効化して、この変更でも構文エラーが発生しないことを確認します。

    1. Ctrl + F3 を押してコードを有効化します。

タスク 3: 一般属性

ローカルクラス LCL_CARRIER で、LCL_PASSENGER_FLIGHT と LCL_CARGO_FLIGHT のインスタンスへの参照を同時に保存できるプライベートインスタンス属性フライトを定義します。CONSTRUCTOR メソッドの実装の属性に、属性 passenger_flightscargo_flights の組合せコンテンツを入力します。

ステップ

  1. ローカルクラス LCL_FLIGHT で、クイックフィックスを使用して、このクラスに基づくパブリックテーブルデータ型を生成します (テーブルタイプの推奨名: tab)。

    1. ローカルクラス LCL_FLIGHT の定義に移動します。

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

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

    4. 新しい TYPES 命令は以下のようになります。

      ABAP
      1
      TYPES tab TYPE STANDARD TABLE OF REF TO lcl_flight WITH DEFAULT KEY.
  2. ローカルクラス LCL_CARRIER のプライベートセクションで、新しいインスタンス属性フライトを定義します。ローカルクラス LCL_FLIGHT で登録したテーブルデータ型でデータ型を指定します。

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

      ABAP
      1234
      DATA passenger_flights TYPE lcl_passenger_flight=>tt_flights. DATA cargo_flights TYPE lcl_cargo_flight=>tt_flights. DATA flights TYPE lcl_flight=>tab.
  3. ローカルクラス LCL_CARRIER のメソッド CONSTRUCTOR の実装を調整します。LCL_PASSENGER_FLIGHT のメソッド GET_FLIGHTS_BY_CARRIER を呼び出した後、passenger_flights に保存されているすべてのオブジェクト参照をフライトに追加します。

    1. ローカルクラス LCL_CARRIERCONSTRUCTOR メソッドの実装に移動します。

    2. ENDMETHOD 命令の前に、以下のコードを追加します。

      ABAP
      123
      LOOP AT passenger_flights INTO DATA(passflight). APPEND passflight TO flights. ENDLOOP.

      テーブル理解が必要な場合は、代わりに以下のコードを追加します。

      ABAP
      12345
      flights = VALUE #( BASE flights FOR pflight IN passenger_flights ( pflight ) ).
  4. 同様に、cargo_flights にループを実装し、これらの参照をフライトにも追加します。

    1. ENDMETHOD 命令の前に、以下のコードを追加します。

      ABAP
      123
      LOOP AT cargo_flights INTO DATA(cargoflight). APPEND cargoflight TO flights. ENDLOOP.

      テーブル理解が必要な場合は、代わりに以下のコードを追加します。

      ABAP
      12345
      flights = VALUE #( BASE flights FOR cflight IN cargo_flights ( cflight ) ).

タスク 4: 一般アクセスおよびダウンキャスト

ローカルクラス LCL_CARRIER のインスタンス属性 passenger_flights および cargo_flights を削除し、新しい属性 flights に置き換えます。コードがサブクラスの特定のコンポーネントにアクセスする場合は、ダウンキャストを実装します。

ステップ

  1. ローカルクラス LCL_CARRIER の定義部分で、属性 passenger_flights および cargo_flights の定義を削除またはコメントします。

    1. 2 つの DATA 命令を選択し、Ctrl + < を押して各コード行の前にコメント記号を追加します。

      ABAP
      12
      * DATA passenger_flights TYPE lcl_passenger_flight=>tt_flights. * DATA cargo_flights TYPE lcl_cargo_flight=>tt_flights.
  2. CONSTRUCTOR メソッドでは、インライン宣言を使用して passenger_flights および cargo_flights をローカル変数として宣言します。

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

      ABAP
      1234567
      DATA(passenger_flights) = lcl_passenger_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ). DATA(cargo_flights) = lcl_cargo_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ).
  3. メソッドがインライン宣言を呼び出した後、プライベートインスタンス属性にローカル変数 passenger_flights の行数を入力します (推奨名: pf_count)。

    ヒント

    最初に、値割当を実装します。次に、クイックフィックスを使用して属性宣言を登録します。
    1. コードを以下のように調整します。

      ABAP
      12345
      DATA(passenger_flights) = lcl_passenger_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ). pf_count = lines( passenger_flights ).
    2. pf_count にカーソルを置き、Ctrl + 1 を押して Declare attribute pf_count を選択します。

  4. 同様に、プライベートインスタンス属性にローカル変数 cargo_flights の行数を入力します (推奨名: cf_count)。

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

      ABAP
      12345
      DATA(cargo_flights) = lcl_cargo_flight=>get_flights_by_carrier( i_carrier_id = i_carrier_id ). cf_count = lines( cargo_flights ).
    2. cf_count にカーソルを置き、Ctrl + 1 を押して Declare attribute cf_count を選択します。

  5. メソッド GET_OUTPUT で、組込 LINES 関数を定義した属性に置き換えます。

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

      ABAP
      123456
      METHOD get_output. APPEND |{ 'Carrier Name:'(001) } { me->name }| TO r_result. APPEND |{ 'Passenger Flights:'(002) } { pf_count }| TO r_result. APPEND |{ 'Average free seats:'(003) } { get_average_free_seats( ) }| TO r_result. APPEND |{ 'Cargo Flights:'(004) } { cf_count }| TO r_result. ENDMETHOD.
  6. メソッド FIND_CARGO_FLIGHT で、属性 cargo_flights を属性 flights に置き換えます。

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

      ABAP
      12
      * LOOP AT me->cargo_flights INTO DATA(flight) LOOP AT me->flights INTO DATA(flight)
  7. メソッド GET_FREE_CAPACITY の呼出で、CAST 式を追加します。

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

      ABAP
      1234
      IF connection_details-airport_from_id = i_airport_from_id AND connection_details-airport_to_id = i_airport_to_id * AND flight->get_free_capacity( ) >= i_cargo. AND CAST lcl_cargo_flight( flight )->get_free_capacity( ) >= i_cargo.
  8. LCL_CARGO_FLIGHT のインスタンスのみを考慮するには、LOOP の WHERE 条件を拡張します。

    ヒント

    基本行データ型の内部テーブルでは、擬似コンポーネント table_line を使用してコンテンツを指定します。
    1. コードを以下のように調整します。

      ABAP
      123
      LOOP AT me->flights INTO DATA(flight) WHERE table_line->flight_date >= i_from_date AND table_line IS INSTANCE OF lcl_cargo_flight.
  9. 同様に、メソッド FIND_PASSENGER_FLIGHT の実装を調整します。

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

      ABAP
      12345678910111213141516171819
      LOOP AT me->flights INTO DATA(flight) WHERE table_line->flight_date >= i_from_date AND table_line IS INSTANCE OF lcl_passenger_flight. DATA(connection_details) = flight->get_connection_details( ). IF connection_details-airport_from_id = i_airport_from_id AND connection_details-airport_to_id = i_airport_to_id AND CAST lcl_passenger_flight( flight )->get_free_seats( ) >= i_seats. DATA(days_later) = flight->flight_date - i_from_date. IF days_later < e_days_later. "earlier than previous one? e_flight = flight. e_days_later = days_later. ENDIF. ENDIF. ENDLOOP.
  10. GET_AVERAGE_FREE_SEATS メソッドの実装を調整します。テーブル削減で内部テーブルフライトを使用し、削減に CAST 式と WHERE 条件を追加します。必要な乗客フライト数は、上記で登録した属性 pf_count に保存されます。

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

      ABAP
      12345678910111213141516
      METHOD get_average_free_seats. * Table Reduction ******************************************************** * r_result = REDUCE #( * INIT i = 0 * FOR flight IN passenger_flights * NEXT i += flight->get_free_seats( ) * ) / lines( passenger_flights ). r_result = REDUCE #( INIT i = 0 FOR flight IN flights WHERE ( table_line IS INSTANCE OF lcl_passenger_flight ) NEXT i += CAST lcl_passenger_flight( flight )->get_free_seats( ) ) / pf_count. ENDMETHOD.
  11. グローバルクラスをコンソールアプリとして有効化してテストします。

    1. Ctrl + F3 を押します。

    2. F9 を押します。