Asakusa FrameworkのOperator DSLの分岐演算子(@Branch)のメモ。
|
分岐演算子は、レコードの内容に応じて別々の出力先に振り分ける演算子。
性能特性はExtract(旧ドキュメントではMap)。[/2016-02-11]
入力 ポート数 |
入力データモデル の制約 |
イメージ | 出力 ポート数 |
出力データモデル の制約 |
入力1レコード に対する 出力レコード数 |
---|---|---|---|---|---|
1 |
![]() |
任意 | 全てinと同じ データモデル |
いずれかのポートに 必ず1レコード出力される。 (各ポートは0〜1レコード) |
どういう内容のときにどこに出力するかをプログラマーが実装する。
hogeというデータモデルの「価格」項目の値を元に、EXPENSIVE・CHEAP・ERRORの3つに分岐させる例。
(この図はToad Editorを用いて作っています)
入力データ例 | 出力データ例 | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
in |
|
→ | expensive |
|
||||||||||
→ | cheap |
|
||||||||||||
→ | error |
|
hoge = { "価格" price : INT; };
import com.asakusafw.vocabulary.operator.Branch; import com.example.modelgen.dmdl.model.Hoge;
public abstract class ExampleOperator { /** * 価格で処理を分岐する * * @param hoge * 対象のレコード * @return 分岐先を表すオブジェクト */ @Branch public Status select(Hoge hoge) { int price = hoge.getPrice(); if (price < 0) { return Status.ERROR; } if (price >= 1000000) { return Status.EXPENSIVE; } return Status.CHEAP; }
/** * 値段に関するレコードの状態 */ public enum Status { /** 高い */ EXPENSIVE, /** 安い */ CHEAP, /** エラー */ ERROR, } }
分岐演算子では、出力先を表す列挙型(この例ではStatus)を返すメソッド(この例ではselect())を定義する。
列挙型なので、命名規則は大文字のスネークケースにする。(単語を大文字で書き、単語と単語をつなぎたい場合は「_(アンダースコア)」を使用する)
※メソッド内では引数で渡されたデータモデルの値を変更するようなコーディングをしてしまうことも出来るが、ここで値を変えても出力データが変わるわけではない。
(むしろフレームワーク側でデータが初期化されず次レコードの判定でおかしな挙動になる可能性もありそうなので、すべきではない)
値を変更したい場合は更新演算子(@Update)を使用する。
import com.example.modelgen.dmdl.model.Hoge; import com.example.operator.ExampleOperatorFactory; import com.example.operator.ExampleOperatorFactory.Select;
private final In<Hoge> in; private final Out<Hoge> out1; private final Out<Hoge> out2; private final Out<Hoge> out3;
@Override public void describe() { ExampleOperatorFactory operators = new ExampleOperatorFactory(); // 価格で処理を分岐する Select select = operators.select(this.in); this.out1.add(select.expensive); this.out2.add(select.cheap); this.out3.add(select.error); }
Flow DSLでは、自分が作ったOperatorのFactoryクラス(AsakusaFWのコンパイラーによって生成される)を使用する。
メソッド名はOperatorクラスに書いたメソッド名と同じ。
戻り値の型はAsakusaFWのコンパイラーによって生成されたクラス。(メソッド名を先頭が大文字のキャメルケースに変換したもの)
分岐演算子の出力ポートの名前は、列挙型で定義した各列挙子の名前(EXPENSIVEとかCHEAPとか)を先頭が小文字のキャメルケースに変換したものになる。
(例:列挙子が「ERROR_PRICE
」だったら、出力ポート名は「errorPrice
」になる)
分岐演算子の単体テストの実装例。
package com.example.operator;
import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import org.junit.Test; import com.example.modelgen.dmdl.model.Hoge; import com.example.operator.ExampleOperator.Status;
/** * {@link ExampleOperator}のテスト. */ public class ExampleOperatorTest { @Test public void selectError() { test(-1, Status.ERROR); } @Test public void selectCheap() { test(0, Status.CHEAP); test(1000000 - 1, Status.CHEAP); } @Test public void selectExpensive() { test(1000000, Status.EXPENSIVE); test(1000000 + 1, Status.EXPENSIVE); }
private void test(int price, Status expected) { ExampleOperator operator = new ExampleOperatorImpl(); Hoge hoge = new Hoge(); hoge.setPrice(price); Status status = operator.select(hoge); assertThat(status, is(expected)); } }
Operatorのテストクラスは、通常のJavaのJUnitのテストケースクラスとして作成する。
テスト対象のOperatorクラス自身は抽象クラスだが、Operatorクラス名の末尾に「Impl」の付いた具象クラスがAsakusaFWによって生成されるので、それを使う。
分岐演算子は、Javaのswitch文に似ている。
Hoge hoge = 〜; List<Hoge> out1 = 〜; List<Hoge> out2 = 〜; List<Hoge> out3 = 〜; Status status = operator.select(hoge); switch(status) { case EXPENSIVE: out1.add(hoge); break; case CHEAP: out2.add(hoge); break; case ERROR: out3.add(hoge); break; }