S-JIS[2013-11-09/2016-02-11] 変更履歴

Asakusa Framework 分岐演算子

Asakusa FrameworkOperator DSLの分岐演算子(@Branch)のメモ。


概要

分岐演算子は、レコードの内容に応じて別々の出力先に振り分ける演算子。
性能特性はExtract(旧ドキュメントではMap)。[/2016-02-11]

入力
ポート数
入力データモデル
の制約
イメージ 出力
ポート数
出力データモデル
の制約
入力1レコード
に対する
出力レコード数
1   任意 全てinと同じ
データモデル
いずれかのポートに
必ず1レコード出力される。
(各ポートは0〜1レコード)

どういう内容のときにどこに出力するかをプログラマーが実装する。


hogeというデータモデルの「価格」項目の値を元に、EXPENSIVE・CHEAP・ERRORの3つに分岐させる例。
(この図はToad Editorを用いて作っています)

入力データ例   出力データ例
in
hoge
price
-1
100
300
1000000
200
1000001
expensive
hoge
price
1000000
1000001
cheap
hoge
price
100
300
200
error
hoge
price
-1

example.dmdl(DMDL):

hoge = {

    "価格"
    price : INT;
};

ExampleOperator.java(Operator DSL):

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)を使用する。

ExampleJob.java(Flow DSL):

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」になる)


単体テスト

分岐演算子の単体テストの実装例。

ExampleOperatorTest.java:

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;
	}

Operator DSLへ戻る / AsakusaFW目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま