The Power of Strategy Design Pattern in JavaScript
Tags: javascript, webdev, node, programming
Published: 2022-04-13 03:20:31

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jsaofnrfd6coezsedpfl.jpg)

JavaScript is a language that is very well known for its flexibility. You've probably heard people that say its one of JavaScript weaknesses or even some that say the total opposite. I tend to be more on the latter side because we tend to use this to our advantage to do amazing things that hardly seemed possible many years ago.

React is already a factual proof that backs that up as amazing tools were invented thereafter. There is also [Electron](https://www.electronjs.org/) which powers today's booming technology like [Visual Studio Code](https://code.visualstudio.com/) and [Figma](https://www.figma.com/).

Every JavaScript library uses some form of a design pattern nowadays which is also a hot topic in the modern JavaScript ecosystem. One design pattern that we will focus on in this post is the Strategy Design Pattern. And because JavaScript is so flexible it makes design patterns like the Strategy robust as we will see in this post.

In this post, we will be going over the Strategy Design Pattern. This is a well known pattern that encapsulates one or more _strategies_ (or _algorithms_) to do a task. These encapsulated strategies all have the same signature so the context (the one who provides the _interface_) never knows when they are dealing with the same or different object (or _strategy_). This means that each strategy can be swapped together many times without our program ever realizing it during the lifetime of our app.

## What kind of objects are involved?

In the Strategy pattern, these two objects are always involved:

1. Context
2. Strategy

The _Context_ must always have a reference or pointer to the _current strategy being used_. That means if we have 200 strategies then it's optional that the other 199 are used. You can think of them as being "inactive".

The _Context_ also provides the interface _to the caller_. The _caller_ is the _client_. The _caller_ can use any of the strategies to perform their work and they can also switch the current strategy with another strategy at any time on demand.

The actual _Strategy_ implements the execution logic for itself that will be used when executed.

## Strengths

In a normal function implementation the function is usually doing something and returns a value. In the Strategy Design Pattern when there is a base (_Context_) class and one Strategy it is like a function that calls the Strategy and returns the result (in other the words the same thing).

But when there are _two_ or more strategies, the point is that the strategy can be one of many strategies controlled by the caller.

The major benefit here is that we can define _as many strategies as we want_ and swap between each one to be used on demand without inflicting a single hint of change in behavior of code as long as the pattern is written the way it should.

Implementations of a Strategy can change but as long as they keep the same signature as expected by the context then there is no need to experience unnecessary changes to code.

Here is a diagram depicting this flow:

![strategy-pattern-diagram-flow1.png](https://jsmanifest.s3.us-west-1.amazonaws.com/posts/power-of-strategy-design-pattern-in-javascript/strategy-pattern-diagram-flow1.png 'Strategy Pattern Diagram Flow 1 Png')

## Implementation

Our first implementation will focus on fetching. We'll define a `createFetcher` function that returns the _interface_ to create fetchers. These fetchers can be spawned by the client and can be implemented however they desire _as long as they take in a url, retrieve and returns its response_.

We'll be using the [axios](https://github.com/axios/axios) request library, node's native [https](https://nodejs.org/dist/latest-v16.x/docs/api/https.html) module and the [node-fetch](https://github.com/node-fetch/node-fetch) library to implement as one strategy each.

In total we will have 3 strategies:

```js
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)
```

Inside our `createFetcher` function we created this line: `const _identifer = Symbol('_createFetcher_')`

This line is important because we want to ensure that each strategy created _is actually a strategy_ otherwise our program will treat any passed in object as a strategy. It may sound like a positive benefit to have anything treated as a strategy but we would lose validity which makes our code more prone to errors which can easily deter our debugging experience if we misstep.

`Symbol` returns to us a unique variable by [definition](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol). It is also _hidden within the implementation of the context_, so there is no way that objects created outside of our `create` function will be treated as a strategy. They would have to use the method made publicly from the _interface_ provided by the _context_.

When the client calls `use` it's submitting `axiosFetcher` to be the used as the current strategy and is then bound as a reference until the client swaps in another strategy via `use`.

Now we have three strategies for retrieving data:

```js
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))
  })
```

Hurray! We've now seen how it can be implemented in code. But can we think of a situation in the real world where we need this? You can think of plenty actually! However if this is your first time reading about this pattern then I understand that it can be hard to think of a scenario beforehand unless we see one in practice first.

The examples we went over in this post shows the pattern implementation but anyone reading this can ask "Why bother implementing three fetcher strategies when you can just directly use one like axios to get the response and call it a day?"

In the upcoming example we will be going over a scenario where the Strategy Design Pattern is _definitely_ needed.

### Handling different data types

Where the strategy pattern shines most is when we need to handle different data types when doing something like _sorting_.

In the previous examples we _didn't really care_ about any data types because we just wanted some response. But what happens when we receive a collection of something and needed to do some narrow task like categorizing them? What if they need to be _sorted correctly_?

When we need to sort several collections where each are a collection of another data type we can't just use the native `.sort` method on all of them because each value can be treated differently in terms of "less" and "greater".

We can use the Strategy Pattern and define different sets of sorting algorithms that are readily available in the runtime so that we can _use them interchangeably on demand_.

Consider these collections:

```js
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'),
]
```

We can create a `Sort` strategy class and a `Sorter` context class.

Note that they don't need to be classes. We're just choosing to use classes now to diversify the implementation a little:

```js
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()
```

It's pretty straight forward. `Sorter` keeps a reference to the `Sort` that is currently being _used_. This is the sort function that will be picked up when calling `sort`. Each `Sort` instance is a strategy and passed into `use`.

The `Sorter` does not know anything about the strategies. It does not know that there is a date sorter, number sorter, etc. It _just calls the Sort's execute method_.

However the client _knows_ about all of the `Sort` instances and controls the strategies as well as the `Sorter`:

```js
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,
)
```

With that said, its entirely up to us (the client) to handle this accordingly:

```js
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))
}
```

We now have a robust _15 line function_ that can sort _4 different variations_ of collections!

```js
console.log('Sorted numbers', sort(nums))
console.log('Sorted letters', sort(letters))
console.log('Sorted dates', sort(dates))
```

![strategy-design-pattern-sorted-collections-result.png](https://jsmanifest.s3.us-west-1.amazonaws.com/posts/power-of-strategy-design-pattern-in-javascript/strategy-design-pattern-sorted-collections-result.png 'Strategy Design Pattern Sorted Collections Result Png')

And that is the power of the Strategy Design Pattern in JavaScript.

Thanks to the nature of JavaScript treating functions as values this code example blends in that capability to its advantage and works with the Strategy pattern seamlessly.

## Conclusion

And that concludes the end of this post! I hope you found this to be useful and stay tuned for more useful tips in the future!!

Find me on [medium](https://medium.com/@jsmanifest)