The adapter design pattern is very useful in JavaScript, and can be seen in dealing with cross-browser compatibility issues and integrating calls to multiple third-party SDKS. In fact, in daily development, many times will inadvertently write code in accordance with a certain design pattern, after all, design pattern is summed up by the old generation can help improve the development efficiency of some templates, from the daily development. Adapters are actually pretty common in JavaScript.

In Wikipedia, the adapter pattern is defined as:

In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used from another interface. It is typically used to make existing classes work with other classes without modifying their source code.

Examples from life

The most common in life is the adapter of the power plug, the world’s socket standards are not the same, if you need to buy the corresponding power plug according to the standards of each country that is too much waste of money, if you take the socket, the family wall broken, rewiring, it is certainly not realistic. So there’s an adapter for the plug, which is used to convert one plug into another plug, and the thing that does the transfer between the socket and your power supply is the adapter.

Representation in code

When it comes to programming, I personally understand it this way:

Hide the dirty code you don’t want to see, and you can say it’s an adapter

Access multiple third-party SDKS

Daily development of examples that we’re doing a WeChat public development, which USES WeChat payment module, alignment for a long time, finally ran through the whole process, just when you’re ready to happy package online code, got a new requirement: we need to pay treasure to the public, access to the SDK, also want to have the payment process

To reuse code, we might write logic like this in a script:

if (platform === 'wechat') {
  wx.pay(config)
} else if (platform === 'alipay') {
  alipay.pay(config)
}

// Do some subsequent logic
Copy the code

However, in general, the interface calls provided by the SDK will be more or less different from each other, although sometimes the documentation may be the same, which is a tribute to the vendor.

So for the above code it might look like this:

// This is not a real parameter configuration, just an example
const config = {
  price: 10.goodsId: 1
}

// It is also possible that the returned value is handled differently
if (platform === 'wechat') {
  config.appId = 'XXX'
  config.secretKey = 'XXX'
  wx.pay(config).then((err, data) = > {
    if (err) // error

    // success})}else if (platform === 'alipay') {
  config.token = 'XXX'
  alipay.pay(config, data => {
    // success
  }, err => {
    // error})}Copy the code

So far, the code interface is pretty clean, and it’s not too bad code, as long as we write the comments. However, life is always full of surprises, and we received the demand to add THE SDK of QQ, Meituan, Millet, or some banks.

Your code might look something like this:

switch (platform) {
  case 'wechat':
    // Wechat processing logic
  break
  case 'QQ':
    // the processing logic of QQ
  break
  case 'alipay':
    // Alipay processing logic
  break
  case 'meituan':
    // Meituan's processing logic
  break
  case 'xiaomi':
    // Xiaomi's processing logic
  break
}
Copy the code

This is no longer a problem that can be remedied by a few comments. The code becomes more and more difficult to maintain, and the various SDKS call in strange ways, and it is a waste of resources to have to write the code all over again if someone else needs to do something similar.

So in order to ensure the clarity of our business logic, and to avoid future generations to repeatedly step on this pit, we will split it out as a common function to exist: find one of the SDK call method or a rule we agreed on as a benchmark. We’re going to tell the caller how you’re going to do it, how you’re going to get the data back, and then we’re going to make all these dirty judgments inside the function:

function pay ({ price, goodsId }) {
  return new Promise((resolve, reject) = > {
    const config = {}

    switch (platform) {
      case 'wechat':
        // Wechat processing logic
        config.price = price
        config.goodsId = goodsId
        config.appId = 'XXX'
        config.secretKey = 'XXX'
        wx.pay(config).then((err, data) = > {
          if (err) return reject(err)

          resolve(data)
        })
      break
      case 'QQ':
        // the processing logic of QQ
        config.price = price * 100
        config.gid = goodsId
        config.appId = 'XXX'
        config.secretKey = 'XXX'
        config.success = resolve
        config.error = reject
        qq.pay(config)
      break
      case 'alipay':
        // Alipay processing logic
        config.payment = price
        config.id = goodsId
        config.token = 'XXX'
        alipay.pay(config, resolve, reject)
      break}})}Copy the code

This way, no matter what environment we are in, as long as our adapter supports it, we can make the calls according to our agreed general rules, and what SDK is being implemented is what the adapter needs to care about:

// run anywhere
await pay({
  price: 10.goodsId: 1
})
Copy the code

For the SDK provider, it’s just a matter of knowing the parameters they need and returning the data in their own way. For the SDK call room, we just need to agree on the general parameters, and listen to the callback processing as agreed.

The task of integrating multiple third-party SDKS is left to the adapter, and then we compress, obfuscate, and place the adapter code in an invisible corner where the code logic becomes clear :).

Adapters are basically what they do. It must be made clear that adapters are not silver bullets. The tedious code always exists, but you can’t see it when you write a business.

Some other examples

$(‘selector’).on is an obvious adaptor pattern.

Demoted step by step, and smoothed out some of the browser differences so that we could simply use on for event listening in mainstream browsers:

// A simple pseudocode example
function on (target, event, callback) {
  if (target.addEventListener) {
    // The standard way to listen to events
    target.addEventListener(event, callback)
  } else if (target.attachEvent) {
    // The listening mode of the earlier version of IE
    target.attachEvent(event, callback)
  } else {
    // Some older browsers listen for events
    target[`on${event}`] = callback
  }
}
Copy the code

Or it’s even more common in Node. Since there were no promises in the early days, most of the asynchro was done by callback, with an error-first callback as an agreed rule:

const fs = require('fs')

fs.readFile('test.txt', (err, data) => {
  if (err) // Handle exceptions

  // Handle the correct result
})
Copy the code

While our new functions are carried out in the way of async/await. When we need to reuse some functions in the old project, it is definitely not feasible to modify the code of the old project directly. This compatibility needs to be done by the caller, so to keep the logic code from looking too cluttered, we might convert this callback into a version of the Promise we can call:

const fs = require('fs')

function readFile (fileName) {
  return new Promise((resolve, reject) = > {
    fs.readFile(fileName, (err, data) => {
      if (err) reject(err)

      resolve(data)
    })
  })
}

await readFile('test.txt')
Copy the code

As mentioned earlier, this error-first callback is a convention form, so we can easily implement a generic adapter:

function promisify(func) {
  return (. args) = > new Promise((resolve, reject) = >{ func(... args, (err, data) => {if (err) reject(err)

      resolve(data)
    })
  })
}
Copy the code

We then perform the conversion before using it to execute the code in the way we expect:

const fs = require('fs')

const readFile = promisify(fs.readFile)

await readFile('test.txt')
Copy the code

In Node8, a utility function like util.promisify is officially implemented

summary

Personal opinion: All design patterns are not imagined out of thin air, they must be summarized in the process of development of some efficient methods, which means that you may not have to start to eat the name of the various design patterns. Because the scenarios described in the book may not be comprehensive, and there may be better solutions for certain languages, it is not likely that the code will have a soul

The paper come zhongjue shallow, and must know this to practice. ———— Winter Night reading shows ziyu, Lu You