JavaScriptのデザインパターン:オブザーバーパターン

JavaScriptでは、頻繁に起きる問題があります。 特定のイベントに応答して、これらが提供するデータを使用してページの一部を更新する方法が必要です。 たとえば、1つまたは多くのコンポーネントに投影するユーザー入力とします。 これは、すべての同期を維持するために、コード内の多くのプッシュとプルにつながります。

これは、オブザーバーデザインパターンが助けることができる場所です—それは要素間の一対多のデータバインディングを可能にします。 この一方向データバインディングは、イベント駆動型にすることができます。 このパターンを使用すると、特定のニーズに合わせて解決する再利用可能なコードを構築できます。

この記事では、オブザーバーデザインパターンを探索したいと思います。 これは、クライアント側のスクリプトで見られる一般的な問題を解決するのに役立ちます。 これは、一対多、一方向、およびイベント駆動型のデータバインディングです。 同期する必要がある多くの要素がある場合に頻繁に発生する問題です。

私はECMAScript6を使ってパターンを説明します。 はい、クラス、矢印関数、および定数があります。 あなたがまだ慣れていない場合は、自分でこれらのトピックを探索すること自由に感じます。 私は構文糖のみを導入するES6の部分を使用するので、必要に応じてES5で移植可能です。そして、テスト駆動開発(TDD)を使用してパターンを処理します。 このようにして、各コンポーネントがどのように有用であるかを知る方法があります。

ES6の新しい言語機能は、いくつかの簡潔なコードを作成します。 それでは、始めましょう。

イベントオブザーバー

パターンの高レベルビューは次のようになります:

EventObserver│ ├── subscribe: adds new observable events│ ├── unsubscribe: removes observable events|└── broadcast: executes all events with bound data

私はオブザーバーパターンを肉付けした後、私はそれを使用する単語数を追加します。 単語数コンポーネントは、このオブザーバを取り、それをすべて一緒に持って来ます。

EventObserverを初期化するには、次の操作を行います:

class EventObserver { constructor() { this.observers = ; }}

観測されたイベントの空のリストから始め、新しいインスタンスごとにこれを行います。 これからは、デザインパターンを肉付けするために、EventObserver内にさらにメソッドを追加しましょう。

新しいイベントを追加するには、Subscribeメソッド

を実行します:

subscribe(fn) { this.observers.push(fn);}

観測されたイベントのリストを取得し、新しい項目を配列にプッシュします。 イベントのリストは、コールバック関数のリストです。

このメソッドをプレーンなJavaScriptでテストする方法の1つは次のとおりです:

// Arrangeconst observer = new EventObserver();const fn = () => {};// Actobserver.subscribe(fn);// Assertassert.strictEqual(observer.observers.length, 1);

私はNodeでこのコンポーネントをテストするためにNode assertionsを使用します。 まったく同じアサーションがチャイアサーションとも存在します。

メモ観察されたイベントのリストは、謙虚なコールバックで構成されています。 次に、リストの長さをチェックし、コールバックがリスト上にあることをアサートします。

イベントを削除するには、購読解除メソッド

を実行します:

unsubscribe(fn) { this.observers = this.observers.filter((subscriber) => subscriber !== fn);}

コールバック関数に一致するものをリストから除外します。 一致するものがない場合、コールバックはリストに残ります。 フィルターは新しいリストを返し、オブザーバーのリストを再割り当てします。

この素敵な方法をテストするには、次のようにします:

// Arrangeconst observer = new EventObserver();const fn = () => {};observer.subscribe(fn);// Actobserver.unsubscribe(fn);// Assertassert.strictEqual(observer.observers.length, 0);

コールバックは、リストにあるのと同じ関数と一致する必要があります。 一致するものがある場合、unsubscribeメソッドはリストからそれを削除します。 注テストでは、関数参照を使用して追加および削除します。

ブロードキャストメソッド

すべてのイベントを呼び出すには、次の操作を行います:

broadcast(data) { this.observers.forEach((subscriber) => subscriber(data));}

これは、観測されたイベントのリストを反復処理し、すべてのコールバックを実行します。 これにより、サブスクライブされたイベントに必要な一対多の関係が得られます。 コールバックデータをバインドするdataパラメータを渡します。

ES6は、矢印関数を使用してコードをより効果的にします。 ほとんどの作業を行う(subscriber) => subscriber(data)関数に注意してください。 このone-liner arrow関数は、この短いES6構文の利点があります。 これは、JavaScriptプログラミング言語の明確な改善です。

このブロードキャスト方法をテストするには、次のようにします:

// Arrangeconst observer = new EventObserver();let subscriberHasBeenCalled = false;const fn = (data) => subscriberHasBeenCalled = data;observer.subscribe(fn);// Actobserver.broadcast(true);// Assertassert(subscriberHasBeenCalled);

変数の値を変更できるように、constの代わりにletを使用します。 これにより、変数が変更可能になり、コールバック内でその値を再割り当てできます。 コード内でletを使用すると、変数がある時点で変更されていることを仲間のプログラマに信号を送ります。 これにより、JavaScriptコードの読みやすさと明快さが増します。

