Categories
JavaScript Design Patterns

JavaScript Design Patterns — Adapters and Facades

Spread the love

Design patterns are the basis of any good software. JavaScript programs are no exception.

In this article, we’ll look at the adapter and facade patterns.

Adapter

The adapter pattern is useful for creating a consistent interface for another object.

The object we’re creating an interface for doesn’t fit with how it’s used.

Therefore, we create an adapter so that we can use them easily.

It also hides the implementation so that we can use it easily and with low coupling.

For instance, we can create an adapter object as follows:

const backEnd = {
  setAttribute(key, value) {
    //.,,
  },
  getAttribute() {
    //...
  }
}

const personAdapter = {
  setFirstName(name) {
    backEnd.setAttribute('firstName', name);
  },
  setLastName(name) {
    backEnd.setAttribute('lastName', name);
  },
  getFirstName() {
    backEnd.getAttribute('firstName');
  },
  getLastName() {
    backEnd.getAttribute('lastName');
  }
}

We created an adapter object called personAdapter so that we can use backEnd to set all the attributes.

The adapter has an interface that people can understand more easily since the method names are explicit.

Other objects or classes can use the object to manipulate the attributes.

We can use adapters to create interfaces that we want to expose to the public any way we want.

Inheriting Objects with Adapters

With adapters, we can also create an interface for multiple classes or objects.

For instance, we can write:

const backEnd = {
  setAttribute(key, value) {
    //.,,
  },
  getAttribute() {
    //...
  }
}

const parentAdapter = {
  //...
  getAdapterName() {
    //...
  }
}

const personAdapter = Object.create(parentAdapter);

personAdapter.setFirstName = (name) => {
  backEnd.setAttribute('firstName', name);
};

personAdapter.setLastName = (name) => {
  backEnd.setAttribute('lastName', name);
};

personAdapter.getFirstName = () => {
  backEnd.getAttribute('firstName');
};

personAdapter.getLastName = () => {
  backEnd.getAttribute('lastName');
}

In the code above, we created personAdapter with Object.create , which allows us to inherit from another parent object.

So we get the getAdapterName method from the parentAdapter prototype object.

Then we add our own methods to personAdapter .

Now we can get inherited methods from another object plus the methods in our own adapter object in the adapter.

Likewise, we can do the same with the class syntax.

For instance, we can create an adapter class instead of an object.

We can write:

const backEnd = {
  setAttribute(key, value) {
    //.,,
  },
  getAttribute() {
    //...
  }
}

class ParentAdapter {
  //...
  getAdapterName() {
    //...
  }
}

class PersonAdapter extends ParentAdapter {
  setFirstName(name) {
    backEnd.setAttribute('firstName', name);
  }

  setLastName(name) {
    backEnd.setAttribute('lastName', name);
  }

  getFirstName() {
    backEnd.getAttribute('firstName');
  }

  getLastName() {
    backEnd.getAttribute('lastName');
  }

}

const personAdapter = new PersonAdapter();
console.log(personAdapter);

The code above has a PersonAdapter and ParentAdapter classes instead of objects.

We used the extends keyword to inherit members from ParentAdapter .

This is another way we can inherit members from a parent entity.

Facade Pattern

We can use the facade pattern to create a class or object to hide the implementation of something complex behind it.

This is used to create an easy to use interface for the outside while keeping the complex implementation of something inside.

For instance, we can write:

const complexProduct = {
  setShortName(name) {
    //...
  },

  setLongName(name) {
    //...
  },

  setPrice(price) {
    //...
  },

  setDiscount(discount) {
    //...
  },
  //...
}

const productFacade = {
  setName(type, name) {
    if (type === 'shortName') {
      complexProduct.setShortName(name);
    } else if (type === 'longName') {
      complexProduct.setLongName(name);
    }
  }
  //...
}

We created a productFacade that has some methods that call various methods in the complexProduct object.

This way, we can use the facade to simplify what we do, and also reduce the coupling to the complexProduct object.

We let the productFacade object communicates with the complexProduct object.

It also serves to reduce coupling from complex objects.

In addition, a facade can also create an interface for something that won’t change as often as what’s behind it.

An interface that doesn’t change as often is good since more changes mean more risks of bugs.

Likewise, we can create a facade class that does the same thing as follows:

const complexProduct = {
  setShortName(name) {
    //...
  },

  setLongName(name) {
    //...
  },

  setPrice(price) {
    //...
  },

  setDiscount(discount) {
    //...
  },
  //...
}

class ProductFacade {
  setName(type, name) {
    if (type === 'shortName') {
      complexProduct.setShortName(name);
    } else if (type === 'longName') {
      complexProduct.setLongName(name);
    }
  }
  //...
}

Then we can create an instance of ProductFacade instead of using the object literal directly.

A facade can be used to manipulate one or more objects. That’s different from the adapter pattern, where we make an adapter for one object.

Conclusion

We can use the adapter pattern to hide the implementation of one object bu creating an interface that’s easier to work with to the outside.

The facade pattern lets us hide complex implementations behind one interface that can be used instead of one or more complex objects being the facade.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *