読者です 読者をやめる 読者になる 読者になる

enumに振る舞いを持たせる2?

前に書いたenumを使ったちょっとやり過ぎかと思われるswitchな実装について、しばらく使ってみてやはり良い!と思ったので個人的に思うとこを書いてみる。

まず、一般的にenumにされるようなアプリケーションにおける定数というのは使いまわされる箇所が多い。定数によって振り分ける必要性のあるコードというのはつまりは確実に"ビジネスロジック"ということになる。そしてその"ビジネスロジック"部は当然要テストな箇所になる。そこでこの実装が生きてくるわけですよ。

enumに対する処理をswitchで書いていた場合は、得てしてでかいメソッドになりやすい*1。よくあるのが以下のようなパターン。

public void hugeMethod(Parameter param) {
	// なんか色々処理
	
	switch(param.getKind()) {
	case A :{
		//処理
	}
	case B :{
		//処理
	}
	case C :{
		//処理
	}
	default:{
		throw new AssertionError();
	}
	}
	
	//さらに処理
}

で、これだとこのメソッドをテストする時にswitch部分だけのテストを書くのが非常に書きづらい。そんなのswitchのとこだけ別メソッドにすればいいじゃん!とか思うけどみんながみんなやるわけでもない*2

これが前回の記事同様にenum-switch-pattern*3で作ってあれば

public void hugeMethod(Parameter param) {
	// なんか色々処理
	
	param.getKind().apply(SomeFunc.INSTANCE, param);
	
	//さらに処理
}

static enum SomeFunc extends Function<Parameter, Void, Nothing> {
	// 可能であればenum-singletonで作っておくのが使いやすいと思う
	INSTANCE;
	
	public Void caseOfA(Parameter p) {
		//処理
	}
	public Void caseOfB(Parameter p) {
		//処理
	}
	public Void caseOfC(Parameter p) {
		//処理
	}
}

}

というように書くことが出来て、SomeFunc単品でテストを行うことが出来るようになる*4。でも、これだけだとここまで過激なことやるメリットがそれだけかよ!とか思われるだろうけど、このパターンにはもっといい利点がある。

その利点とはenumの種別が増えたとき。これがこのパターンを使った時の最大の利点だと思う。パターンを使わずにswitchで実装していた場合だと、全てのswitchの箇所を探し、それに対して必要な実装を施し、前述したような構造のメソッド内でピンポイントにテストできるようにしないとならない。でもenum-switch-patternを使っていれば、Functionに増やした種別に対応するメソッドを作ってあげるだけで全ての該当箇所がコンパイルエラーで落ちる。あとは個別に必要な処理を追加していくだけ。見落とすこともなければテストもしやすい。


そんな訳でかなり際どい使い方のパターンではあるのだけれど、決してただキモいだけなのではなくてそこにはやっぱりちゃんとでかいメリットもあるんだぜ!ってこと。みんなももっと使うといいんだぜ!なんてえらそーに言ってみる。

*1:偏見かもだけど

*2:まぁ、これをいったらしょうがないのかもだけど

*3:今名付けた

*4:トップレベルのクラスに移してもいいしね