Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

Implementing 5 Common Design Patterns in JavaScript (ES8)

Save for later
  • 14 min read
  • 01 May 2018

article-image

In this tutorial, we'll see how common design patterns can be used as blueprints for organizing larger structures.

Defining steps with template functions


A template is a design pattern that details the order a given set of operations are to be executed in; however, a template does not outline the steps themselves. This pattern is useful when behavior is divided into phases that have some conceptual or side effect dependency that requires them to be executed in a specific order. Here, we'll see how to use the template function design pattern.

We assume you already have a workspace that allows you to create and run ES modules in your browser for all the recipes given below:

How to do it...

  1. Open your command-line application and navigate to your workspace.
  2. Create a new folder named 09-01-defining-steps-with-template-functions.
  3. Copy or create an index.html file that loads and runs a main function from main.js.
  4. Create a main.js file that defines a new abstract class named Mission:

// main.js 
class Mission { 
  constructor () { 
    if (this.constructor === Mission) { 
      throw new Error('Mission is an abstract class, must 
      extend'); 
    } 
  } 
}

  1. Add a function named execute that calls three instance methods—determineDestinationdeterminPayload, and launch:

// main.js 
class Mission { 
  execute () { 
    this.determinDestination(); 
    this.determinePayload(); 
    this.launch(); 
  } 
}

  1. Create a LunarRover class that extends the Mission class:

// main.js 
class LunarRover extends Mission {}

  1. Add a constructor that assigns name to an instance property:

// main.js 
class LunarRover extends Mission 
  constructor (name) { 
    super(); 
    this.name = name; 
  } 
}

  1. Implement the three methods called by Mission.execute:

// main.js 
class LunarRover extends Mission {} 
  determinDestination() { 
    this.destination = 'Oceanus Procellarum'; 
  } 
 
  determinePayload() { 
    this.payload = 'Rover with camera and mass spectrometer.'; 
  } 
 
  launch() { 
    console.log(` 
Destination: ${this.destination} 
Playload: ${this.payload} 
Lauched! 
Rover Will arrive in a week. 
    `); 
  } 
}

  1. Create a JovianOrbiter class that also extends the Mission class:

// main.js 
class LunarRover extends Mission {} 
constructor (name) { 
    super(); 
    this.name = name; 
  } 
 
  determinDestination() { 
    this.destination = 'Jovian Orbit'; 
  } 
 
  determinePayload() { 
    this.payload = 'Orbiter with decent module.'; 
  } 
 
  launch() { 
    console.log(` 
Destination: ${this.destination} 
Playload: ${this.payload} 
Lauched! 
Orbiter Will arrive in 7 years. 
    `); 
  } 
}

  1. Create a main function that creates both concrete mission types and executes them:

// main.js 
export function main() { 
  const jadeRabbit = new LunarRover('Jade Rabbit'); 
  jadeRabbit.execute(); 
  const galileo = new JovianOrbiter('Galileo'); 
  galileo.execute(); 
}

  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. The output should appear as follows:

common-design-patterns-javascript-img-0

How it works...


The Mission abstract class defines the execute method, which calls the other instance methods in a particular order. You'll notice that the methods called are not defined by the Mission class. This implementation detail is the responsibility of the extending classes. This use of abstract classes allows child classes to be used by code that takes advantage of the interface defined by the abstract class.

In the template function pattern, it is the responsibility of the child classes to define the steps. When they are instantiated, and the execute method is called, those steps are then performed in the specified order.

Ideally, we'd be able to ensure that Mission.execute was not overridden by any inheriting classes. Overriding this method works against the pattern and breaks the contract associated with it.

This pattern is useful for organizing data-processing pipelines. The guarantee that these steps will occur in a given order means that, if side effects are eliminated, the instances can be organized more flexibly. The implementing class can then organize these steps in the best possible way.

Assembling customized instances with builders


The previous recipe shows how to organize the operations of a class. Sometimes, object initialization can also be complicated. In these situations, it can be useful to take advantage of another design pattern: builders.

Now, we'll see how to use builders to organize the initialization of more complicated objects.

How to do it...

  1. Open your command-line application and navigate to your workspace.
  2. Create a new folder named 09-02-assembling-instances-with-builders.
  3. Create a main.js file that defines a new class named Mission, which that takes a name constructor argument and assigns it to an instance property. Also, create a describe method that prints out some details:

// main.js 
class Mission { 
  constructor (name) { 
    this.name = name; 
  } 
 
  describe () { 
    console.log(`
      The ${this.name} mission will be launched by a
       ${this.rocket.name}
      rocket, and deliver a ${this.payload.name} to
      ${this.destination.name}. 
    `); 
  } 
   }

  1. Create classes named DestinationPayload, and Rocket, which receive a name property as a constructor parameter and assign it to an instance property:

// main.js 
 
class Destination { 
  constructor (name) { 
    this.name = name; 
  } 
} 
 
class Payload { 
  constructor (name) { 
    this.name = name; 
  } 
} 
 
class Rocket { 
  constructor (name) { 
    this.name = name; 
  } 
}


 

  1. Create a MissionBuilder class that defines the setMissionNamesetDestinationsetPayload, and setRocket methods:

// main.js 
class MissionBuilder { 
 
  setMissionName (name) { 
    this.missionName = name; 
    return this; 
  } 
 
  setDestination (destination) { 
    this.destination = destination; 
    return this; 
  } 
 
  setPayload (payload) { 
    this.payload = payload; 
    return this; 
  } 
 
  setRocket (rocket) { 
    this.rocket = rocket; 
    return this; 
  } 
}

  1. Create a build method that creates a new Mission instance with the appropriate properties:

// main.js 
class MissionBuilder { 
  build () { 
    const mission = new Mission(this.missionName); 
    mission.rocket = this.rocket; 
    mission.destination = this.destination; 
    mission.payload = this.payload; 
    return mission; 
  } 
}

  1. Create a main function that uses MissionBuilder to create a new mission instance:

// main.js 
export function main() { 
  // build an describe a mission 
  new MissionBuilder() 
    .setMissionName('Jade Rabbit') 
    .setDestination(new Destination('Oceanus Procellarum')) 
    .setPayload(new Payload('Lunar Rover')) 
    .setRocket(new Rocket('Long March 3B Y-23')) 
    .build() 
    .describe(); 
}

  1. Start your Python web server and open the following link in your browser: http://localhost:8000/.
  2. Your output should appear as follows:

common-design-patterns-javascript-img-1

How it works...


The builder defines methods for assigning all the relevant properties and defines a build method that ensures that each is called and assigned appropriately. Builders are like template functions, but instead of ensuring that a set of operations are executed in the correct order, they ensure that an instance is properly configured before returning.

Because each instance method of MissionBuilder returns the this reference, the methods can be chained. The last line of the main function calls describe on the new Mission instance that is returned from the build method.

Replicating instances with factories


Like builders, factories are a way of organizing object construction. They differ from builders in how they are organized. Often, the interface of factories is a single function call. This makes factories easier to use, if less customizable, than builders.

Now, we'll see how to use factories to easily replicate instances.

How to do it...

  1. Open your command-line application and navigate to your workspace.
  2. Create a new folder named 09-03-replicating-instances-with-factories.
  3. Copy or create an index.html that loads and runs a main function from main.js.
  4. Create a main.js file that defines a new class named Mission. Add a constructor that takes a name constructor argument and assigns it to an instance property. Also, define a simple describe method:

// main.js 
class Mission { 
  constructor (name) { 
    this.name = name; 
  } 
 
  describe () { 
    console.log(` 
The ${this.name} mission will be launched by a ${this.rocket.name} rocket, and 
deliver a ${this.payload.name} to ${this.destination.name}. 
    `); 
  } 
}

  1. Create three classes named DestinationPayload, and Rocket, that take name as a constructor argument and assign it to an instance property:

// main.js 
class Destination { 
  constructor (name) { 
    this.name = name; 
  } 
} 
 
class Payload { 
  constructor (name) { 
    this.name = name; 
  } 
} 
 
class Rocket { 
  constructor (name) { 
    this.name = name; 
  } 
}

  1. Create a MarsMissionFactory object with a single create method that takes two arguments: name and rocket. This method should create a new Mission using those arguments:

// main.js 
 
const MarsMissionFactory = { 
  create (name, rocket) { 
    const mission = new Mission(name); 
    mission.destination = new Destination('Martian surface'); 
    mission.payload = new Payload('Mars rover'); 
    mission.rocket = rocket; 
    return mission; 
  } 
}

  1. Create a main method that creates and describes two similar missions:

// main.js 
 
export function main() { 
  // build an describe a mission 
  MarsMissionFactory 
    .create('Curiosity', new Rocket('Atlas V')) 
    .describe(); 
  MarsMissionFactory 
    .create('Spirit', new Rocket('Delta II')) 
    .describe(); 
}

  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. Unlock access to the largest independent learning library in Tech for FREE!
    Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
    Renews at ₹800/month. Cancel anytime
  3. Your output should appear as follows:

common-design-patterns-javascript-img-2

How it works...


The create method takes a subset of the properties needed to create a new mission. The remaining values are provided by the method itself. This allows factories to simplify the process of creating similar instances. In the main function, you can see that two Mars missions have been created, only differing in name and Rocket instance. We've halved the number of values needed to create an instance.

This pattern can help reduce instantiation logic. In this recipe, we simplified the creation of different kinds of missions by identifying the common attributes, encapsulating those in the body of the factory function, and using arguments to supply the remaining properties. In this way, commonly used instance shapes can be created without additional boilerplate code.

Processing a structure with the visitor pattern


The patterns we've seen thus far organize the construction of objects and the execution of operations. The next pattern we'll look at is specially made to traverse and perform operations on hierarchical structures. Here, we'll be looking at the visitor pattern.

How to do it...

  1. Open your command-line application and navigate to your workspace.
  2. Copy the 09-02-assembling-instances-with-builders folder to a new 09-04-processing-a-structure-with-the-visitor-pattern directory.

  1. Add a class named MissionInspector to main.js. Create a visitor method that calls a corresponding method for each of the following types: MissionDestinationRocket, and Payload:

// main.js 
/* visitor that inspects mission */ 
class MissionInspector { 
  visit (element) { 
    if (element instanceof Mission) { 
      this.visitMission(element); 
    } 
    else if (element instanceof Destination) { 
      this.visitDestination(element); 
    } 
    else if (element instanceof Rocket) { 
      this.visitRocket(element); 
    } 
    else if (element instanceof Payload) { 
      this.visitPayload(element); 
    } 
  } 
}

  1. Create a visitMission method that logs out an ok message:

// main.js 
class MissionInspector { 
  visitMission (mission) { 
    console.log('Mission ok'); 
    mission.describe(); 
     } 
}

  1. Create a visitDestination method that throws an error if the destination is not in an approved list:

// main.js 
class MissionInspector { 
  visitDestination (destination) { 
    const name = destination.name.toLowerCase(); 
 
    if ( 
      name === 'mercury' || 
      name === 'venus' || 
      name === 'earth' || 
      name === 'moon' || 
      name === 'mars' 
    ) { 
      console.log('Destination: ', name, ' approved'); 
    } else { 
      throw new Error('Destination: '' + name + '' not approved      
      at this time'); 
    } 
     } 
}

  1. Create a visitPayload method that throws an error if the payload isn't valid:

// main.js 
class MissionInspector { 
  visitPayload (payload) { 
    const name = payload.name.toLowerCase(); 
    const payloadExpr = /(orbiter)|(rover)/; 
 
    if ( payloadExpr.test(name) ) { 
      console.log('Payload: ', name, ' approved'); 
    } 
    else { 
      throw new Error('Payload: '' + name + '' not approved at 
      this time'); 
    } 
  } 
}

  1. Create a visitRocket method that logs out an ok message:

// main.js 
class MissionInspector { 
 
  visitRocket (rocket) { 
    console.log('Rocket: ', rocket.name, ' approved'); 
  } 
}

  1. Add an accept method to the Mission class that calls accept on its constituents, then tells visitor to visit the current instance:

// main.js 
class Mission { 
 
  // other mission code ... 
 
  accept (visitor) { 
    this.rocket.accept(visitor); 
    this.payload.accept(visitor); 
    this.destination.accept(visitor); 
    visitor.visit(this); 
  } 
  }

  1. Add an accept method to the Destination class that tells visitor to visit the current instance:

// main.js 
class Destination { 
 
  // other mission code ... 
 
 
  accept (visitor) { 
    visitor.visit(this); 
    } 
  }

  1. Add an accept method to the Payload class that tells visitor to visit the current instance:

// main.js 
class Payload { 
 
  // other mission code ... 
 
  accept (visitor) { 
    visitor.visit(this); 
    } 
  }

  1. Add an accept method to the Rocket class that tells visitor to visit the current instance:

// main.js 
class Rocket { 
 
  // other mission code ... 
 
  accept (visitor) { 
    visitor.visit(this); 
    } 
  }

