最近のVisitor
最近よくVisitorを使うので、もうちょっと使いやすいスタイルにならないかなーと思って作ったのがこれ。
まず、acceptメソッド。こいつはVisitorの対象とするノードとかに書く。
/** * 指定のビジタを受け入れる。 * @param <R> 戻り値の型 * @param <C> コンテキストオブジェクトの型 * @param <E> ビジタで発生する例外の型 * @param visitor 受け入れるビジタ * @param context コンテキストオブジェクト(省略可) * @return ビジタの実行結果 * @throws E ビジタでの処理中に例外が発生した場合 */ <R, C, E extends Throwable> R accept( HogeVisitor<R, C, E> visitor, C context) throws E;
で、visitメソッド。こっちはVisitor側に書く。
/** * 〜を渡り歩くビジタ。 * <p> * この実装では、すべてのメソッドが何も行わずに{@code null}を返す。 * </p> * @param <R> 戻り値の型 * @param <C> コンテキストオブジェクトの型 * @param <E> 例外の型 */ public abstract class HogeVisitor<R, C, E extends Throwable> { /** * {@link Foo#accept(HogeVisitor,Object)} * が呼び出された際にコールバックされる。 * @param elem * {@link Foo#accept(HogeVisitor,Object)} * が呼び出されたオブジェクト。 * @param context コンテキストオブジェクト(省略可) * @return このビジタの実行結果 * @throws E この処理中に例外が発生した場合 */ public R visitFoo(Foo elem, C context) throws E { return null; } ... }
これを継承していろいろ書く。R, Cのおかげで戻り値と引数の型を強めに設定できるうえ、throws EのおかげでIOExceptionみたいなチェック例外もスローできる。
public class HogePrinter extends HogeVisitor<Void, Writer, IOException> { @Override public Void visitFoo(Foo elem, Writer context) throws IOException { ... elem.getBar().accept(this, context); return null; } ... }
throwsが煩わしい人は、なくても困らない例外を用意しておく。
/** * 決してスローされることのない例外型を表現する。 * <p> * 総称例外型を指定する場面で型引数として利用することを想定している。 * </p> */ public final class Nothing extends RuntimeException { private static final long serialVersionUID = 1L; /** * インスタンス生成の禁止。 */ private Nothing() { throw new AssertionError(); } }
ポイントはextends RuntimeExceptionにしておくこと。これでvisit*の中からacceptを呼んだときに、accept側も伝搬してthrows Nothingにまずなる。で、Nothing extends RuntimeExceptionなんでaccept空はチェック例外が全部消えて、visit*側のthrowsを消しても怒られなくなる。
public class HogePrinter extends HogeVisitor<Void, PrinitWriter, Nothing> { @Override public Void visitFoo(Foo elem, PrintWriter context) /* (消す) throws Nothing */ { ... // acceptを呼んでもエラーにならない elem.getBar().accept(this, context); return null; } ... }
問題は、Visitorを使う人が世の中にどれくらいいるのかというところ。