また、実装には多くのパターンがあります。 それらの1つをWebアプリケーションの例、つまりイベント駆動モデルとして使用したいと思います。
フロントエンド開発者にはよく知られています-DOMイベントを使用するたびに、このモデルを使用します。 小さなWebアプリケーションではなく、その上に構築してみましょう。ファイルマネージャです。
なぜファイルマネージャーなのですか? 私の意見では、このモデルは、(独自のモジュールだけでなく)さまざまなモジュールのセットで動作する必要があるアプリケーションに最適だからです。 まあ、また、ファイルマネージャーの新しいバージョンを使用しているところに取り組んでいるからです。 したがって、すべての例は非常に現実的ですが、認識できないほど単純化されています。
それでは始めましょう!
アプリケーションの構成は何ですか?
- コア。 ファイルに関するデータを保存し、サーバー側と対話します
- 表示。 ファイルのリストを描画し、ユーザーのアクションに応答します
- チーム。 ファイルで特定のアクションを実行する
そして、私たちの悟りがタオの真の力を見つけるために、私たちはモジュールの相互作用の失敗した方法-互いに「直接接触」を最初に説明します。
カーネルは、サーバー側からファイルのリストを受け取り、ビューに渡します。 ビューには、現在のディレクトリにあるものとユーザーのアクションを待っているものがユーザーに表示されます。
ユーザーがファイルをクリックします。 ビューは、何が起こっているのかを説明するためのコアに変わります。 カーネルはビューを落ち着かせ、何も起こらなかったと言います-ファイルだけが選択され、今ではそれがわかっています。
ユーザーは、フォルダーをダブルクリックします。 助けを求めてコアをパニックに見た。 カーネルは、間違ったアドレスをアドレス指定したことを心に静かに通知し、 「open」コマンドに送信します...
-それは複雑ですか?
しかし、今のところは耐えられます。 次に、別のビュー、ディレクトリツリーを追加しましょう。 カーネルは、この種類のファイルを受信ファイルのリストとともに転送する必要があることを覚えておく必要があります。
ビュー- 「お気に入り」を追加します。
コアはショックを受けています - 「これはあなたが必要なものですか?」
私は継続しません-そして、それは私たちが拡張の機会がないことを非常に明確です。 それぞれの新しいチームを実装するには、絶えずコアを追加する必要があります。
イベントのイデオロギー
そして今、私たちは、啓発の結果として(それが来ると約束した)、私たちが言わないまで、タスクについて瞑想します:
-変更はイベントです!
そして、明日考えていたことを明日忘れないように、詳細を書き留めます。
- アプリケーションにはオブジェクトがあり、この場合はイベントへのサブスクリプションを受け入れるカーネルです。
- すべての来訪者(コアを含む)は、彼らにとって重要なイベントを購読します。
- イベントが発生すると、カーネルはすべてのサブスクライバーに通知します。
- どのオブジェクトでもイベントを生成できます。
- イベントのリストは制限されていません。
例に戻り、すべてがどれほど簡単になったかを見てみましょう。
カーネルは、サーバー側からファイルのリストを受け取り、「open」イベントを生成します。 あらゆる種類が彼らがすべきことを描く
ユーザーがファイルをクリックします。 ビューは選択イベントを生成します。 カーネルは、選択されたファイルを記憶しています。
ユーザーは、フォルダーをダブルクリックします。 ビューはdblclickイベントを生成します。 すぐに「open」コマンドがバトルに突入し、カーネルにサーバーへのリクエストを強制します。
-簡単になりましたか?
間違いなく。 しかし、さらに、このアプリケーションは2つの重要なプロパティを取得しています。
- 弱いつながり。 モジュールはお互いについて何も知る必要がなくなりました。
- 2番目の特性はそれほど明白ではありません。それに気づかなかった人のために、最後に説明します。
そして今、私たちが最も愛するポイントに到達しましょう-コードを書いてください!
実装は1つの例外を除いてjQueryの場合と同じになります-
特定のイベントコールに関連付けられたデータは、イベントオブジェクト自体のフィールドに転送されます。
"use strict"; // window.elFinder = function(node, options) { var self = this, // listeners = {}; /** * id * * @type Array */ this.selected = []; /** * * * @type Object */ this.ui = {}; /** * * * @type Object */ this.commands = {}; /** * . * * @param String , * @param Object * @return elFinder */ this.bind = function(event, callback) { var i; if (typeof(callback) == 'function') { event = ('' + event).toLowerCase().split(/\s+/); for (i = 0; i < event.length; i++) { if (listeners[event[i]] === void(0)) { listeners[event[i]] = []; } listeners[event[i]].push(callback); } } return this; }; /** * . * * @param String * @param Object * @return elFinder */ this.unbind = function(event, callback) { var l = listeners[('' + event).toLowerCase()] || [], i = l.indexOf(callback); i > -1 && l.splice(i, 1); return this; }; /** * . * * @param String * @param Object * @return elFinder */ this.trigger = function(event, data) { var event = event.toLowerCase(), handlers = listeners[event] || [], i; if (handlers.length) { event = $.Event(event); for (i = 0; i < handlers.length; i++) { // // event.data = $.extend(true, {}, data); try { if (handlers[i](event, this) === false || event.isDefaultPrevented()) { break; } } catch (ex) { window.console && window.console.log && window.console.log(ex); } } } return this; } /** * . * * @param Object * @return jQuery.Deferred */ this.ajax = function(data) { var self = this, dfrd = $.Deferred() .fail(function(error) { self.error({error : error}); }) .done(function(data) { // self.trigger(data.cmd, data); }); $.ajax({ // ... data : data }).error(function() { dfrd.reject('Unable to connect to backend'); }).success(function(data) { if (!this.validData(data)) { dfrd.reject('Invalid data from backend'); } else if (data.error) { dfrd.reject(data.error); } dfrd.resolve(data); }) return dfrd; } // - - // .............. // "" // - / . $.each(['open', 'select', 'error'], function(i, name) { self[name] = function() { var arg = arguments[0]; return arguments.length == 1 && typeof(arg) == 'function' ? self.bind(name, arg) : self.trigger(name, $.isPlainObject(arg) ? arg : {}); } }); // this.open(function(e) { // }) .select(function(e) { // this.selected = e.data.selected; }) .error(function(e) { // alert(e.data.error); }); } elFinder.prototype = { // views : { // cwd : function(fm, node) { var self = this, view = $('<div/>') .appendTo(node) .delegate('div[id]', 'click', function() { view.find('div[id]').removeClass('selected'); $(this).addClass('selected'); // "select" fm.select({selected : [this.id]}); }) .delegate('div[id]', 'dblclick', function() { fm.trigger('dblclick', {target : this.id}); }) ; // "open" fm.open(function(e) { var files = e.data.files; // }); } }, // commands : { open : function(fm) { this.enabled = false; // "dblclick" fm.select(function(e) { // , / // , Enter this.enabled = e.data.selected.length > 0; }) bind('dblclick', function(e) { // fm.ajax({cmd : 'open', target : e.data.target}); }); } } }
繰り返しますが、コードは大幅に簡素化され、プロジェクトの実際のコードとは大きく異なります。 実際にどのように見えるかを知りたい場合-githubのプロジェクト
そして今まとめます。
イベント駆動型モデルを使用することにより、どのようなメリットが得られましたか?
- アプリケーションコンポーネントの接続性が弱く、その結果、そのさらなる拡張の良い機会。
- オープンAPI これは最初は明らかではありませんが、特に何もせずに、
他の開発者がプロジェクトのメインコードに干渉することなく拡張機能を作成できるようにするAPIを作成しました。 また、外部スクリプトはアプリケーションと対話できます。
短所:
- 放送。 特定のリスナーにメッセージを送信することはできません。 私たちの場合、これは重要ではありません。
- 「不安」 すべてのオブジェクトがすべてのイベントをリッスンでき、すべてのオブジェクトがイベントを生成できます。 私たちの場合、2番目が重要です。 つまり、リスナーは送信されたデータを信頼できません。 リスナーのデータを確認することで解決します。
- 誰が最初の-それとスリッパ。 リスナーは、イベントのそれ以上の通知を停止できます。 理論的には、それは決して解決されません。 モジュールの接続/イベントのサブスクライブの優先順位を監視することにより、部分的に解決されました。 しかし、私たちは1年以上DOMイベントを扱ってきましたが、この欠陥はよく知られています。
私の意見では、私たちにとって唯一の重要なマイナスは、この素晴らしいパターンを使用することのすべての利点を上回っていません。
関連リンク:
en.wikipedia.org/wiki/Coupling_(computer_science)
en.wikipedia.org/wiki/Event-driven_architecture
en.wikipedia.org/wiki/Event-Oriented_Programming