  1. Create a main function that creates different instances with the builder, visits them with the MissionInspector instance, and logs out any thrown errors:

// main.js 
export function main() { 
  // build an describe a mission 
  const jadeRabbit = new MissionBuilder() 
    .setMissionName('Jade Rabbit') 
    .setDestination(new Destination('Moon')) 
    .setPayload(new Payload('Lunar Rover')) 
    .setRocket(new Rocket('Long March 3B Y-23')) 
    .build(); 
 
  const curiosity = new MissionBuilder() 
    .setMissionName('Curiosity') 
    .setDestination(new Destination('Mars')) 
    .setPayload(new Payload('Mars Rover')) 
    .setRocket(new Rocket('Delta II')) 
    .build(); 
 
  // expect error from Destination 
  const buzz = new MissionBuilder() 
    .setMissionName('Buzz Lightyear') 
    .setDestination(new Destination('Too Infinity And Beyond')) 
    .setPayload(new Payload('Interstellar Orbiter')) 
    .setRocket(new Rocket('Self Propelled')) 
    .build(); 
 
  // expect error from payload 
  const terraformer = new MissionBuilder() 
    .setMissionName('Mars Terraformer') 
    .setDestination(new Destination('Mars')) 
    .setPayload(new Payload('Terraformer')) 
    .setRocket(new Rocket('Light Sail')) 
    .build(); 
 
  const inspector = new MissionInspector(); 
 
  [jadeRabbit, curiosity, buzz, terraformer].forEach((mission) => 
   { 
    try { 
      mission.accept(inspector); 
    } catch (e) { console.error(e); } 
  }); 
}

  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. Your output should appear as follows:

common-design-patterns-javascript-img-3


How it works...


The visitor pattern has two components. The visitor processes the subject objects and the subjects tell other related subjects about the visitor, and when the current subject should be visited.

The accept method is required for each subject to receive a notification that there is a visitor. That method then makes two types of method call. The first is the accept method on its related subjects. The second is the visitor method on the visitor. In this way, the visitor traverses a structure by being passed around by the subjects.

The visitor methods are used to process different types of node. In some languages, this is handled by language-level polymorphism. In JavaScript, we can use run-time type checks to do this.

The visitor pattern is a good option for processing hierarchical structures of objects, where the structure is not known ahead of time, but the types of subjects are known.

Using a singleton to manage instances


Sometimes, there are objects that are resource intensive. They may require time, memory, battery power, or network usage that are unavailable or inconvenient. It is often useful to manage the creation and sharing of instances.

Here, we'll see how to use singletons to manage instances.

How to do it...

  1. Open your command-line application and navigate to your workspace.
  2. Create a new folder named 09-05-singleton-to-manage-instances.
  3. Copy or create an index.html that loads and runs a main function from main.js.
  4. Create a main.js file that defines a new class named Rocket. Add a constructor takes a name constructor argument and assigns it to an instance property:

// main.js 
class Rocket { 
  constructor (name) { 
    this.name = name; 
  } 
}

  1. Create a RocketManager object that has a rockets property. Add a findOrCreate method that indexes Rocket instances by the name property:

// main.js 
const RocketManager = { 
  rockets: {}, 
  findOrCreate (name) { 
    const rocket = this.rockets[name] || new Rocket(name); 
    this.rockets[name] = rocket; 
    return rocket; 
  } 
}

  1. Create a main function that creates instances with and without the manager. Compare the instances and see whether they are identical:

// main.js 
export function main() { 
  const atlas = RocketManager.findOrCreate('Atlas V'); 
  const atlasCopy = RocketManager.findOrCreate('Atlas V'); 
  const atlasClone = new Rocket('Atlas V'); 
 
  console.log('Copy is the same: ', atlas === atlasCopy); 
  console.log('Clone is the same: ', atlas === atlasClone); 
}

  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. Your output should appear as follows:

common-design-patterns-javascript-img-4

How it works...


The object stores references to the instances, indexed by the string value given with name. This map is created when the module loads, so it is persisted through the life of the program. The singleton is then able to look up the object and returns instances created by findOrCreate with the same name.

Conserving resources and simplifying communication are primary motivations for using singletons. Creating a single object for multiple uses is more efficient in terms of space and time needed than creating several. Plus, having single instances for messages to be communicated through makes communication between different parts of a program easier.

Singletons may require more sophisticated indexing if they are relying on more complicated data.

You read an excerpt from a book written by Ross Harrison, titled ECMAScript Cookbook. This book contains over 70 recipes to help you improve your coding skills and solving practical JavaScript problems.



6 JavaScript micro optimizations you need to know

Mozilla is building a bridge between Rust and JavaScript

Behavior Scripting in C# and Javascript for game developers