【ABAP】はじめてのデザインパターン

## デザインパターンとは
デザインパターンとは、オブジェクト指向で用いられる、クラスの「型」のようなものです。ソフトウェア開発の歴史の中で、クラスを再利用、拡張しやすいように先人たちが考えた設計が体系化されています。

プログラムの開発においては似たような問題を扱うことがよくあり、デザインパターンを適用することで再利用性に優れた設計をすることができます。デザインパターン知っておくことで、既存のコードを読むときに理解が深まったり、設計者、開発者同士で共通認識を持てるというメリットもあります。

## デザインパターンを学ぶ
デザインパターンの古典と呼ばれる本に[Design Patterns: Elements of Reusable Object-Oriented Software](https://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612)があります。最初に「先人たちの知恵」をまとめた本です。読んでみたのですが、私にはかなり難解でした。

次に、ABAPのデザインパターンについて書かれた[Design Patterns in ABAP Objects](https://www.sap-press.com/design-patterns-in-abap-objects_4277/)を読みました。こちらは語り口もわかりやすく、サンプルコードも当然ABAPなので理解しやすかったです。OO ABAPを使っていこうとする人におすすめです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/253a99aa-a3e1-0608-3cc8-76471b69c952.png)

この本では27のデザインパターンが紹介されていますが、この記事では初めに知っておくとよいと思うデザインパターンを2つ紹介します。

## 紹介するデザインパターン
### Strategy
同じインターフェース（型）を共有する複数のクラスがあり、実行時にどのクラスを使うのかを動的に決定します。各クラスは同じメソッドを持っているので、プログラムからはそれらを区別なく使うことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/f5c21e98-135a-3718-1079-5c7c4abd06b1.png)

### Factory
Strategyでは、なんらかの条件にもとづいてどのクラスを使用するかを決定します。Factoryは、クラス決定のためのロジックを定義するクラス（またはメソッド）です。Factoryを使えば、プログラム側ではどのクラスを生成するかを意識しなくて済みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/df850038-cc31-9f9f-99a9-5fca47a021c3.png)

## ユースケース 
カスタムワークフローを作るケースを考えてみます。ワークフロープログラムを使って、さまざまな伝票（購買発注、会計伝票など）を承認します。
承認処理は伝票によってさまざまです。例えば購買発注はリリースステータスが設定され、会計伝票は転記がされます。

**Strategy**
伝票種別ごとの承認処理を個別のクラスに記述します。各クラスは共通のインターフェースを実装しています。

**Factory**
伝票種別ごとに使用するクラスを決定してオブジェクトを生成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/5f6275dc-f7b9-e737-d36b-87010b33ce37.png)

### Strategyの実装
インターフェースの実装は以下のようになります。

```abap:IF_APPROVE
INTERFACE zif_approve
  PUBLIC .

  METHODS approve
    IMPORTING iv_key           type string
    RETURNING VALUE(rt_return) TYPE bapiret2_t.

ENDINTERFACE.
```

購買発注承認用のクラスは以下のようになります。簡単にするため承認処理は書いていません。

```abap:ZCL_APPROVE_PO
CLASS zcl_approve_po DEFINITION
  PUBLIC
  INHERITING FROM zcl_approve_abs
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    METHODS zif_approve~approve REDEFINITION.

  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

CLASS zcl_approve_po IMPLEMENTATION.

  METHOD zif_approve~approve.
    "承認処理 (BAPI)

    WRITE: |PO { iv_key } has been approved.|.

  ENDMETHOD.
ENDCLASS.
```
会計伝票承認用のクラスは以下のようになります。

```abap:ZCL_APPROVE_ACC_DOC
CLASS zcl_approve_acc_doc DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    INTERFACES zif_approve.

  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS zcl_approve_acc_doc IMPLEMENTATION.

  METHOD zif_approve~approve.
    "転記処理

    WRITE: |Accounting doc { iv_key } has been approved.|.

  ENDMETHOD.
ENDCLASS.
```

メインのプログラムは以下のようになります。

```abap
REPORT zsample_wf.
PARAMETERS:
  p_type TYPE string DEFAULT 'PO',
  p_key TYPE string.

START-OF-SELECTION.
  DATA: lo_approve TYPE REF TO zif_approve.

  "TODO:承認用のクラスを決定する

  "承認処理
  lo_approve->approve( p_key ).
```

### Factoryの実装
入力された伝票種別にもとづいて生成するクラスを判断します。

```abap:ZCL_APPROVE_FACTORY
CLASS zcl_approve_factory DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.
    CLASS-METHODS get_instance
      IMPORTING iv_doc_type       TYPE string
      RETURNING VALUE(ro_approve) TYPE REF TO zif_approve
      RAISING   cx_class_not_existent.

  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.



CLASS zcl_approve_factory IMPLEMENTATION.
  METHOD get_instance.
    CASE iv_doc_type.
      WHEN 'PO'.
        ro_approve ?= NEW zcl_approve_po( ).
      WHEN 'ACC'.
        ro_approve ?= NEW zcl_approve_acc_doc( ).
      WHEN OTHERS.
        RAISE EXCEPTION TYPE cx_class_not_existent.
    ENDCASE.

  ENDMETHOD.

ENDCLASS.
```
Factoryクラスを使うと、プログラムの処理は以下のようになります。

```abap
  "承認用のクラスを決定
  TRY.
      lo_approve ?= zcl_approve_factory=>get_instance( p_type  ).
    CATCH cx_class_not_existent.
      LEAVE LIST-PROCESSING.
  ENDTRY.

  "承認処理
  lo_approve->approve( p_key ).
```

### 動作
伝票種別に「PO」と入力して実行すると、以下のメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/56c69610-bde1-119e-a60d-9d50ec7fdcd7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/493a6659-e535-3c53-12d1-eeead9031983.png)
伝票種別に「ACC」と入力して実行すると、以下のメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/53a47051-41fe-ff11-d463-9815a4634b70.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276021/8cabbf17-1467-0876-857c-ed03f2da3beb.png)

## 他のデザインパターン
今回のケースでは、簡単にするために伝票のキーは1つだけ、かつString型でした。実際には伝票によってキーの型や数が変わります。そんな時には**Property Container**のパターンが使えます。Property Containerは、キー + バリュー形式のテーブルと、テーブルへのSet、Getメソッドを持ったクラスです。
また、購買発注承認用クラスと会計伝票承認用クラスで一部のメソッドを共有したい場合もあると思います（たとえば、エラーハンドリングなど）。そんなときは、**Template Method**のパターンを使って、抽象クラスに共通メソッドを実装し、そのクラスを継承して個別のクラスを作ることもできます。