このページでは IEC 61131-3 Ed.4 のオブジェクト指向機能について解説します。クラス・継承・インタフェース・オブジェクト指向ファンクションブロックの各機能を利用すると、PLC プログラムをより再利用可能で拡張しやすい構造で記述できます。
クラス(class ~ end_class)は、オブジェクト指向プログラミングを実現するためのPOUです。メンバ変数とメソッドを持ちます。クラスのインスタンスは変数として宣言します。
class Counter var protected _value: int := 0; end_var method public setValue var_input value: int; end_var _value := value; end_method method public getValue: int getValue := _value; end_method method public increment _value := _value + 1; end_method method public reset _value := 0; end_method end_class program Main var c: Counter; // クラスのインスタンスを宣言 v: int; end_var c.increment(); c.increment(); v := c.getValue(); // vは2 c.reset(); v := c.getValue(); // vは0 // v := c._value; // Compile time error! Inaccessible element. end_program
クラスにはコンストラクタの概念がありません。必要に応じて初期化用のメソッドを定義して呼び出してください。constructor や init という名前のメソッドはユーザーが自由に定義できます。
クラスのメンバ変数はvarブロックで宣言します。アクセス修飾子(public、private、protected、internal)で可視性を制御します。デフォルトはprotectedです。
| 修飾子 | アクセス可能な範囲 | デフォルト |
|---|---|---|
public | どこからでもアクセス可 | |
internal | 同一名前空間内のみ | 名前空間をサポートする場合のみ有効(実装依存) |
protected | 同一クラスおよび派生クラス | ✓ |
private | 同一クラス内のみ |
クラスインスタンス宣言時に外部から初期値を指定できるのはpublic変数のみです。protected・internal・private変数の初期値はクラス定義内で指定します。
class Account var private balance: lreal := 0.0; // 同一クラス内のみアクセス可 end_var var protected account_number: string; // 同一クラスと派生クラスからアクセス可(デフォルト) end_var var public owner: string; // どこからでもアクセス可 end_var end_class // インスタンス宣言時の初期値指定はpublic変数のみ可 var acc: Account := (owner := 'Alice'); // OK // acc2: Account := (balance := 100.0); // エラー: privateは外部から初期化不可 end_var
メソッド(method ~ end_method)はクラスに属するファンクションです。戻り値の型は method メソッド名 : 型名 で宣言し、メソッド名への代入で戻り値を設定します。戻り値のないメソッドは式の中で呼び出せません。
アクセス修飾子 public、private、protected、internal でメソッドの可視性を制御します。デフォルトは protected です。internal は名前空間をサポートする実装でのみ有効です(実装依存)。
メソッド内の変数(var・var_input・var_output・var_in_out)はすべて一時的であり、呼び出しをまたいで値は保持されません。メソッドはクラスのメンバ変数(varブロック)への読み書きアクセスを持ちます。なお、var_temp と var は同義です。
メソッドのオーバーロード(同名で引数の型が異なる複数のメソッド定義)は、IEC 61131-3 の範囲外です。
class Stack var private data: array[0..9] of int; top: int := 0; end_var method public push var_input val: int; end_var if top < 10 then data[top] := val; top := top + 1; end_if; end_method method public pop: int // 戻り値型はint if top > 0 then top := top - 1; pop := data[top]; // メソッド名に代入して戻り値を設定 end_if; end_method end_class
this はメソッドが呼び出されているインスタンス自身を指します。同一クラス内のメソッドを内部呼び出しする際は this.メソッド名() を使います。super は親クラスのメソッドを呼び出します。this および super は、メソッド・アクション・ファンクションブロック本体の外では使用できません。また、super.super.メソッド名() のように祖先をたどった呼び出しはサポートされていません。
class Counter var private cv: uint := 0; max_val: uint := 1000; end_var method public up: uint var_input inc: uint; end_var if cv <= max_val - inc then cv := cv + inc; end_if; up := cv; end_method method public up5: uint up5 := this.up(inc := 5); // 同一インスタンスのupを内部呼び出し end_method end_class
派生クラスで親クラスのメソッドを上書きするには override キーワードが必要です。override により、変数の宣言型が親クラスであっても、実際のインスタンス型のメソッドが呼び出されます(動的束縛)。
上書きできるのは public・protected・internal の各アクセス修飾子を持つメソッドのみです。private メソッドは上書きできません。上書きするメソッドは、上書き元と同じアクセス修飾子を使う必要があります。override のないメソッドの上書きや、final メソッドのオーバーライドはエラーになります。
class LightRoom var light: bool; end_var method public nighttime light := true; end_method end_class class Light2Room extends LightRoom var light2: bool; end_var method public override nighttime // 親クラスのnighttimeを上書き super.nighttime(); // 親クラスのメソッドを呼び出す light2 := true; end_method end_class
メソッドの省略可能なパラメータに指定されたデフォルト値は、動的束縛の対象外であり、宣言型のシグネチャから静的に決まります。インタフェース型変数または基底クラス型変数経由で呼び出す場合、省略したパラメータのデフォルト値は宣言型のメソッドシグネチャから取得されます。実際に呼ばれるメソッドの実装は派生クラスのものでも、デフォルト値は宣言型から取られる点に注意が必要です。
abstract をメソッドに付けると抽象メソッドになり、本体は空にします。abstract クラスはインスタンス化できず、派生クラスですべての抽象メソッドを実装する必要があります。ただし、派生クラス自身が abstract である場合はその限りではありません。final メソッドは派生クラスでのオーバーライドを禁止します。final クラスはそれ以上継承できません。
抽象クラスには1つ以上の抽象メソッドが必要です。また、abstract は override や final と組み合わせて使用できません。abstract は抽象クラスのメソッドにのみ使用できます。
class abstract Shape // abstractクラスはインスタンス化不可 method public abstract area: lreal // 抽象メソッド(本体は空) end_method end_class class Circle extends Shape var public r: lreal := 1.0; end_var method public override area: lreal // 抽象メソッドの実装(必須) area := 3.14159 * r * r; end_method end_class
プロパティは、変数のように読み書きできる言語要素です。その裏で get/set メソッドが実行されます。property_get ~ end_property で読み取り処理を、property_set ~ end_property で書き込み処理を定義します。どちらか一方だけを定義することもでき、get のみなら読み取り専用、set のみなら書き込み専用になります。
プロパティは var_input・var_output・var_in_out セクションを持てません。一時変数には var・var_temp を使用します。プロパティ名と同じ変数がスコープ内に暗黙的に定義されており、get では戻り値として使用し、set では入力値として読み取ります。
class Length var public cm: lreal; // 内部保持値(センチメートル) end_var // 読み書き可能プロパティ: inch property_get public inch: lreal inch := cm / 2.54; // プロパティ名に代入して戻り値を設定 end_property property_set public inch: lreal cm := inch * 2.54; // プロパティ名で入力値を参照 end_property // 読み取り専用プロパティ: valid property_get public valid: bool valid := cm >= 0.0; end_property // 書き込み専用プロパティ: reset property_set public reset: bool if reset then cm := 0.0; end_if; end_property end_class program Main var len: Length; val: lreal; end_var len.inch := 10.0; // property_set inch を呼び出す val := len.inch; // property_get inch を呼び出す if not len.valid then // property_get valid を呼び出す len.reset := true; // property_set reset を呼び出す end_if; // len.reset := false; // OK(property_set は存在) // if len.reset then // エラー: property_get が未定義 // len.valid := true; // エラー: property_set が未定義 end_program
プロパティはクラス、ファンクションブロック、インタフェースに定義できます。インタフェースに定義するプロパティは本体を持たないプロトタイプのみです。
クラスはextendsで他のクラス(スーパークラス)を継承し、スーパークラスのメソッドとメンバ変数を引き継ぎます。implementsでインタフェースを実装できます。インタフェース interface ~ end_interface はメソッドのシグネチャのみを定義します。ポリモーフィズムの実現には、インタフェース型変数または基底クラスへの参照型 ref_to、もしくは var_in_out 変数を使用します。
継承は単一継承のみサポートします。複数のクラスからの多重継承は不可です。インタフェースについては、複数実装できます。なお、クラスはファンクションブロックを継承することはできません。ファンクションブロックはクラスまたはファンクションブロックを継承できます。
interface IShape method area: lreal // プロトタイプにアクセス修飾子は不要(暗黙的にpublic) end_method method perimeter: lreal end_method end_interface class Circle implements IShape var private constant PI : lreal := 3.141592653589793; end_var var public r: lreal := 1.0; end_var method public init var_input radius: lreal; end_var r := radius; end_method method public area: lreal // インタフェース実装メソッドはpublic必須 area := PI * r * r; end_method method public perimeter: lreal perimeter := 2.0 * PI * r; end_method end_class class Rectangle implements IShape var public w: lreal := 1.0; h: lreal := 1.0; end_var method public init var_input width: lreal; height: lreal; end_var w := width; h := height; end_method method public area: lreal area := w * h; end_method method public perimeter: lreal perimeter := 2.0 * (w + h); end_method end_class // インタフェース型変数によるポリモーフィズム // インタフェース型変数はクラスインスタンスへの参照を保持する(null が初期値) function_block AreaPrinter var_input shape: IShape; end_var // インタフェース型を変数の型として使用 var area_val: lreal; end_var if shape <> null then area_val := shape.area(); // デリファレンス不要 end_if; end_function_block program Main var c: Circle; r: Rectangle; printer: AreaPrinter; s: IShape; end_var c.init(radius := 2.0); r.init(width := 4.0, height := 3.0); s := c; // クラスインスタンスをインタフェース型変数に代入 printer(shape := s); // Circle.area() を呼び出す(動的束縛) s := r; printer(shape := s); // Rectangle.area() を呼び出す(動的束縛) end_program
インタフェース型変数は、インタフェースを実装したクラスのインスタンスへの参照を保持します。初期値は null です。null チェックを行わずにメソッドを呼び出すとエラーになります。なお、インタフェース型は var_in_out 変数には使用できません。
インタフェースは extends で他のインタフェースを継承できます。複数のインタフェースを同時に継承することも可能です。派生インタフェースは基底インタフェースのメソッドプロトタイプをすべて継承します。その派生インタフェースを実装するクラスは、継承されたメソッドを含むすべてのメソッドを実装する必要があります。なお、インタフェースはクラスや FB を継承することはできません。
interface IReadable method read: int end_method end_interface interface IWritable method write var_input val: int; end_var end_method end_interface // IReadable と IWritable の両方を継承 interface IReadWrite extends IReadable, IWritable end_interface class Register implements IReadWrite var private data: int; end_var method public read: int // IReadable からの継承 read := data; end_method method public write // IWritable からの継承 var_input val: int; end_var data := val; end_method end_class
代入試行(?=)は、インタフェース型変数または参照型変数に対してダウンキャストを安全に行う演算子です。キャスト先の型と互換性がある場合は有効な参照が設定され、互換性がない場合は null が設定されます。結果の利用前に null チェックを行う必要があります。
interface ITF1 end_interface interface ITF2 end_interface interface ITF3 end_interface class MyClass implements ITF1, ITF2 end_class program Main var inst: MyClass; itf1: ITF1; itf2: ITF2; itf3: ITF3; end_var itf1 := inst; // MyClass は ITF1 を実装しているので代入可 itf2 ?= itf1; // itf1 の実体は MyClass → ITF2 も実装 → 代入成功 itf3 ?= itf1; // itf1 の実体は MyClass → ITF3 は未実装 → null になる if itf2 <> null then // itf2 を通じて ITF2 のメソッドを呼び出せる end_if; // itf3 は null なので、null チェックなしで使うとエラー end_program
OOFBは、既存のファンクションブロックに継承・インタフェース実装・メソッドのOOP機能を追加したものです。var_inputやvar_outputによる入出力宣言やインスタンス化といったFBとしての基本機能はそのまま保持されます。通常のFBとの違いは、extendsによる継承・implementsによるインタフェース実装・methodによる追加メソッドが使用できる点です。
interface IDevice method init // アクセス修飾子なし(暗黙的にpublic) end_method method execute end_method end_interface function_block BaseDevice implements IDevice var public enabled: bool := false; end_var method public init // インタフェース実装メソッドはpublic必須 enabled := true; end_method method public execute // 基底クラスの処理 end_method end_function_block function_block Motor extends BaseDevice var public speed: int := 0; end_var method public override execute // 継承メソッドを上書きするにはoverrideが必要 if enabled then speed := 100; end_if; end_method end_function_block
FB を継承した場合、基底 FB のボディは自動的には実行されません。基底 FB のボディを実行するには、派生 FB のボディ内で super() を呼び出します。super() は引数なしで呼び出します。super() は FB のボディ内でのみ使用でき、メソッド内では使用できません。
function_block BaseFB var_input a: int; end_var var_output x: int; end_var x := a + 1; // 基底FBのボディ end_function_block function_block DerivedFB extends BaseFB var_input b: int; end_var super(); // 基底FBのボディを呼び出す(x := a + 1 が実行される) x := 3 * x + b; // その結果を使ってさらに処理 end_function_block