はてなキーワード: イベントハンドラとは
ご意見くださって、ありがとうございます。
例にあげたRPGのモデル設計はhttp://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1178047006を参考にしました。
ですが自分で書くとしても、十中八九そのような設計にするでしょう。
モンスター.attack(勇者.attackPower)
勇者.attack(モンスター)
主語.動詞(目的語)
モデルに対する命令は、ふつう入力Controllerのイベントハンドラに書かれる。attackに関する命令はどちらの設計をとったとしても"攻撃が選択された"イベントハンドラに書かれるだろう。
モンスター.attack(勇者.attackPower)
だと、たとえばモンスターにダメージを与える要素として、他に「すばやさ」などを加えようとしたら、モンスターのattackメソッドを変更する必要が生じる。
また、FF5には"竜騎士のジャンプ"という攻撃方法があった。ジャンプを選択すると、竜騎士は空高く飛び、一定時間が経ったのちに落ちてきて、空中からモンスターを攻撃する、というもの。これを実装しようとすると、入力Controllerのイベントハンドラの直前の部分で別スレッドに投げるなり、タイマーを起こすなりして処理したくなる。
味方同士の”連携攻撃”を実装しようとするとやはり、入力Controllerのイベントハンドラを修正したくなるだろう。
つまり、悪い方の設計ではModel「勇者」への設計変更の都合で、Model「モンスター」や入力Controllerを修正する必要が出てきてしまう。
勇者.attack(モンスター)
では、どの設計変更の例でも、勇者側を変更するだけで良い。この設計には、クラス間の責任の分担がはっきりするという利点がある。
list.append(elem)でしょ。
リストは実質的にはオブジェクトというより、ただのデータ構造。
リストやディクショナリ(連想配列)は便利な道具だが、これを前面に押し出した設計は複雑さを招く場合がある。
たとえば、"複数のモンスターの集まり"や"ソートされたリスト"を、リストを継承して実装しようとすると、とたんにドツボにハマる。
Onスライムが仲間を呼んだ(){ スライムの集まり.append(スライム) スライムの個数ラベル.更新(スライムの集まり.length) if(スライムの集まり.length == 1 and スライムの集まり.get(0).name == "キングスライム"){ スライムの名前ラベル.更新("キングスライム") } }
スライムは8匹集まるとキングスライムになるので、最初の一行でスライムの集まりを変更して以降、スライムの個数を気にする必要があることに注意して欲しい。
上記のコードを洗練させるために、Model「スライムの集まり」で個数が変わった時にイベント"OnLengthChange"を発行するようにしても良いが、そのようにすると、lengthメソッドを安全に呼べるのはそのイベントハンドラのみ、ということになる。プログラマに注意を促すために、lengthメソッドのコメントにその旨を注意書きしておく必要がある。
"スライムの集まり"リストクラスは、プログラマが期待するだろうリストクラスの振る舞い《append後の個数はappend前+1》を破壊している。
"ソートされたリスト"は、appendメソッドをオーバーライドして、追加時に勝手に順番を並び替えてくれるようなものだが、これも《リストに要素を追加した時には、その要素は末尾に置かれる》というリストクラスの振る舞いを破壊する。スライムの例と同じように、イベントの発行とメソッドの注意書きが必要になる。プログラマはそれらのクラスを使うときに、より注意深くならなくてはいけない。
このように、クラスの振る舞いを壊してしまうようなクラス設計は、プログラマを地雷原におくりこむことになる。
とはいえ、リストのようなデータ構造を便利に使えるシーンもある。
はじめからおわりまで100行程度のスクリプトで、とくにイベントドリブンにするほどの複雑さがない場合。順序を追って読めるので、時間的な流れがわかりやすい。
一方、RPGの例で示したような設計は、オブジェクト指向をフルに使って、コードを責任に応じて役割分担しやすくなる利点がある。
前者は"トランザクションスクリプト"的設計と呼ばれ、後者は"ドメインモデル"設計と呼ばれる。
cssやstyle指定で
tr.even {background-color:silver;}
と指定していたとして、何かのイベントなどで
this.style.backgroundColor='red';
(ここでの this は、対象の要素 - つまり <tr class="even">)などと指定し、もとに戻すときは
this.style.backgroundColor='';
と、空の文字列を指定すればうまくいくみたい。これは、正式な仕様なのかな?
例としては
<tr class="even" onmouseover="this.style.backgroundColor='blue';" onmouseleave="this.style.backgroundColor='';">
「VBやC#でイベントを実装するのって、formクラスを継承したユーザ定義のクラスでメソッドを実装し、イベントハンドラを定義して処理を振り分けているだけ」
普通、そういうイベントハンドラで処理を呼び出される形態のものをイベントドリブンと言うんだが。
VC++やVC#はVBよりも自由度が高く、イベントドリブンでないプログラムも作れるようになっているので「あれ? イベントドリブンって不要じゃね?」と錯覚しがちだが、ウィンドウシステムが一般的である今、イベントドリブンが廃れることは無いと思うが。
長いので一行まとめ:便利になる前の試行錯誤の時点で潰されてるんじゃないかって思った。
昔、まだダイヤルアップの人とか 800*600 の人とかが普通にいた頃、自サイトをコンパクトにする試みを色々やっていた。何かそういう実験をひそやかにやるのが主な目的のサイトだった。ナビゲーションリンクを各ページに埋め込む代わりに JavaScript に記述して呼び出せばキャッシュが効いて効率が上がるんじゃないか、とか。(JavaScript の効かないブラウザ・切ってるブラウザ向けに、総目次になっているトップへのリンクを表記しといて最低限のアクセシビリティは保ってた。アクセシビリティなんて言葉知らなかったけど。)
フレームや段組に貴重な画面領域をごそっと持って行かれるのを避けたかった。でもマウスホイールが付いてないマウスもザラにあったり、ましてやマウスジェスチャーなんて誰も使ってなかった時代背景から、スクロールも最小限に抑えたい気持ちもあった。そこで、普段は隠れてるけど、ある簡単な操作(画面右下にマウスを移動させるとか)でナビゲーションリンクが出て来る仕組みを色々考案しては試していた。
その中のひとつとして、右クリック(コンテキストメニュー)にナビゲーションを仕込むという試みをした時があった。本来のコンテキストメニューに戻す項目をそのメニュー内と画面の端っこにつけて、トップページで使用法をアナウンスする形でリリースした。
その後間もなく、どういう経緯で目をつけられたのかは分からないけど、今で言うモヒカン族くずれの方々が「ユーザービリティ!ユーザービリティ!」と雄叫びを上げながらわらわらとサイト狩りに来たのでびびってやめた。相当びびったのでその後数年間 W3C 教(というか某方面教)に入信して狩られないお作法を学んだ。
あれから数年、別腹で試みて、当時はノーリアクションだったり難色を示されてた「JavaScript でお絵かき」とか「送信中は押せない送信ボタン」とか「記事を貼り付ける位置を自由に決められる掲示板」とかが、世間様に受け入れられたり色んな人が改良してたりするのを見て、土壌が大分変わってるように感じた。だもんでコンテキストメニューいじりも何か進展があったんじゃないかとおぼろげに思ってたりしたけど、具体的には Flash のコンテキストメニューいじるくらいのノリでグリグリやってるんじゃないかなあと思ってたけど、その辺はみたとこ昔と変わらないんだね。
もしかしたら今もまだ「右クリック」に過剰反応して、そういう狩りをして芽を摘んでる人がいるんじゃないのかなあとか思った。そんな自分も document.body に付いてるイベントハンドラは根こそぎ削除して閲覧してたりして、評価する機会をふいにしてるから、人の事言えた義理でもないけど。
ちなみに単純な右クリック禁止自体はやる意味が無いのでやったことがない。6年くらい前に「右クリック禁止解除」ってブックマークレット作ってたくらいだから(当時はブックマークレットって言葉も知らなかったけど)やりたくないっていうアドバンスな気持ちも持ち合わせてたんだと思う。
エディタなしでも大丈夫。アドレスバーでJavaScriptを実行してみよう!
アドレスバーに入れた場合、最後の値が表示されます。
javascript:"Hello world!"
サンプルでは良くあるけれど、実際、ダイアログはほとんど使いません。でもちょっとしたデバッグには便利です。
javascript:alert("Hello alert() world!")
このとき「alert()」とは「window.alert()」を表します。windowオブジェクトはブラウザの表示ウィンドウを表すオブジェクトです。windowオブジェクトのプロパティやメソッドを使用する場合は、alertに限らず「window.」が省略できます。
これが「print "Hello world!"」に一番近と思います。
javascript:document.write("Hello document.write() world!"); document.close()
documentオブジェクトは表示中のドキュメント全体を表すオブジェクトです。
Document Object Modelを利用して要素を追加します。
javascript:document.write("<html><body></body><html>"); document.close(); var p = document.createElement("p"); p.appendChild(document.createTextNode("Hello DOM world!")); document.body.appendChild(p);
現在のドキュメントがHTMLであれば、先ほどの「document.write()」はHTMLを扱うことになります。これを使用してbody要素を作っておき、そこへp要素を追加しています。
[追記] createTextNodeを使用するように変更しました。
javascript:document.write("<html><body>Hello world!</body><html>"); document.close(); p = document.createElement("p"); p.innerHTML = "Hello javascript world!"; document.body.appendChild(p); p.addEventListener("click", function(){this.style.borderStyle="solid"}, false)
イベントハンドラの使用方法は幾つかありますが、ここでは「addEventListener」を使用します(申し訳ありませんがIEでは動きません)。この例では、イベントに対するコールバック関数として無名関数の「function(){……}」が登録されます。p要素がクリックされたとき「function(){……}」が呼び出されます。このとき、this(変数ではありません!)が、要素pを示します。