Cotton Bolls: Observable関数
« データベース定義XML | トップページ | テストデータのセットアップ »
2004.09.05
Observable関数
業務系のWebアプリには,よくマスター項目の入力支援サブウィンドウが必要になる.
例えば,郵便番号から都道府県と住所を補完してくれる入力支援サブウィンドウを考えよう(補完ボタン押下でサンプル表示):
ユーザが補完ボタンを押すと,住所補完サブウィンドウがポップアップする.住所補完サブウィンドウには,郵便番号マスタから検索された住所の候補が表示され,住所を選択するとサブウィンドウが閉じて都道府県と住所が補完される.
単純に実装すれば,住所補完サブウィンドウの選択ボタン押下時に次のようなJavaScriptコードを書けばよいだろう:
function selectButton_onClick(pref_code, address) { var form = opener.document.address_form; form.pref_code.value = pref_code; form.address.value = address; window.close(); }
ここで,親ウィンドウのHTMLが次のようになっていると仮定する:
<form action="..." name="address_form"> ... </form>
このとき,住所補完サブウィンドウからはopener.document.address_formが親ウィンドウに書いてあるformオブジェクトを表す.selectButton_onClick関数は,このformオブジェクト内にある都道府県リストボックスと住所テキストボックスの値を設定して自らのウィンドウを閉じる.
この実装がまずいのは明らかだ.住所補完サブウィンドウが親ウィンドウの詳細を知っている.そのため,カプセル化の原則を破っており,親ウィンドウの修正や住所補完サブウィンドウの再利用を困難にしてしまう.
これを改善するには,サブウィンドウから親ウィンドウへの依存関係を断ち切る必要がある.ここではObserverパターンを使って改善を試みよう.すなわち,住所補完サブウィンドウをObservable,親ウィンドウをObserverとみなせばよい.
最初の実装に戻って住所補完サブウィンドウの呼び出し部分を見てみよう:
function addressCompletionButton_onClick() { var zip_code = document.address_form.zip_code.value; var url = "address-completion.html?zip_code=" + zip_code; var style = "width=420,height=250"; var subWindow = window.open(url, "AddressCompletion", style); subWindow.focus(); }
subWindowオブジェクトは住所補完サブウィンドウを表す.このウィンドウに対してaddObserverを呼び出すようにすればよい.素直に考えれば次のようになるだろう:
var subWindow = window.open(url, "AddressCompletion", style); subWindow.addObserver(this); subWindow.focus();
ところが,これはうまくいかない.住所補完サブウィンドウ(address-completion.html)で
function addObserver(observer) { ... }
を定義しても,HTMLファイルが完全にロードされない限りsubWindow.addObserverが未定義になってしまうからだ.実際に調べてみたが,JavaScriptでこのロードタイミングを制御する方法はわからなかった.
そこで,JavaScript言語の動的な側面に着目しよう.次のようなObservable関数を用意する:
function Observable(o) { o.observers = new Array(); o.addObserver = function(observer) { o.observers.push(observer); } o.notifyObservers = function(arg) { for (var i in o.observers) { o.observers[i](arg); } } }
この関数は,任意のオブジェクトをObservableにしてしまう関数だ.
JavaScriptに慣れてない人はnotifyObserversメソッドの中身がよくわからないかもしれない.Observerパターンでは,Observerのupdateメソッドを呼ぶことでObservableの更新を通知する.ところが,JavaScriptは関数もオブジェクトだ.そのため,楽をしてupdateメソッドそのものをObserverとして利用している.
実際親ウィンドウ側でObservable関数を適用したコードは次のようになる:
var subWindow = window.open(url, "AddressCompletion", style); Observable(subWindow); var observer = function(arg) { var form = document.address_form; form.pref_code.value = arg.pref_code; form.address.value = arg.address; } subWindow.addObserver(observer); subWindow.focus();
このように,都道府県コードと住所を更新する関数をObserverとして住所補完サブウィンドウに登録する.
住所補完サブウィンドウ側はすでにObservableになっているため,notifyObserversメソッドがそのまま利用できる:
function selectButton_onClick(pref_code, address) { notifyObservers({pref_code: pref_code, address: address}); window.close(); }
notifyObserversの引数にはオブジェクトのハッシュを渡している(JavaScriptのオブジェクトイニシャライザ).オブジェクトイニシャライザは次とコードと等価だ:
var arg = new Object(); arg.pref_code = pref_code; arg.address = address; notifyObservers(arg);
このように,オブジェクトイニシャライザをPerlのような名前付き引数として利用すると便利だ.
以上で住所補完サブウィンドウから親ウィンドウの詳細を断ち切ることができた.逆に親ウィンドウは住所補完サブウィンドウのupdateの呼ばれ方を知る必要がある.オブジェクト指向の原則の一つ,「依存関係の逆転」だ.
ここで紹介したObservable関数が気に入ってるのは,たった10行程度の中にObserverパターンがすべて凝縮されていることにある.JavaScriptではそれが一つの関数になり,任意のオブジェクトに対して動的にMix-inできるところが面白い.他のパターンもこんなふうに簡潔に表現できるのかもしれない.
追記とお詫び
ここで書いたObservable関数の方法には問題があることがわかりました.
会社の同僚に指摘されたんですが,サブウィンドウ側でリロードするとnotifyObserversもobserversの情報も消えてしまいます.リロードには気がつきませんでした.そうすると,やはり親ウィンドウ側でObserverの管理クラスを用意しないとどうしようもなさそうです.これでは全然お手軽でもないので上記の方法は却下します.
あまり使い込んでないものを公表するのはよくないな….すみませんm(_ _)m.
08:00 PM | 固定リンク
トラックバック
この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/1361108
この記事へのトラックバック一覧です: Observable関数:
コメント
こんにちは。通りすがったモノです。
リロードに問題ありとは言え、Observable関数と、updateメソッドそのものをObserverとするアイディアは非常に興味深く、参考になりました。
ところで、Observerパターンで実装しようとした意図には、依存関係を断ち切ること以外にもあるのでしょうか? Observerとなるwindowが複数存在するようなケースはまず無いと思うので、親にupdate関数を定義し、サブウィンドウからopener.update(親に渡したい値)を呼ぶ。このような単純な方法ではダメだったのでしょうか?
投稿者: kiri (November 29, 2004 12:32 AM)
実際に採用した方法はそれに近いですよ.
でも,住所補完サブウィンドウのほかに別のマスターデータの補完サブウィンドウが必要だったので,親ウィンドウ側は複数のupdate関数が必要になりました.
そこで親ウィンドウにカレントのupdate関数を登録する変数を定義し,サブウィンドウを呼ぶ直前にupdate関数を変数にセットしてサブウィンドウ側から呼び出す,という形をとりました.変数名を固定にしておけば,依存関係は少しマシになります.
確かにobserversをArrayにしたのはオーバースペックかもしれません.でも,こっちのほうがわかりやすいと思いますし,用途をサブウィンドウに限定する必要もないと思ったからです.
投稿者: masarl (November 29, 2004 01:38 PM)