JavaScriptにおける戦略設計パターンのパワー



JavaScriptは非常によくその柔軟性で知られている言語です.あなたはおそらくJavaScriptの弱点のいずれかを言う人々を聞いたことがあるか、いくつかは、全体の反対を言う.何年も前にほとんど思われなかった驚くべきことをするために、これを我々の利点に使う傾向があるので、私は後者側でより多くの傾向があります.
反応は、すでに驚くべきツールとしてのバックアップが発明された事実事実証明です.またElectron どのような今日のブーム技術の力Visual Studio Code and Figma .
JavaScriptライブラリは、最新のJavaScript生態系においても熱い話題であるデザインパターンのいくつかのフォームを今日使用します.我々がこのポストに集中する1つのデザインパターンは、戦略設計パターンです.JavaScriptはとても柔軟性があるので、このポストで見られるように戦略的なデザインパターンを作ります.
このポストでは、戦略デザインパターンを巡ります.これは1つまたは複数の戦略(またはアルゴリズム)をタスクを実行するカプセル化されているよく知られているパターンです.これらのカプセル化された戦略はすべて同じシグネチャを持っているので、コンテキスト(インターフェイスを提供するもの)は、同じオブジェクトまたは異なるオブジェクト(または戦略)を扱うときに決して知ることができません.これは、それぞれの戦略は、我々のアプリケーションの寿命の間にそれを実現することなく、何度も一緒に交換することができることを意味します.

どのようなオブジェクトが関与している?


