テーブル駆動方式は、スティーブ・マコネルの『コード・コンプリート』にも記述のある古典的なプログラミングテクニック。
大して難しくないし、データ構造を利用することでアルゴリズムが劇的にシンプルになることが体験出来るので、新人にやらすといいと思っている。DBのデータモデルを注意深く検討する意識付けにもなる。
配列を使うので、別にオブジェクト指向言語でなくても使える。
特にSI企業とかで、複雑な業務ルールを扱うことになるプログラマや設計者には、一回は体験させておきたい。
ググると、何人かの方が記事書いている。
http://www.geocities.co.jp/Playtown-Domino/5148/prog_03.html
http://www.sakalab.org/3zemi06/no8.html
新人研修とかで、使っていたのは、血液型の相性判定プログラム。2値の組み合わせで出力が変わるタイプのもの(テーブルで仕様を表現できるもの)なら題材はなんでもいい。
if-elseで書かれたコードを下記のように書き直して見せれば、かなりショックを受けるはず。
public class Sample {
private final static int TYPE_A = 0;
private final static int TYPE_B = 1;
private final static int TYPE_O = 2;
private final static int TYPE_AB = 3;
public static void main(String[] args) {
//相性のルールをテーブルで定義
String[][] affilityTable = {
// A B O AB
/*A*/ {"GOOD" ,"BAD" ,"VERY_GOOD","NORMAL" },
/*B*/ {"BAD" ,"GOOD" , "NORMAL" ,"VERY_GOOD"},
/*O*/ {"VERY_GOOD","NORMAL" , "GOOD" ,"BAD" },
/*AB*/ {"NORMAL" ,"VERY_GOOD", "BAD" ,"GOOD" }
};
//A型とAB型の相性
String result = affilityTable[TYPE_A][TYPE_AB];
System.out.println("A型とAB型の相性は"+result);
}
}
kotlinでオブジェクト指向っぽく書いてみた。
package sample.table
import sample.table.BloodType.AffinityResult.VERY_GOOD
import sample.table.BloodType.AffinityResult.GOOD
import sample.table.BloodType.AffinityResult.NORMAL
import sample.table.BloodType.AffinityResult.BAD
import sample.table.BloodType.A
import sample.table.BloodType.B
import sample.table.BloodType.AB
/**
* 血液型相性占い
*/
fun main(args: Array<String>) {
//A型とB型の相性
val result = A.calcAffinityWith(B)
println("A型とB型の相性は${result.name}")
//AB型とB型の相性
val result2 = AB.calcAffinityWith(B)
println("AB型とB型の相性は${result2.name}")
}
/**
* 血液型のEnum
*/
enum class BloodType(val index: Int) {
//デフォルトで、originalを使えば、0〜3が割り当てられるが、言語仕様に依存するので、別途indexを定義
A(0), B(1), O(2), AB(3);
/**
* 相性の結果Enum。内部クラスとして定義する。これは外に出してもいいかも。
*/
enum class AffinityResult {
VERY_GOOD, GOOD, NORMAL, BAD
}
/**
* コンパニオンオブジェクト内で相性ルールをテーブル(二次元配列)で定義。
* 通常のフィールド定義でも動くが、同じテーブルがA,B,O,ABそれぞれで作られてしまうため、無駄
*/
companion object Inner {
private val AFFINITY_TABLE = arrayOf(
arrayOf(GOOD ,BAD , VERY_GOOD, NORMAL ),
arrayOf(BAD ,GOOD , NORMAL ,VERY_GOOD),
arrayOf(VERY_GOOD,NORMAL , GOOD ,BAD ),
arrayOf(NORMAL ,VERY_GOOD, BAD ,GOOD )
)
}
/**
* 相性を算出する。
*/
fun calcAffinityWith(other: BloodType): AffinityResult = AFFINITY_TABLE[this.index][other.index]
}
3次元での組み合わせが必要なら、3次元配列にしておけば良い。
オブジェクト指向言語を使っているなら、Strategyパターンと組み合わせれば、アルゴリズムも選択できる。