# 単一責務の法則

オブジェクト指向プログラムにおいて、高い保守性、生産性を維持するための原則を述べたものとして、頭文字をとった[SOLID原則](https://ja.wikipedia.org/wiki/SOLID)という考え方があります。

この節では特に重要な、最初の文字Sを意味する、「単一責務の法則」について注目したいと思います。この原則が重要な理由は、IEC-61131-3の第2版と第3版の最も大きな違いが生ずる部分であるためです。

IEC-61131-3の第2版では、ファンクションブロックを用いることでいわゆるカプセル化までは対応できました。機能の振る舞いとデータを隠蔽してしまい、外部からは入出力変数でのみアクセスできる機能です。

この機能だけの問題としては、一つのファンクションブロックにどんどん機能を詰め込みがちになるという点です。こういったファンクションブロックの中身の特徴としては、CASE文またはIF文によって振る舞いの切替が行われているのではないでしょうか。

前項の例でいうと、一つのファンクションブロックに「モータ駆動軸を動作する」「エアシリンダ軸を動作する」が両方定義されていて、タイプによりIFで分岐されていたり、特有の機能である、ソフトリミットや、機械DOGセンサによる原点出しといった機能の有無をアクチュエータのタイプにより切り替える必要があります。そうでなければにて異なるファンクションブロックを複数作成する必要があります。

これを避けるための機能が先にご紹介したインターフェースです。インターフェースで扱えるのは入出力変数ではなく、プロパティとメソッドです。この組み合わせにおいて、「単一責務の法則」を適用したシンプルな単位でインターフェースを分離しておくことが重要です。例えば、制御対象を表すファンクションブロックを分けた際、そこに必要なインターフェース定義は、「最大公約数」ごとに個別に分けたインターフェースを定義します。

```{csv-table}
:header: 機能名, インターフェース名, 多回転エンコーダ付きモータ駆動軸, 多回転エンコーダ無しモータ駆動軸, エアシリンダ軸
動作機能, ITF_mover, x, x, x
ソフトリミット機能, ITF_move_limit, x , x , ー
機械原点復帰機能, ITF_pos_init, ー , x , x
```

このように機能の有無に応じて、実装ファンクションブロックは次の通りインターフェースを多重実装します。

多回転エンコーダ付きモータ駆動軸
    : ```{code} iecst
      FUNCTION BLOCK FB_PositionPersistMotor IMPLEMENTS ITF_mover, ITF_move_limit
      ```
多回転エンコーダ無しモータ駆動軸
    : ```{code} iecst
      FUNCTION BLOCK FB_PositionVolatileMotor IMPLEMENTS ITF_mover, ITF_move_limit, ITF_pos_init
      ```
エアシリンダ軸
    : ```{code} iecst
      FUNCTION BLOCK FB_AirSylinder IMPLEMENTS ITF_mover, ITF_pos_init
      ```

## インターフェースの有無チェック

さて、こうしたインターフェースを分離しておくことで、それぞれの実装ファンクションブロックは必要最小限の機能だけを実装すればよく、シンプルに作りこむことが可能となります。これはフレームワーク化した際に、新たな派生アクチュエータを拡張する際の開発者ユーザビリティを大きく高めることでしょう。まさにSOLID原則のOpen-Close原則に従ったものとなります。

しかし、共通フレームワーク内ではこのインターフェース型変数を使って様々な処理を行う必要があります。ロードされた実装ファンクションブロック毎に個別に実装されたインターフェースのサービスにアクセスするにはどうすればよいでしょうか。

前項の例では、`ITF_mover`インターフェースは共通で実装していますが、それ以外は全てにおいて実装されている訳ではありません。よって、まずこのインターフェースが実装されているかどうか検査し、実装されていれば、そのインターフェース型にキャスト（型変換）する必要があります。このための機能が`__QUERYINTERFACE`というTwinCAT拡張命令です。

```{tip}
[本家InfoSysの__QUERYINTERFACEの説明](https://infosys.beckhoff.com/content/1033/tc3_plc_intro/2529176459.html?id=8921520647493442581)
```

### `__QUERYINTERFACE`を使うための準備

まず準備として、検査対象となる全てのインターフェースの実装ファンクションブロックに、`__System.IQueryInterface`のインターフェースが含まれなければなりません。今回の例では、共通インターフェースとして`ITF_mover`がありますので、このインターフェースが、`__System.IQueryInterface`を継承しておくと、全ファンクションブロックで実装されていることになります。

```{code} iecst
INTERFACE ITF_mover EXTENDS __System.IQueryInterface
```

これで、`ITF_mover`を実装したファンクションブロックは、漏れなく`__QUERYINTERFACE`関数による型変換機能を提供することができます。

### `__QUERYINTERFACE`の使い方

`__QUERYINTERFACE`を用いた個別サービスへのアクセス例を次に示します。まず、入力変数を通じて外部からファンクションッブロックのインターフェースポインタを受け取っています。この時に指定するデータ型は、共通インターフェースである`ITF_mover`です。

このインターフェースポインタを通して受け取ったファンクションブロックは、`ITF_mover`の機能しか使えません。ソフトリミット機能を使うため、インターフェース`ITF_move_limit`型の内部変数を宣言しておきます。

次に、`__QUERYINTERFACE`を使って、第一引数に外部から受け取った変換前の`ITF_mover`型の変数、第二引数にソフトリミット機能の`ITF_move_limit`型の内部変数を与えて、型変換を試みます。

この結果、TRUEが戻れば`ITF_move_limit`インターフェースは実装されていることがわかります。変換後の内部変数を通じて`ITF_move_limit`インターフェースのサービスを利用することができます。

```{code-block} iecst
FUNCTION_BLOCK FB_Executor 
VAR_INPUT
    fbMover : ITF_mover; // 外部とは共通機能である位置決めインターフェースを使って受け渡し
END_VAR
VAR
    fbSoftLimit : ITF_move_limit; // 内部でキャストして使うソフトリミット機能サービス
    fbInitializer : ITF_move_limit; // 内部でキャストして使う原点復帰機能サービス
END_VAR

// __QUERYINTERFACE 異なる型にキャスト（型変換）をTRYし、成功したらTRUEが戻ってくる
IF __QUERYINTERFACE(fbMover, fbSoftLimit) THEN
	(*
        この中で、fbMover を通じて受け渡されたファンクションブロック
        の ITF_move_limit のインターフェース実装メソッド、プロパティに
        fbSoftLimit を通してアクセスできる。
    *)
END_IF
```

