2016/07/09

[勉]Visitorパターン

唐突だが、デザインパターンの中のVisitorパターンの勉強をしよう。

以前購入した『モダンC言語プログラミング』の中に出てくるのだが、なんだかよくわからなかったのだ。


GoF本には、

あるオブジェクト構造上の要素で実行されるオペレーションを表現する。

と書いてある。
うーん、意味がわからん。
一応GoF本は買うだけ買ったのだけど、文章の意味がわかりづらくてほとんど読んでいないのだ。。

 

13.Visitor パターン | TECHSCORE(テックスコア)
こちらでは、「処理を追加しやすくするため、Visitor側に書けるようにする」みたいに書かれている。

あれ、オブジェクト指向って、データと操作をオブジェクトでまとめましょう、みたいな感じじゃなかったっけ?
これだと、操作をデータと別の場所に書けるようにしました、という受け取り方をしてしまうのだが。。。

まだ、理解が足りてないので、あせらないようにしよう。


TECHSCOREさんのところにあるソースファイルを見ると、最後にやりたいのは鈴木さんの家庭SuzukiHomeのpraisedChild()やreprovedChild()だと思う。

それを呼ぶ処理は、RookieTeacherのvisit()なのだけど、visit()の引数でTanakaHomeだったりSuzukiHomeだったりの処理を呼んでいる。

鈴木さんに対してはreprovedChild()を呼んでいるのだけど、これは親の発言だと思うから、先生側が呼び出してよいものだろうか。。。
どっちかといえば、TanakaHomeでもSuzukiHomeでも同じメソッドを呼び出して、TanakaHomeだったらこういう発言、SuzukiHomeだったらこういう発言、という方が、発言の内容からすると自然だと思う。

まあ、サンプルだし、ここの記事全体が先生を例に取っているようだから、内容が多少不自然なのは仕方ないのかもしれん。

ここで言いたいのは、鈴木さん向けの処理や田中さん向けの処理をRookieTeacherの中に実装するのではなく、SuzukiHomeやTanakaHomeに書くことができるということだろう。
もし吉田さん向けの処理を追加したければ、RookieTeacherには引数がYoshidaHomeのvisit()を用意して、中身はYoshidaHomeの中に書いてしまえば良い。
これが「追加しやすい」なのだろう。


もしRookieTeacherの中に処理を追加するのだったら、どう書くだろうか?
visit()の引数を各Homeの基底クラスか何かにして、オブジェクトの種類でswitch-caseで分けるとかかな。

これを回避したい理由は何だろうか?
思いつくのは、家庭が増えるにつれてcaseが多くなってしまう、だろう。
各caseに処理を書くと長くなるので別のメソッドにするかもしれない。
それくらいだったら、別のオブジェクトにメソッドを書いて、switch自体なくしてしまえばよいじゃないか、ということか。

 

ただ、私の感想からすると、別にswitch-caseでいいやん、と思ってしまう。
だって、デバッグしやすいじゃないか。
JavaとかC++だったらよいかもしれないけど、Cだと値しかないから、もしオブジェクトが異なるポインタをもらったとしても、その先のオブジェクトの種類を調べるのは面倒だと思う。
もしオブジェクトの最初にオブジェクトの種類があると、ああこのオブジェクトはこの型でキャストされるのね、とわかって追いやすい。

 

でも、switch-caseで書き始めてしまって、どんどん追加になっていって、あああああ、と思うこともしばしばなので、やはり場合によりけりだろう。
やはりデザインパターンというだけあって、パターンが有効なデザインというのも考えんといかんはず。


GoF本に戻ってみよう。
TECHSCOREさんのソースに当てはめると、こうなるか。

  • あるオブジェクト=RookieTeacher
  • 実行されるオペレーション=各HomeのpraisedChild()やreprovedChild()
  • 表現する=各Homeに実装を書く

読んでみると、分岐がどうのこうのというよりも、行いたいことの本質ではない、値のチェックとかそういう処理を同じクラスに書いてしまうとわかりづらい、という主張をしているようだ。

 

GoF本の例では、コンパイラになっている。
「アブストラクト・シンタックスツリーとしてプログラムを表現するコンパイラ」なのだけど、何かもうちょっとわかりやすい例はなかったのだろうか・・・。
ソースファイルを食わせたら文法を解析してXMLみたいなツリーに変換してくれるツールということか?
"Gang of Four"だから、4人のうちの誰かがこういうコンパイラみたいなものが得意だったのかもしれん。

困ったことに、私にコンパイラの話をされてもピンとこないのだ。
例では、VariableRefNodeとAssignmentNodeになっている。
VariableRefは変数参照、Assignmentは割り当て、か?
日本語で「変数あるいは算術表記を表現するノードとは別に代入文を表現するノードを扱う必要がある」と書かれているので、VariableRefNodeは「変数あるいは算術表記を表現するノード」で、AssignmentNodeは「代入文を表現するノード」なのかも。

Visitor
原文らしきものがあった。

Most of these operations will need to treat nodes that represent assignment statements differently from nodes that represent variables or arithmetic expressions.

色を付けたところが、それぞれ対応する箇所になるか。

代入文は"assignment statements"になるようだ。
AssignemntNodeは、これだろう。

もう片方が"variables or arithmetic expressions"なのにVariableRefNodeなのは、VariableNodeだとわかりづらいからか。
VariableArithmeticNodeだと長いし。
VarArithExpNode・・何かだめだな。

 

例として載っているメソッドは、

  • TypeCheck() : 型チェック
  • GenerateCode() : コード生成
  • PrettyPrint() : きれいに印刷する

なのだが、

  • これらのメソッドは各クラスに分散して実装することになるけど、それだとメンテナンス性が悪いんじゃないの?
  • 同じクラスの中に型チェックときれいな印刷のための処理が混ざっているのはどうなの?

ということらしい。
じゃあクラスを分けなければいいじゃないか、と思ったのだが、そうもいかないのだろうか。。。

 

これをどう解決しているかというと、TypeCheckingVisitorやCodeGeneratingVisitorというクラスを作って、

  • TypeCheckingVisitor
    • VisitAssignment() : AssignmentNodeのTypeCheck()
    • VisitVariableRef() : VariableRefNodeのTypeCheck()
  • CodeGeneratingVisitor
    • VisitAssignment() : AssignmentNodeのCodeGenerate()
    • VisitVariableRef() : VariableRefNodeのCodeGenerate()

と、処理でクラスを分けている。

そうか・・・C言語だとクラスに分けずとも関数が書けるから、あまり気にしないのかもしれないな。。。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。