Simplifying System Complexity with the Facade Pattern in JavaScript

TL;DR: This article explains the concept of the Facade pattern in JavaScript. Facades are simple interfaces that hide system complexities, providing a simplified API for public usage. They shield developers from being locked into an architecture and have been widely used in JavaScript. We’ll provide a step-by-step guide on how to implement a facade in a JavaScript application.

Introduction: The Facade pattern is a structural pattern used in programming to simplify a complex system. This pattern creates a simpler, easy-to-use interface that conceals the intricate details of the underlying system. It not only simplifies the use of an API but also shields developers from becoming tethered to a particular architecture, allowing them to modify the internals without altering the facade that’s visible to the public.

  • Why use the Facade pattern? Facades have been widely used in JavaScript, especially since frameworks began wrapping simple interfaces around disparate browser APIs. They offer a clean, straightforward API for developers to interact with complex systems or libraries.

Implementing the Facade Pattern:

  • Step 1 – processor.js: This module returns an API of five methods that each deal with a different data type. Although the processing isn’t complex, it demonstrates how a module doesn’t need to depend on the facade. The facade depends on this module.
define(function() {
  'use strict';

  return { 
    processString: function (string) { 
      return string.substring(0, string.length / 2);
    },
    processNumber: function (number) {
      return number * number;
    },
    processBoolean: function (bool) {
      return !bool;
    },
    processArray: function (array) {
      return array.length;
    },
    processObject: function (object) {
      return Object.keys(object).length;  
    }
  };
});
  • Step 2 – facade.js: This is where we define our facade. The facade contains one method, processData, which depending on the data type passed to it, invokes a corresponding method from the processor module.
define(function(require) { 
  'use strict';
  
  var processor = require('facade/processor'); 
  
  return { 
    processData: function (data) { 
      switch (Object.prototype.toString.call(data)) { 
        case '[object String]':
          return processor.processString(data);
          break;
        case '[object Number]':
          return processor.processNumber(data);
          break;  
        case '[object Boolean]':
          return processor.processBoolean(data);
          break;
        case '[object Array]':
          return processor.processArray(data);
          break;
        case '[object Object]':
          return processor.processObject(data);
          break;
        default:
          return 'Unable to process the data';
      }
    }
  };
});
  • Step 3 – init.js: This is the initialization file, where we include the facade as a dependency. We then test the facade with different types of data inputs.
define(function(require) {
  'use strict';

  return {
    init: function() {
      var facade = require('facade/facade'); 
          
      console.log(facade.processData('test string')); 
      console.log(facade.processData(5));
      console.log(facade.processData(false));
      console.log(facade.processData([1, 2, 3]));
      console.log(facade.processData({prop: 'something', other: 'else'}));
    } 
  };
});
  • Step 4 – main.js: This file runs the examples. The runExample function is attached to the global window object and can be used to execute any example available in the examples object.
require (
  ['facade/init'],
  function ( facade) {
    'use strict';

    var examples = {
      facade: facade
    };
    
    window.runExample = function (example) {
      examples[example].init();
    };
  }
);

Conclusion: A facade is a powerful tool that helps to hide the complexity of the underlying system, presenting a simplified API for interaction. By structuring your JavaScript code with facades, you provide a public-facing interface that remains consistent even if the internals change. This results in more maintainable code that is also easier to understand and use. The implementation steps provided here demonstrate this approach using a JavaScript application.