インターフェースの活用#

インターフェースとは、ファンクションブロックに実装するべき共通のメソッドやプロパティ、また、その引数の型と戻り値を定義するための仕組みです。定義したインターフェースを実装(implementation)する際に、あらかじめ定義したプロパティとメソッドの実装を強制されます。

これを実装したファンクションブロックは、さまざまなバリエーションのものを複数作ったとしても、同じメソッドやプロパティを持つことが保証されます。上記の例では、次の二つのファンクションブロックがいずれもI_Observerインターフェースを実装しており、どちらもsend_signalメソッドを持っています。

  • FB_AlarmSimpleListerner

  • FB_AlarmDBListener

どちらも同じメソッドとプロパティ、また、その引数や戻り値の型が等しいことが保証されています。

TwinCAT PLCプログラムにおけるポリモーフィズム(多態性)#

前項で説明した通り、オブジェクト指向で最も重要なことは、抽象レベルを分けたプログラム構造の設計です。このための重要な機能の一つがポリモーフィズム(多態性)です。インターフェースを用いる目的は、このポリモーフィズムを実現するための手段といっても差支えありません。

まず最初に共通の振る舞いやパラメータを持つ機能仕様を分析します。この機能から、具象レベルと抽象レベルに機能を分離します。

例えば、「アクチュエータを動作させる」という抽象レベルのインターフェースに対して、「モータを作動する」「エアシリンダを作動する」などの具象レベルの実装ファンクションブロックを定義します。どちらも、「速度」や「目標位置」というプロパティと、「作動する」「停止する」というメソッドを持ちますので、インターフェースに定義した上でこれらを具象レベルに合わせて実装します。

次に、これらを制御するドライバファンクションブロックを定義します。このファンクションブロックでは、モータやエアシリンダなどの具象レベルのファンクションブロックを直接扱うのではなく、抽象レベルであるインターフェースを通じて処理ロジックを書きます。

メインプログラムでは、ドライバファンクションブロックをインスタンス化し、インターフェース変数に具象レベルのモータ用、エアシリンダ用のファンクションブロックのインスタンスを用途に併せてセットします。セットする実装インスタンスによって異なる振る舞いを示す汎用性の高いファンクションブロックが出来上がります。これが多態性と呼ばれるデザインパターンです。

../_images/2024-06-26-13-15-02.png

図 2.9 ポリモーフィズムの実装例#

図 2.9は、その使い方を示した実装例です。図の中央にあるInterfaceFutureには、雛形となるメソッドとプロパティ、およびこれらの引数や戻り値を定義されており、FB_AbstructFutureをはじめとする左端の破線枠内の具象レベルのファンクションブロックを定義します。インターフェースと実装FBとの関係は、この図の通り破線に白抜き矢印で示すことが、UMLでルール化されています。

これらのインターフェース実装は、ドライバファンクションブロックであるFB_Executorファンクションブロック内で使われますので、インターフェース型のメンバ変数_futureを定義します。

このメンバ変数は入出力変数とするか、カプセル化するために内部変数としておき、セッタを通じて外部から代入できるようにしておきます。図の例ではfutureプロパティのセッタを通じて代入できるようにしています。

次にインターフェースで定義したプロパティやメソッドを用いたロジックを書きます。この例では、FB_Executorファンクションブロック内のexecute()メソッドにこれを記述しているものとします。

最後に、メインプログラムを記述します。具象レベルのファンクションブロックのインスタンス変数を定義し、FB_Executorfutureプロパティを通じてセットします。

この後、FB_Executorexecute()メソッドを実行することで、事前に代入した具象レベルのファンクションブロックに応じた振る舞いが実行されます。

ライブラリ化のすすめ

抽象レベルを分離した設計の本質は、具象レベルのロジックを隠蔽してしまい、抽象度の高いエンジニアリングに専念することです。抽象度の高いエンジニアリングに専念することでビジネス的な価値を最大化することが期待できます。

しかし、どうしても人間は、ロジックを細部まで自分の手の届く範囲に置いておきたい生き物です。これが特に製造現場において抽象度の高いソフトウェアが嫌われ、メモリアドレスやラダーといった具象度の高い方式でソフトウェア製品が生み出され続ける原因だと考えられます。

背景にあるのは、ブラックボックス化されたソフトウェアによって誰も手に付けることができず、コントロールが効かなくなったことによる失敗事例が多数あったからではないでしょうか。しかしこれを恐れたままでは、ソフトウェアによるイノベーションは今後生まれないでしょう。

ブラックボックス化して、アンマネージ状態になってしまった原因は、むしろ特定のエンジニアに依存し過ぎてしまったことにあります。これはラダーやメモリ番号に基づくプログラムを導入したとしても、やはりその複雑さから誰も手を付けられなくなる状態から逃れられません。

ここまで説明した抽象度を分ける仕組みを利用し、うまく機能分離をしたところでぜひライブラリ化を検討しましょう。ライブラリ化は複雑なロジックを隠蔽かするだけではなく、単体テストや、ドキュメンテーションもセットで考えるべきです。

また文化として、ライブラリ毎にしっかり責任を負うエンジニアリングチームを配置し、彼らを信頼することも欠かせません。単体テストによる品質の担保、そしてそれを後工程であるライブラリユーザに対するドキュメントの提示、これこそが日本のモノづくりが得意とする「自工程完結」に他なりません。

インターフェースの実体はポインタ#

図 2.9で示す実装例のメインプログラムでは、インターフェース型の変数に、直接ファンクションブロックのインスタンス変数を代入していました。

// fbExecutor.futureはインターフェース型のセッタ、fbPattern1はそのインターフェースを実装したファンクションブロックのインスタンス変数
fbExecutor.future := fbBatchJob1; 

TwinCATの実装に慣れた方であれば、この代入方法だとインスタンス変数そのものの値渡しになるのでは?と思われると思います。

しかしそうではありません。インターフェース型変数に対してそれを実装したファンクションブロックのインスタンス変数を代入すると、暗黙的にインスタンス変数のポインタが渡される事になります。

つまり、インターフェース型変数の実体はポインタです。また、これをインターフェースポインタと呼びます。

従って、インターフェース型の変数を使うロジックにおいては、次の通り有効なインターフェースポインタであるかどうかを判定した上で使う必要があります。

IF _future <> 0 THEN
    _future.anymethod();
END_IF