戦略パターンでは、これら2つのオブジェクトは常に関係しています.
  • 文脈
  • 戦略
  • コンテキストは常に使用されている現在の戦略への参照またはポインタを持たなければなりません.つまり、200の戦略を持っていれば、他の199が使用されるオプションです.あなたはそれらを「非アクティブ」であると考えることができます.
    コンテキストは、呼び出し元へのインターフェイスも提供します.呼び出し元はクライアントです.呼び出し元は、彼らの仕事を実行するための戦略のいずれかを使用することができますし、彼らはまた、オンデマンドでいつでも別の戦略と現在の戦略を切り替えることができます.
    実際の戦略は実行時に使用される実行ロジックを実装します.

    強さ


    通常の関数の実装では、関数は通常何かを行い、値を返します.ベース(コンテキスト)クラスと1つのストラテジがある場合、ストラテジーデザインパターンでは、その戦略を呼び出し、その結果を返す関数(他の単語で同じ)になります.
    しかし、2つ以上の戦略があるとき、ポイントは戦略が呼び出し元によって制御される多くの戦略のうちの1つでありえるということです.
    ここでの主な利点は、パターンの方法が書かれている限り、コードの動作の変更の単一のヒントを課すことなく、要求に応じて使用されるそれぞれの間で必要なスワップとして多くの戦略を定義することができます.
    戦略の実装は変化することができます、しかし、彼らが文脈によって予想通りに同じサインを保つ限り、コードに不必要な変更を経験する必要はありません.
    以下に、このフローを示す図です.

    実装


    最初の実装はフェッチに集中します.を定義しますcreateFetcher fetcherを作成するインターフェイスを返す関数です.これらのfetcherは、クライアントが生成することができますが、彼らはURLを取得し、取得し、その応答を返す限り、彼らは欲望を実装することができます.
    私たちはaxios リクエストライブラリ、ノードのネイティブhttps モジュールとnode-fetch それぞれの戦略として実装するライブラリ.
    合計で3つの戦略があります.
    const axios = require('axios').default
    const https = require('https')
    const fetch = require('node-fetch')
    
    function createFetcher() {
      const _identifer = Symbol('_createFetcher_')
      let fetchStrategy
    
      const isFetcher = (fn) => _identifer in fn
    
      function createFetch(fn) {
        const fetchFn = async function _fetch(url, args) {
          return fn(url, args)
        }
        fetchFn[_identifer] = true
        return fetchFn
      }
    
      return {
        get fetch() {
          return fetchStrategy
        },
        create(fn) {
          return createFetch(fn)
        },
        use(fetcher) {
          if (!isFetcher(fetcher)) {
            throw new Error(`The fetcher provided is invalid`)
          }
          fetchStrategy = fetcher
          return this
        },
      }
    }
    
    const fetcher = createFetcher()
    
    const axiosFetcher = fetcher.create(async (url, args) => {
      try {
        return axios.get(url, args)
      } catch (error) {
        throw error
      }
    })
    
    const httpsFetcher = fetcher.create((url, args) => {
      return new Promise((resolve, reject) => {
        const req = https.get(url, args)
        req.addListener('response', resolve)
        req.addListener('error', reject)
      })
    })
    
    const nodeFetchFetcher = fetcher.create(async (url, args) => {
      try {
        return fetch(url, args)
      } catch (error) {
        throw error
      }
    })
    
    fetcher.use(axiosFetcher)
    
    インサイドcreateFetcher 関数この行を作成します.const _identifer = Symbol('_createFetcher_')このラインは重要です.なぜなら、それぞれの戦略が実際に戦略であることを保証したいからです.そうでなければ、私たちのプログラムは、どんなオブジェクトでも戦略として扱うでしょう.それは戦略として扱われる何かを持っているという肯定的な利益のように聞こえるかもしれません、しかし、我々は我々が誤って踏み出すならば、我々のコードをより簡単に我々のデバッグ経験を妨げることができる誤りを起こしやすいようにします.Symbol ユニークな変数をdefinition . また、コンテキストの実装の中に隠されているので、オブジェクトが我々の外に作成された方法はありませんcreate 機能は戦略として扱われる.コンテキストによって提供されたインターフェイスから公開されたメソッドを使用する必要があります.
    クライアントコールuse 提出しますaxiosFetcher 現在のストラテジーとして使用され、クライアントが別の戦略でスワップするまで、参照としてバインドされるuse .
    現在、データを取得するための三つの戦略があります.
    const url = 'https://google.com'
    
    fetcher.use(axiosFetcher)
    
    fetcher
      .fetch(url, { headers: { 'Content-Type': 'text/html' } })
      .then((response) => {
        console.log('response using axios', response)
        return fetcher.use(httpsFetcher).fetch(url)
      })
      .then((response) => {
        console.log('response using node https', response)
        return fetcher.use(nodeFetchFetcher).fetch(url)
      })
      .then((response) => {
        console.log('response using node-fetch', response)
      })
      .catch((error) => {
        throw error instanceof Error ? error : new Error(String(error))
      })
    
    ハーレイ!私たちは今、それがコードで実装される方法を見ました.しかし、我々は我々がこれを必要とする現実世界で状況を考えることができますか?あなたは多くの実際に考えることができます!しかし、これが初めてこのパターンについて読んでいるならば、私は最初に実際に1つを見ない限り、事前にシナリオを考えるのは難しいと理解します.
    我々がこのポストにおいて行った例は、パターン実装を示します、しかし、これを読んでいる誰でも、なぜ、あなたがちょうどAxiosのようなものを直接使用することができるとき、3つのFetcher戦略を実装するのを気にすることができますか
    次の例では、戦略デザインパターンが確実に必要とされるシナリオについて説明します.

    異なるデータ型の取り扱い


    戦略パターンが最も輝いている場所は、ソートのようなときに異なるデータ型を処理する必要があるときです.
    前の例では、何らかのデータ型については気にしませんでした.しかし、何かのコレクションを受信し、それらを分類するようないくつかの狭いタスクを行うために必要なときにどうなりますか?彼らは正しくソートする必要がある場合はどうですか?
    それぞれが別のデータ型のコレクションであるいくつかのコレクションをソートする必要があるとき、私たちはネイティブを使用できません.sort それぞれの値は、それぞれの値を“less”と“large”で異なって扱うことができるからです.
    我々は、戦略パターンを使用することができますし、必要に応じて互換性を使用できるようにランタイムですぐに利用できるソートアルゴリズムの別のセットを定義することができます.
    これらのコレクションを考えてみましょう.
    const nums = [2, -13, 0, 42, 1999, 200, 1, 32]
    const letters = ['z', 'b', 'm', 'o', 'hello', 'zebra', 'c', '0']
    const dates = [
      new Date(2001, 1, 14),
      new Date(2000, 1, 14),
      new Date(1985, 1, 14),
      new Date(2020, 1, 14),
      new Date(2022, 1, 14),
    ]
    // Need to be sorted by height
    const elements = [
      document.getElementById('submitBtn'),
      document.getElementById('submit-form'),
      ...document.querySelectorAll('li'),
    ]
    
    私たちはSort 戦略クラスとSorter コンテキストクラス.
    クラスである必要はないことに注意してください.私たちは、少しだけ実装を多様化するために、現在クラスを使用するのを選びます.
    const sorterId = Symbol('_sorter_')
    
    class Sort {
      constructor(name) {
        this[sorterId] = name
      }
    
      execute(...args) {
        return this.fn(...args)
      }
    
      use(fn) {
        this.fn = fn
        return this
      }
    }
    
    class Sorter {
      sort(...args) {
        return this.sorter.execute.call(this.sorter, ...args)
      }
    
      use(sorter) {
        if (!(sorterId in sorter)) {
          throw new Error(`Please use Sort as a sorter`)
        }
        this.sorter = sorter
        return this
      }
    }
    
    const sorter = new Sorter()
    
    それはかなりまっすぐ進む.Sorter を参照Sort 現在使用中です.これは、呼び出し時にピックアップされるソート関数ですsort . それぞれSort インスタンスは戦略でありuse .
    The Sorter 戦略について何も知りません.日付ソーター、番号選別器などがあることを知りません.
    しかし、クライアントはすべてのSort インスタンスと戦略と同様にSorter :
    const sorter = new Sorter()
    
    const numberSorter = new Sort('number')
    const letterSorter = new Sort('letter')
    const dateSorter = new Sort('date')
    const domElementSizeSorter = new Sort('dom-element-sizes')
    
    numberSorter.use((item1, item2) => item1 - item2)
    letterSorter.use((item1, item2) => item1.localeCompare(item2))
    dateSorter.use((item1, item2) => item1.getTime() - item2.getTime())
    domElementSizeSorter.use(
      (item1, item2) => item1.scrollHeight - item2.scrollHeight,
    )
    
    それによって、完全に我々(クライアント)に応じてこれを処理することができます.
    function sort(items) {
      const type = typeof items[0]
      sorter.use(
        type === 'number'
          ? numberSorter
          : type === 'string'
          ? letterSorter
          : items[0] instanceof Date
          ? dateSorter
          : items[0] && type === 'object' && 'tagName' in items[0]
          ? domElementSizeSorter
          : Array.prototype.sort.bind(Array),
      )
      return [...items].sort(sorter.sort.bind(sorter))
    }
    
    我々は今コレクションの4種類のバリエーションをソートすることができます堅牢な15行の機能を持っている!
    console.log('Sorted numbers', sort(nums))
    console.log('Sorted letters', sort(letters))
    console.log('Sorted dates', sort(dates))
    

    そして、それはJavaScriptの戦略デザインパターンの力です.
    JavaScriptの値を関数として扱う機能のおかげで、このコード例はその機能にその機能をブレンドし、シームレスに戦略パターンで動作します.

    結論


    そして、それはこのポストの終わりを終わります!私はあなたが役に立つと将来のより便利なヒントのために調整滞在することを発見願っています!
    私を見つけるmedium