テンプレートから実行される複雑なコードを実行するためのヘルパー
(担当者の意見:ヘルパー出力は基本的にやめるべき。副作用のあるコードを書かないように共通ライブラリなどで設計するのと一緒に思います)
引用:「埋め込みヘルパー」を必要としない状況が1つある。これは、この種類の情報それ自体を与えるための自然なホームとして動作するクラスを使用している場合である。これの例は、セマンティック(意味論)モデルを用いて「テンプレートからの生成」を実行している場合である。この場合、「埋め込みヘルパー」に期待する挙動の大部分は、合理的に意味論モデル自体の一部になることができる。但し、意味論モデルがあまり複雑にならないようにすればだが。 (担当者の意見:それはないと思う。埋め込みヘルパーはプレゼンテーションを扱うロジックが含まれる。「合理的に意味論モデル自体の一部」とできるはずがない。)
machine : eventList resetEventList commandList state*; eventList : 'events' event* 'end'; event : name=ID code=ID { events.put($name.getText(), new Event($name.getText(), $code.getText())); }; state : 'state' name=ID { obtainState($name); if (null == machine) machine = new StateMachine(states.get($name.getText())); } actionList[$name]? transition[$name]* 'end'; transition [Token sourceState] : trigger = ID '=>' target = ID { states.get($sourceState.getText()).addTransition( events.get($trigger.getText()), obtainState($target) ); };
machine : eventList resetEventList commandList state*; eventList : 'events' event* 'end'; event : name=ID code=ID {helper.addEvent($name, $code);}; state : 'state' name=ID {helper.addState($name);} actionList[$name]? transition[$name]* 'end'; transition [Token sourceState] : trigger = ID '=>' target = ID {helper.addTransition($sourceState, $trigger, $target);};
違いは、コードをヘルパーに移動していること。
こうするためにヘルパーオブジェクトを生成されたパーサーに入れなければならない。 Antlrは、メンバーセクションにフィールドを宣言することにより、これを実行してくれる。
@members { StateMachineLoader helper; //...
class StateMachineLoader... private Reader input; private StateMachine machine; public StateMachineLoader(Reader input) { this.input = input; }
class StateMachineLoader... public StateMachine run() { try { stateMachineLexer lexer = new stateMachineLexer(new ANTLRReaderStream(input)); stateMachineParser parser = new stateMachineParser(new CommonTokenStream(lexer)); parser.helper = this; parser.machine(); machine.addResetEvents(resetEvents.toArray(new Event[0])); return machine; } catch (IOException e) { throw new RuntimeException(e); } catch (RecognitionException e) { throw new RuntimeException(e); } }
antlrパースは、parser.machineの行で起動される。その前の行でヘルパーを設定していることが分かるだろう。この場合、ローダークラスもヘルパーとして動作する。ローダーは非常に単純なため、別々のクラスとしてそれらを試して作ろうとするより、ヘルパー動作をローダーに加える方が良いようである。
(担当者の意見:ローダが簡単なのはわかるが、ローダとヘルパーを一緒にすることそれ自体に理由はないのでは?役割分担から単純であっても分離したほうがよいと思う)
class StateMachineLoader... void addEvent(Token name, Token code) { events.put(name.getText(), new Event(name.getText(), code.getText())); }
(担当者のファウラーへの意見:「しばしば、「ヘルパーは出力を生成してはならない。 」と聞くことがあり、これは確かにそのような出力はテンプレートから可視ではないため問題である」との話があったはず、テンプレートからの可視化を阻害することは問題であったはずなのに、この件について賛成できるようになったのはなぜなのか説明してほしい。)
Personオブジェクトのコレクションを持っており、無秩序に並んだリストからそれらの名前を印刷したい場合を考えてみる。
それぞれの人は電子メールアドレスかURLを持っているかもしれない。 もし、URLを持っているなら、名前にURLを指し示すリンクが欲しく、 電子メールアドレスならmailtoリンクが欲しいが、どちらも無ければリンクはない。
これには多くのロジックが含まれてしまっている。
一方、出力の生成に「埋め込みヘルパー」に出力を持たせた場合。
<ul> #foreach($person in $book.getPeople()) <li>$helper.render($person)</li> #end </ul> class PageHelper... public boolean hasLink(Person person) { return (null != person.getEmail()) || (null != person.getUrl()); } public String getHref(Person person) { if (null != person.getUrl()) return person.getUrl().toString(); if (null != person.getEmail()) return "mailto:" + person.getEmail().toString(); throw new IllegalArgumentException("Person has no link"); }
ここでの要点は、出力生成のいくらかを「埋め込みヘルパー」に入れることは、合理的な選択であるということである。
(担当者の意見:このファウラーの言う中間地点は、個人的には納得のコード。あまりに納得しすぎてしまって、 どうして「出力生成のいくらかを「埋め込みヘルパー」に入れることは、合理的な選択である」のかということがわかりませんでした。)