このテストは、オブザーバーが期待どおりに動作していることを確認するために必要な自信を与えます。 TDDでは、単純なJavaScriptで再利用可能なコードを構築することがすべてです。 プレーンなJavaScriptでテスト可能なコードを書くことには利点があります。 すべてをテストし、コードの再利用に適したものを保持します。

これで、EventObserverを肉付けしました。 問題は、これで何を構築できるかということです。

動作中のオブザーバーパターン:ブログの単語数デモ

デモのために、それはあなたのための単語数を保持するブログ記事を所定の位置に置くための時間。 入力として入力するすべてのキーストロークは、observerデザインパターンによって同期されます。 これは、すべてのイベントが必要な場所への更新を起動するフリーテキスト入力と考えてください。

フリーテキスト入力から単語数を取得するには、次のようにします:

const getWordCount = (text) => text ? text.trim().split(/\s+/).length : 0;

終わった! この一見単純な純粋な関数にはたくさんのことが起こっているので、謙虚な単体テストはどうですか? このようにして、私がこれを何を意図していたのかは明らかです:

// Arrangeconst blogPost = 'This is a blog \n\n post with a word count. ';// Actconst count = getWordCount(blogPost);// Assertassert.strictEqual(count, 9);

blogPost内のやや奇抜な入力文字列に注意してください。 私はこの機能ができるだけ多くのエッジケースをカバーするつもりです。 それが私に適切な単語数を与える限り、私たちは実際には正しい方向に向かっています。補足として、これがTDDの本当の力です。 この実装を反復して、できるだけ多くのユースケースをカバーすることができます。 単体テストは、これがどのように動作するかを示します。 何らかの理由で動作に欠陥がある場合は、反復して微調整するのは簡単です。 テストでは、他の人が変更を加えるのに十分な証拠が残されています。

これらの再利用可能なコンポーネントをDOMに配線する時間。 これは、あなたが普通のJavaScriptを振るうと、右のブラウザにそれを溶接するために取得する部分です。

それを行う方法は、ページに次のHTMLを持つことです:

<textarea placeholder="Enter your blog post..." class="blogPost"></textarea>

このJavaScriptが続いています:

const wordCountElement = document.createElement('p');wordCountElement.className = 'wordCount';wordCountElement.innerHTML = 'Word Count: <strong>0</strong>';document.body.appendChild(wordCountElement);const blogObserver = new EventObserver();blogObserver.subscribe((text) => { const blogCount = document.getElementById('blogWordCount'); blogCount.textContent = getWordCount(text);});const blogPost = document.getElementById('blogPost');blogPost.addEventListener('keyup', () => blogObserver.broadcast(blogPost.value));

すべての再利用可能なコードを取得し、オブザーバーデザインパターンを配置します。 これにより、テキスト領域の変更が追跡され、そのすぐ下に単語数が表示されます。 DOM APIでbody.appendChild()を使用して、この新しい要素を追加しています。 次に、イベントリスナーをアタッチして、それを生き生きとさせます。

注矢印機能を使用すると、ワンライナイベントを配線することができます。 実際には、これを使用してすべてのサブスクライバーにイベント駆動型の変更をブロードキャストします。 ここでは() => blogObserver.broadcast()が大部分の作業を行います。 テキスト領域への最新の変更をコールバック関数に渡すことさえできます。 はい、クライアント側のスクリプトは超クールです。

以下はCodePenです:

CodePenのSitepoint(@SitePoint)によるペンThe Observerパターンを参照してください。

今、私はこの機能を完全とは呼ばないでしょう。 これは、オブザーバーデザインパターンの出発点に過ぎません。 私の心の中での質問は、どこまで行って喜んでいるのですか?

先を見て

このアイデアをさらに取るのはあなた次第です。 Observerデザインパターンを使用して新しい機能を構築するには、多くの方法があります。

あなたはデモを強化することができます:

  • 段落数をカウントする別のコンポーネント
  • 入力されたテキストのプレビューを表示する別のコンポーネント
  • markdownサポートでプレビューを強化します。

上記の機能強化は、プログラミングチョップに挑戦します。

結論

オブザーバデザインパターンは、JavaScriptの実世界の問題を解決するのに役立ちます。 これは、同じデータと同期された要素の束を維持するという長年の問題を解決します。 頻繁にそうであるように、ブラウザが特定のイベントを発生させるとき。 私は今ではあなたのほとんどがこのような問題に遭遇し、ツールやサードパーティの依存関係に向かって実行していると確信しています。

このデザインパターンは、あなたの想像力が行くことをいとわない限り、あなたを装備します。 プログラミングでは、ソリューションをパターンに抽象化し、再利用可能なコードを構築します。 これがあなたをどこまで連れて行くかに制限はありません。

私はあなたが、少しの規律と努力で、あなたがプレーンなJavaScriptで行うことができますどのくらい見ることを願っています。 ES6などの言語の新機能は、再利用可能な簡潔なコードを記述するのに役立ちます。

この記事はGiulio Mainardiによって査読されました。 SitePointのコンテンツを最高のものにするためのSitePointのピアレビュアーのすべてに感謝します!

コメントを残す

メールアドレスが公開されることはありません。