MVC Model
---------
2005-07..2005-09ごろに書いたもの。
--
今さらながら、MVCがよく分かっていないことに気がついた。
DocDiffという僕が作っているテキスト比較アプリケーションのいんちきクラス図
を描いてみる。今まではMVCをそれぞれ別のクラスにするのが正しかろうと単純に考え
てこのようにしていた。
Model View
Model View
+---------+ +----------+ +---------+
|Document | |Difference| |View |
+---------+ 2 1 +----------+ +---------+
|to_word()|-------|difference|-------|to_html()|
|to_char()| |new() | |to_tty() |
|... | |... | |... |
+---------+ +----------+ +---------+
アプリケーションの処理の流れをRubyで書くとこんな感じ。
doc1 = Document.new("string to compare 1")
doc2 = Document.new("string to compare 2")
d =Difference.new(doc1, doc2, :by_word)
v = View.new(d)
print v.to_html
でも考え直すと何だかおかしい。Differenceオブジェクトは一時的に生成して、名
前のある変数に束縛して扱うけれども、実際のところModelではなく、2つのDoc
umentオブジェクトをあるアルゴリズムで比較したViewでしかないように思う。
(SQLではselectでテーブルのviewから新しいテーブルを作って、さらにい
ろいろ操作できたような気がするけれど、それはSQLがOOPLじゃなくて手続き指向
だからじゃないかと思うので、とりあえず置いておく)
ModelといえるのはDocumentオブジェクトだけじゃないだろうか。つまりこ
う。
Model View View
+---------+ +----------+ +---------+
|Document | |Difference| |View |
+---------+ 2 1 +----------+ +---------+
|to_word()|-------|difference|-------|to_html()|
|to_char()| |new() | |to_tty() |
|... | |... | |... |
+---------+ +----------+ +---------+
でも、そうだとしたら、DifferenceクラスとViewクラスが別のクラスにな
っているのは、実装上区別したほうが分かりやすいという都合だけのような気がする。だ
ったらViewクラスはclassではなくmoduleにしてしまって、Differ
enceクラスにmix-inしたほうがきれいな気がする。つまりこんな感じ。
Model View
+---------+ +----------+
|Document | |Difference|
+---------+ 2 1 +----------+
|to_word()|-------|to_html() |
|to_char()| |to_tty() |
|... | |... |
+---------+ +----------+
doc1 = Document.new("string to compare 1")
doc2 = Document.new("string to compare 2")
print Difference.new(doc1, doc2, :by_word).to_html
どうせDocumentクラスだって中はCharStringやら何やらでできている
んだし、DifferenceクラスにViewとしての機能を持たせたっていいだろう。き
れいに書けさえすれば。
しかしこれで正しいのかどうかよく分からない。小さなことでも正しくないやり方でやる
のは性に合わないので、作業が滞っている。こんな小学生レベルのところで詰まってどう
するという気もするけれど、分からないんだからしょうがない。青木淳さんのサイトでも
見るか。
Parametric Method
-----------------
DocDiffにはたくさんのオプションがあり、実際の呼び出しはこうなっている。
doc1 = Document.new("Foo bar.\nBaz quux.", 'ASCII', 'LF')
doc2 = Document.new("Foo.\nBaz quux.", 'ASCII', 'LF')
docdiff = DocDiff.new
expected = [[:change_elt, ["Foo bar.\n"], ["Foo.\n"]],
[:common_elt_elt, ['Baz quux.'], ['Baz quux.']]]
assert_equal(expected, docdiff.compare_by_line(doc1, doc2))
将来はテキスト以外も比較できるように改良してこのようになる予定。
doc1 = Document.new("Foo bar.\nBaz quux.", 'text/plain', 'ASCII', 'LF')
doc2 = Document.new("Foo.\nBaz quux.", 'text/plain', 'ASCII', 'LF')
docdiff = DocDiff.new()
docdiff.update_config(config)
expected = [[:change_elt, ["Foo bar.\n"], ["Foo.\n"]],
[:common_elt_elt, ['Baz quux.'], ['Baz quux.']]]
assert_equal(expected, docdiff.compare_by_line(doc1, doc2))
内部で結果を得るにはこんな感じ。
Difference.new(doc1, doc2, # source1, source2 (Document)
'word', # resolution, granularity (line, word, char)
'lcs' # algorithm (lcs, ...)
).to_view('summary', # terseness, conciseness, cut, truncation, entirety (whole or summary)
'multiline', # layout, arrangement (inline or multiline)
'html', # format (html, tty, manued, wdiff, ...)
{:header=>'...'}) # tags
でもパラメータ1種類につきcase文のwhenを手書きで1つ増やすのは嫌だ。それ
を避けるためには、CharStringでやったように、プラグイン構造にするのがよ
さそう。パラメータの実際の効果を定義するViewモジュール内の各moduleで、パ
ラメータとしてメソッドに与えるシンボルを自己に結びつけて登録しておく。
(続くかも)