JavaScript Design Patterns Tutorial With Examples

This this JavaScript design patterns tutorial, we are going to implement popular design patterns in JavaScript and at the same time we understand this difficult topic with real examples. JavaScript design patterns have a number of features. The language has no interfaces, so you have to resort to some agreements. We assume that a class implements a certain interface if it has inherent methods and properties. The interfaces themselves are described in the comments. The examples use ES6 syntax.

Generating JavaScript Design Patterns

Sometimes it is not desirable to create an instance of a class directly. Then we resort to generating patterns that can choose the optimal instantiation mechanism.

Simple factory

Doing your own doors when building a house would be quite difficult, so you get them ready from the store.

Pattern A simple factory produces the desired instance without bothering the client with the intricacies of this process.

Implementation example

Create an implicit interface for all doors:

/*
Door
getWidth()
getHeight()
*/
class WoodenDoor {
constructor(width, height){
this.width = width
this.height = height
}
getWidth(){
return this.width
}
getHeight(){
return this.height
}
}

We will organize a factory that will produce them:

const DoorFactory = {
makeDoor : (width, height) => new WoodenDoor(width, height)
}

Everything, it is possible to work:

const door = DoorFactory.makeDoor(100, 200)
console.log('Width:', door.getWidth())
console.log('Height:', door.getHeight())

A pattern is useful if creating an object requires some logic. It makes sense to move the repeating code into a separate Simple factory.

Factory Method

The recruitment manager works with candidates for various job openings. Instead of delving into the intricacies of each position, he delegates a technical interview to fellow specialists.

Pattern A factory method allows you to delegate the logic of creating the necessary instances to the child classes.

Implementation example

Create several types of interviewers with one interface:

/*
Interviewer interface

askQuestions()
*/

class Developer {
 askQuestions() {
   console.log('Asking about design patterns!')
 }
}

class CommunityExecutive {
 askQuestions() {
   console.log('Asking about community building')
 }
}

And here is the recruitment manager:

class HiringManager {
       
   takeInterview() {
       const interviewer = this.makeInterviewer()
       interviewer.askQuestions()
   }
}

Child classes expand it and provide the right interviewers:

 
class DevelopmentManager extends HiringManager {
   makeInterviewer() {
       return new Developer()
   }
}

class MarketingManager extends HiringManager {
   makeInterviewer() {
       return new CommunityExecutive()
   }
}

Now you can conduct interviews:

const devManager = new DevelopmentManager()
devManager.takeInterview() // Asking about design patterns

const marketingManager = new MarketingManager()
marketingManager.takeInterview() // Asking about community buildng.

A pattern is useful if the logic of subclass operation is the same, but a specific subclass is discovered only during program execution.

Abstract Factory

Let’s go back to the door. Strictly speaking, wooden doors should be bought in a wooden door shop, iron doors should be bought in an iron shop, and PVC doors should be bought in a specialized PVC shop. Installation expert also required: carpenter, welder or special PVC door installer.

An abstract factory is an interface that groups other factories that are logically related to each other.

Implementation example

We have several doors:

/*
Door interface :

getDescription()
*/

class WoodenDoor {
   getDescription() {
       console.log('I am a wooden door')
   }
}

class IronDoor {
   getDescription() {
       console.log('I am an iron door')
   }
}

and narrow installers:

/*
DoorFittingExpert interface :

getDescription()
*/

class Welder {
   getDescription() {
       console.log('I can only fit iron doors')
   }
}

class Carpenter {
   getDescription() {
       console.log('I can only fit wooden doors')
   }
}

It is necessary to group everything. Wooden doors – with a carpenter, iron – with a welder.

/*
DoorFactory interface :

makeDoor()
makeFittingExpert()
*/

// Деревянная фабрика возвращает плотника и деревянную дверь
class WoodenDoorFactory {
   makeDoor(){
       return new WoodenDoor()
   }

   makeFittingExpert() {
       return new Carpenter()
   }
}

// Железная фабрика возвращает сварщика и железную дверь
class IronDoorFactory {
   makeDoor(){
       return new IronDoor()
   }

   makeFittingExpert() {
       return new Welder()
   }
}

You can do the door:

woodenFactory = new WoodenDoorFactory()

door = woodenFactory.makeDoor()
expert = woodenFactory.makeFittingExpert()

door.getDescription()  // I am a wooden door
expert.getDescription() // I can only fit wooden doors

ironFactory = new IronDoorFactory()

door = ironFactory.makeDoor()
expert = ironFactory.makeFittingExpert()

door.getDescription()  // I am an iron door
expert.getDescription() // I can only fit iron doors

Now complete with each door is the desired master.

A pattern is useful when there are several classes that depend on each other.

Builder

You ordered a hamburger in a fast food restaurant. First, the cashier invites you to choose bread, then a patty, cheese, sauces … The logic of creating a hamburger turned out to be quite complicated, you cannot do without the Builder pattern .

This template allows you to create different versions of the object without contaminating the designer with an extra code.

Implementation example

Let’s start with the hamburger itself:

class Burger {
   constructor(builder) {
       this.size = builder.size
       this.cheeze = builder.cheeze || false
       this.pepperoni = builder.pepperoni || false
       this.lettuce = builder.lettuce || false
       this.tomato = builder.tomato || false
   }
}

And here is the Builder:

 class BurgerBuilder {

   constructor(size) {
       this.size = size
   }

   addPepperoni() {
       this.pepperoni = true
       return this
   }

   addLettuce() {
       this.lettuce = true
       return this
   }

   addCheeze() {
       this.cheeze = true
       return this
   }

   addTomato() {
       this.tomato = true
       return this
   }

   build() {
       return new Burger(this)
   }
}

Voila! Here is our burger:

 const burger = (new BurgerBuilder(14))
   .addPepperoni()
   .addLettuce()
   .addTomato()
   .build()

The Builder pattern is needed if the object can exist in different variations or the instantiation process consists of several steps.

Singleton

The country must have a single president, otherwise the disorder will begin.

The Singleton pattern limits instantiation and allows you to make sure that the class is represented in the program as a single instance.
Implementation example

In JavaScript, Singletons can be implemented using the Template module. Private variables and functions are hidden in the closure.

 const president = (function(){
   const presidentsPrivateInformation = 'Super private'

   const name = 'Turd Sandwich'

   const getName = () => name

   return {
       getName
   }
}())

In this example, private information is hidden from external code. The public method allows you to find out the name of the president (but not change it):

president.getName() // 'Turd Sandwich'
president.name // undefined
president.presidentsPrivateInformation // undefined

Structural Design Patterns in JavaScript

Structural  design patterns deal with the structure of objects and the relationship between them.

Adapter

An adapter is, for example, a card reader, which is needed to transfer photos from a memory card to a computer.

This pattern wraps an object that is incompatible with something and makes it compatible without changing the source code.

Implementation example

Take a game in which the hero hunts for lions.

All lions have one interface:

/*
Lion interface :

roar()
*/

class AfricanLion  {
   roar() {}
}

class AsianLion  {
   roar() {}
}

A player can only hunt objects that correspond to this interface:

 class Hunter {
   hunt(lion) {
       // ...
       lion.roar()
       //...
   }
}

You need to put into play a wild dog, but it has a different interface. To combine a dog and a hunter, you need an Adapter:

class WildDog {
   bark() {
   }
}

class WildDogAdapter {

   constructor(dog) {
       this.dog = dog;
   }
   
   roar() {
       this.dog.bark();
   }
}

Now you can enter into the game a new character:

wildDog = new WildDog()
wildDogAdapter = new WildDogAdapter(wildDog)

hunter = new Hunter()
hunter.hunt(wildDogAdapter)

Bridge

You have a website to which you want to add a theme. You can create copies of all existing pages for each topic, or change the page according to the chosen topic.

The Bridge pattern allows you to separate the implementation from the object itself and build a hierarchy of implementations.

Implementation example

Here is the page hierarchy of your website:

 /*
Webpage interface :

constructor(theme)
getContent()
*/

class About{
   constructor(theme) {
       this.theme = theme
   }
   
   getContent() {
       return "About page in " + this.theme.getColor()
   }
}

class Careers{
  constructor(theme) {
   this.theme = theme
  }
 
  getContent() {
   return "Careers page in " + this.theme.getColor()
  }
}

And a separate hierarchy of different themes:

/*
Theme interface :

getColor()
*/

class DarkTheme{
   getColor() {
       return 'Dark Black'
   }
}
class LightTheme{
   getColor() {
       return 'Off white'
   }
}
class AquaTheme{
   getColor() {
       return 'Light blue'
   }
}

But their interaction:

const darkTheme = new DarkTheme()

const about = new About(darkTheme)
const careers = new Careers(darkTheme)

console.log(about.getContent() )// "About page in Dark Black"
console.log(careers.getContent() )// "Careers page in Dark Black"

Linker

Any organization consists of people. Some workers are grouped together, and the organization itself is a large group.

The Linker template allows you to consistently process individual objects and their groups. It works with a part-whole hierarchy.

Implementation example

We have different types of workers:

/*
Employee interface :

constructor(name, salary)
getName()
setSalary()
getSalary()
getRoles()
*/

class Developer {

   constructor(name, salary) {
       this.name = name
       this.salary = salary
   }

   getName() {
       return this.name
   }

   setSalary(salary) {
       this.salary = salary
   }

   getSalary() {
       return this.salary
   }

   getRoles() {
       return this.roles
   }

   develop() {
       /* */
   }
}

class Designer {

   constructor(name, salary) {
       this.name = name
       this.salary = salary
   }

   getName() {
       return this.name
   }

   setSalary(salary) {
       this.salary = salary
   }

   getSalary() {
       return this.salary
   }

   getRoles() {
       return this.roles
   }

   design() {
       /* */
   }
}

and the organization itself:

 class Organization {
   constructor(){
       this.employees = []
   }

   addEmployee(employee) {
       this.employees.push(employee)
   }

   getNetSalaries() {
       let netSalary = 0

       this.employees.forEach(employee => {
           netSalary += employee.getSalary()
       })

       return netSalary
   }
}

Now you can calculate the total salary of all employees:

const john = new Developer('John Doe', 12000)
const jane = new Designer('Jane', 10000)

const organization = new Organization()
organization.addEmployee(john)
organization.addEmployee(jane)

console.log("Net salaries: " , organization.getNetSalaries()) // 22000

Decorator

The car repair shop offers several types of services. To calculate the total bill, you need to take the price for one service and consistently add all the others, until you get the final cost. Each service is a Decorator .

This pattern wraps the object and dynamically changes its behavior.

Implementation example

Take coffee for example. The easiest coffee that implements the appropriate interface:

/*
Coffee interface:
getCost()
getDescription()
*/

class SimpleCoffee{

   getCost() {
       return 10
   }

   getDescription() {
       return 'Simple coffee'
   }
}

We want to be able to add various additives to coffee, for this we will create some decorators:

class MilkCoffee {


   constructor(coffee) {
       this.coffee = coffee
   }

   getCost() {
       return this.coffee.getCost() + 2
   }

   getDescription() {
       return this.coffee.getDescription() + ', milk'
   }
}

class WhipCoffee {

   constructor(coffee) {
       this.coffee = coffee
   }

   getCost() {
       return this.coffee.getCost() + 5
   }

   getDescription() {
       return this.coffee.getDescription() + ', whip'
   }
}

class VanillaCoffee {

   constructor(coffee) {
       this.coffee = coffee
   }

   getCost() {
       return this.coffee.getCost() + 3
   }

   getDescription() {
       return this.coffee.getDescription() + ', vanilla'
   }
}

Now you can make coffee to your taste:

let someCoffee

someCoffee = new SimpleCoffee()
console.log(someCoffee.getCost())// 10
console.log(someCoffee.getDescription())

someCoffee = new MilkCoffee(someCoffee)
console.log(someCoffee.getCost())// 12
console.log(someCoffee.getDescription())

someCoffee = new WhipCoffee(someCoffee)
console.log(someCoffee.getCost())// 17
console.log(someCoffee.getDescription())

someCoffee = new VanillaCoffee(someCoffee)
console.log(someCoffee.getCost())// 20
console.log(someCoffee.getDescription())

Facade

To turn on the computer, just press the button. This is very simple, but a lot of complex actions take place inside the computer that is turned on. A simple interface to a complex system is the Facade .

Implementation example

Create a computer class:

 
class Computer {

   getElectricShock() {
       console.log('Ouch!')
   }

   makeSound() {
       console.log('Beep beep!')
   }

   showLoadingScreen() {
       console.log('Loading..')
   }

   bam() {
       console.log('Ready to be used!')
   }

   closeEverything() {
       console.log('Bup bup bup buzzzz!')
   }

   sooth() {
       console.log('Zzzzz')
   }

   pullCurrent() {
       console.log('Haaah!')
   }
}

and simple facade for its complex functions:

class ComputerFacade
{
   constructor(computer) {
       this.computer = computer
   }

   turnOn() {
       this.computer.getElectricShock()
       this.computer.makeSound()
       this.computer.showLoadingScreen()
       this.computer.bam()
   }

   turnOff() {
       this.computer.closeEverything()
       this.computer.pullCurrent()
       this.computer.sooth()
   }
}

So working with a computer is much easier:

const computer = new ComputerFacade(new Computer())
computer.turnOn() // Ouch! Beep beep! Loading.. Ready to be used!
computer.turnOff() // Bup bup buzzz! Haah! Zzzzz

Adapted

In long-distance trains, water for hot drinks is boiled in large containers – for all at once. This saves electricity (or gas).

The Adaptive pattern minimizes memory usage or computational costs by sharing the same data between many similar objects.

Implementation example

We have different types of tea and a big kettle:

// Adaptive - what will be cached
// Types of tea - adapters.
class KarakTea {
}

// Acts like a factory
class TeaMaker {
   constructor(){
       this.availableTea = {}
   }

   make(preference) {
       this.availableTea[preference] = this.availableTea[preference] || (new KarakTea())
       return this.availableTea[preference]
   }
}

Now we will create a tea shop that will take orders:

 class TeaShop {
   constructor(teaMaker) {
       this.teaMaker = teaMaker
       this.orders = []
   }

   takeOrder(teaType, table) {
       this.orders[table] = this.teaMaker.make(teaType)
   }

   serve() {
       this.orders.forEach((order, index) => {
           console.log('Serving tea to table#' + index)
       })
   }
}

It works as follows:

const teaMaker = new TeaMaker()
const shop = new TeaShop(teaMaker)

shop.takeOrder('less sugar', 1)
shop.takeOrder('more milk', 2)
shop.takeOrder('without sugar', 5)

shop.serve()
// Serving tea to table# 1
// Serving tea to table# 2
// Serving tea to table# 5

Proxy (Deputy)

Imagine a door with a combination lock. Its main functionality is to open up and skip you to another room, but a Proxy is added on top, which checks the access right.

An alternate provides some kind of additional logic: it restricts access to the main object, maintains logs, or performs caching.

Implementation example

First of all, create the door interface and its implementation:

/*
Door interface :

open()
close()
*/

class LabDoor {
   open() {
       console.log('Opening lab door')
   }

   close() {
       console.log('Closing the lab door')
   }
}

And now we will write a proxy class to ensure the security of our door:

class Security {
   constructor(door) {
       this.door = door
   }

   open(password) {
       if (this.authenticate(password)) {
           this.door.open()
       } else {
        console.log('Big no! It ain\'t possible.')
       }
   }

   authenticate(password) {
       return password === 'ecr@t'
   }

   close() {
       this.door.close()
   }
}

Now, anyone can not log in:

const door = new Security(new LabDoor())
door.open('invalid') // Big no! It ain't possible.

door.open('ecr@t') // Opening lab door
door.close() // Closing lab door

JavaScript behavioral design patterns

Behavioral  design patterns provide interaction between objects and distribute responsibilities.

Chain of duty

You have three accounts (A, B, C) with different amounts and different priority of use. First, A is checked, if there is enough money on it to buy, then the chain is interrupted. Otherwise, B is checked, then C. The chain will continue until it finds a suitable handler.

The Chain of Responsibility pattern allows you to line up objects in such a chain.

Implementation example

Here is your account that implements the account linking logic:

 class Account {

   setNext(account) {
       this.successor = account
   }
   
   pay(amountToPay) {
       if (this.canPay(amountToPay)) {
           console.log(`Paid ${amountToPay} using ${this.name}`)
       } else if (this.successor) {
           console.log(`Cannot pay using ${this.name}. Proceeding...`)
           this.successor.pay(amountToPay)
       } else {
           console.log('None of the accounts have enough balance')
       }
   }
   
   canPay(amount) {
       return this.balance >= amount
   }
}

class Bank extends Account {
   constructor(balance) {
       super()
       this.name = 'bank'
       this.balance = balance
   }
}

class Paypal extends Account {
   constructor(balance) {
       super()       
       this.name = 'Paypal'
       this.balance = balance
   }
}

class Bitcoin extends Account {
   constructor(balance) {
       super()       
       this.name = 'bitcoin'
       this.balance = balance
   }
}

Now we make a chain:

// Сделаем такую цепочку
//      bank->paypal->bitcoin
//
// Приоритет у банка
//      Если банк не может оплатить, переходим к Paypal
//      Если Paypal не может, переходим к Bitcoin

const bank = new Bank(100)          // Bank with balance 100
const paypal = new Paypal(200)      // Paypal with balance 200
const bitcoin = new Bitcoin(300)    // Bitcoin with balance 300

bank.setNext(paypal)
paypal.setNext(bitcoin)

// Начнём с банка
bank.pay(259)

// Cannot pay using bank. Proceeding ..
// Cannot pay using paypal. Proceeding ..:
// Paid 259 using Bitcoin!

Team

You come to a restaurant and make an order to the waiter. He redirects your Team to a chef who knows what and how to cook.

Pattern The command encapsulates some actions and data necessary for them and allows to separate the Client from the Recipient.

Implementation example

This is the Recipient, who is able to perform various actions:

 // Receiver
class Bulb {
   turnOn() {
       console.log('Bulb has been lit')
   }
   
   turnOff() {
       console.log('Darkness!')
   }
}

But a set of commands:

/*
Command interface :

   execute()
   undo()
   redo()
*/

// Command
class TurnOnCommand {
   constructor(bulb) {
       this.bulb = bulb
   }
   
   execute() {
       this.bulb.turnOn()
   }
   
   undo() {
       this.bulb.turnOff()
   }
   
   redo() {
       this.execute()
   }
}

class TurnOffCommand {
   constructor(bulb) {
       this.bulb = bulb
   }
   
   execute() {
       this.bulb.turnOff()
   }
   
   undo() {
       this.bulb.turnOn()
   }
   
   redo() {
       this.execute()
   }
}

This is the code of the caller (the waiter from the example):

// Invoker
class RemoteControl {
   submit(command) {
       command.execute()
   }
}

Here is an example of use:

const bulb = new Bulb()

const turnOn = new TurnOnCommand(bulb)
const turnOff = new TurnOffCommand(bulb)

const remote = new RemoteControl()
remote.submit(turnOn) // Bulb has been lit!
remote.submit(turnOff) // Darkness!

Iterator

Music players have buttons next and previous, which allow sequentially sorting through songs or radio stations.

The Iterator pattern provides access to the elements of an object without revealing the way they are represented internally.

Implementation example

Radio station:

 class RadioStation {
   constructor(frequency) {
       this.frequency = frequency    
   }
   
   getFrequency() {
       return this.frequency
   }
}

Iterator:

 class StationList {
   constructor(){
       this.stations = []
   }

   addStation(station) {
       this.stations.push(station)
   }
   
   removeStation(toRemove) {
       const toRemoveFrequency = toRemove.getFrequency()
       this.stations = this.stations.filter(station => {
           return station.getFrequency() !== toRemoveFrequency
       })
   }
}

You can add and remove stations, and also get their frequency:

const stationList = new StationList()

stationList.addStation(new RadioStation(89))
stationList.addStation(new RadioStation(101))
stationList.addStation(new RadioStation(102))
stationList.addStation(new RadioStation(103.2))

stationList.stations.forEach(station => console.log(station.getFrequency()))

stationList.removeStation(new RadioStation(89)) // Will remove station 89

Mediator

When you are talking to someone on a mobile phone, there is an Intermediary – Provider between you.

In this pattern, the Mediator object controls the interaction between two other objects (Colleagues), reducing the connection between them.

Implementation example

Let’s create a simple chat where users (Colleagues) can send messages to each other.

Chat looks like this:

 // Mediator
class ChatRoom {
   showMessage(user, message) {
       const time = new Date()
       const sender = user.getName()

       console.log(time + '[' + sender + ']:' + message)
   }
}

And these are the users themselves:

class User {
   constructor(name, chatMediator) {
       this.name = name
       this.chatMediator = chatMediator
   }
   
   getName() {
       return this.name
   }
   
   send(message) {
       this.chatMediator.showMessage(this, message)
   }
}

Let’s start the conversation:

const mediator = new ChatRoom()

const john = new User('John Doe', mediator)
const jane = new User('Jane Doe', mediator)

john.send('Hi there!')
jane.send('Hey!')

// Output will be
// Feb 14, 10:58 [John]: Hi there!
// Feb 14, 10:58 [Jane]: Hey!

The keeper

Some calculators can keep in memory the last expression, that is, the last state of the calculations.

The Keeper template captures the current state of an object and makes it possible to restore it.

Implementation example

Create a text editor with the function of saving content.

Guardian Object:

 class EditorMemento {
   constructor(content) {
       this._content = content
   }
   
   getContent() {
       return this._content
   }
}

Editor himself:

 class Editor {
   constructor(){
       this._content = ''
   }
   
   type(words) {
       this._content = this._content + ' ' + words
   }
   
   getContent() {
       return this._content
   }
   
   save() {
       return new EditorMemento(this._content)
   }
   
   restore(memento) {
       this._content = memento.getContent()
   }
}

Now you can safely write a coursework, all data will be saved!

const editor = new Editor()

// write something
editor.type('This is the first sentence.')
editor.type('This is second.')

// save state
const saved = editor.save()

// write something else
editor.type('And this is third.')

// content before saving
console.log(editor.getContent())// This is the first sentence. This is second. And this is third.

// recovery of the last state
editor.restore(saved)

console.log(editor.getContent()) // This is the first sentence. This is second.

Observer

This pattern is also known as publish-subscription pattern .

On job search sites, you can subscribe to job options that interest you. When a suitable offer appears, the site sends you a notification.

The Observer pattern allows you to notify all interested objects about the changes that have occurred.

Implementation example

Job seekers want to be notified:

const JobPost = title => ({
   title: title
})

class JobSeeker {
   constructor(name) {
       this._name = name
   }

   notify(jobPost) {
       console.log(this._name, 'has been notified of a new posting :', jobPost.title)
   }
}

A Bulletin Board can send these notifications:

 class JobBoard {
   constructor() {
       this._subscribers = []
   }

   subscribe(jobSeeker) {
       this._subscribers.push(jobSeeker)
   }

   addJob(jobPosting) {
       this._subscribers.forEach(subscriber => {
           subscriber.notify(jobPosting)
       })
   }
}

Subscribe to our newsletter:

// create subscribers
const jonDoe = new JobSeeker('John Doe')
const janeDoe = new JobSeeker('Jane Doe')
const kaneDoe = new JobSeeker('Kane Doe')

// create a bulletin board
// sign applicants
const jobBoard = new JobBoard()
jobBoard.subscribe(jonDoe)
jobBoard.subscribe(janeDoe)

// notify subscribers of a new job
jobBoard.addJob(JobPost('Software Engineer'))

// John Doe has been notified of a new posting : Software Engineer
// Jane Doe has been notified of a new posting : Software Engineer

Visitor

To go abroad, you need to get a permit (visa). But once in the country, you can safely visit a variety of places without asking for additional permission. It is enough to know about them.

The Visitor pattern allows you to add additional operations to objects without changing their source code.

Implementation example

Let’s model a zoo with different types of animals:

 class Monkey {
   shout() {
       console.log('Ooh oo aa aa!')
   }

   accept(operation) {
       operation.visitMonkey(this)
   }
}

class Lion {
   roar() {
       console.log('Roaaar!')
   }
   
   accept(operation) {
       operation.visitLion(this)
   }
}

class Dolphin {
   speak() {
       console.log('Tuut tuttu tuutt!')
   }
   
   accept(operation) {
       operation.visitDolphin(this)
   }
}

Now we want to hear what sounds they make. To do this, create a visitor:

 const speak = {
   visitMonkey(monkey){
       monkey.shout()
   },
   visitLion(lion){
       lion.roar()
   },
   visitDolphin(dolphin){
       dolphin.speak()
   }
}

He simply calls each class and calls the appropriate method:

const monkey = new Monkey()
const lion = new Lion()
const dolphin = new Dolphin()

monkey.accept(speak)    // Ooh oo aa aa!
lion.accept(speak)      // Roaaar!
dolphin.accept(speak)   // Tuut tutt tuutt!

The visitor can not change existing objects. With it you can, for example, add all these animals the ability to jump without creating additional methods.

 const jump = {
   visitMonkey(monkey) {
       console.log('Jumped 20 feet high! on to the tree!')
   },
   visitLion(lion) {
       console.log('Jumped 7 feet! Back on the ground!')
   },
   visitDolphin(dolphin) {
       console.log('Walked on water a little and disappeared')
   }
}

Voila:

monkey.accept(speak)   // Ooh oo aa aa!
monkey.accept(jump)    // Jumped 20 feet high! on to the tree!

lion.accept(speak) // Roaaar!
lion.accept(jump)      // Jumped 7 feet! Back on the ground!

dolphin.accept(speak)  // Tuut tutt tuutt!
dolphin.accept(jump)   // Walked on water a little and disappeared

Strategy

To order some data set you use the bubble sorting algorithm. It does an excellent job with small volumes, but it slows down with large ones. Quick sort has the opposite problem. Then you decide to change the algorithm depending on the size of the set. This is your strategy.

Template Strategy allows you to switch the algorithm used, depending on the situation.

Implementation example

Embody Strategy in JavaScript will help the function of the first class.

const bubbleSort = dataset => {
   console.log('Sorting with bubble sort')
   // ...
   // ...
   return dataset
}

const quickSort = dataset => {
   console.log('Sorting with quick sort')
   // ...
   // ...
   return dataset
}

And this is a client who can use any strategy:

const sorter = dataset => {
   if(dataset.length > 5){
       return quickSort
   } else {
       return bubbleSort
   }
}

Now you can sort the arrays:

const longDataSet = [1, 5, 4, 3, 2, 8]
const shortDataSet = [1, 5, 4]

const sorter1 = sorter(longDataSet)
const sorter2 = sorter(shortDataSet)

sorter1(longDataSet) // Sorting with quick sort
sorter2(shortDataSet) // Sorting with bubble sort

condition

You paint in Paint. Depending on your choice, the brush changes its state: it draws in red, blue or any other color.

The Status pattern  allows you to change the behavior of a class when a state changes.

Implementation example

Create a text editor in which you can change the state of the text – bold, italic, etc.

These are the conversion functions:

const upperCase = inputString => inputString.toUpperCase()
const lowerCase = inputString => inputString.toLowerCase()
const defaultTransform = inputString => inputString

And here is the editor himself:

 class TextEditor {
   constructor(transform) {
       this._transform = transform
   }
   
   setTransform(transform) {
       this._transform = transform
   }
   
   type(words) {
       console.log(this._transform(words))
   }
}

You can work:

const editor = new TextEditor(defaultTransform)

editor.type('First line')

editor.setTransform(upperCase)

editor.type('Second line')
editor.type('Third line')

editor.setTransform(lowerCase)

editor.type('Fourth line')
editor.type('Fifth line')

// First line
// SECOND LINE
// THIRD LINE
// fourth line
// fifth line

Template Method

You build a house according to a certain plan: first the foundation, then the walls, and only then the roof. The order of these steps cannot be changed, but their implementation may be different.

The template method defines the “skeleton” of the algorithm, but delegates the implementation of steps to the child classes.

Implementation example

Create a tool for testing, building and deploying the application.

The base class defines the skeleton of the assembly algorithm:

class Builder {
   // Template method
   build() {
       this.test()
       this.lint()
       this.assemble()
       this.deploy()
   }
}

And the child classes are the concrete implementation of each step:

 class AndroidBuilder extends Builder {
   test() {
       console.log('Running android tests')
   }
   
   lint() {
       console.log('Linting the android code')
   }
   
   assemble() {
       console.log('Assembling the android build')
   }
   
   deploy() {
       console.log('Deploying android build to server')
   }
}

class IosBuilder extends Builder {
   test() {
       console.log('Running ios tests')
   }
   
   lint() {
       console.log('Linting the ios code')
   }
   
   assemble() {
       console.log('Assembling the ios build')
   }
   
   deploy() {
       console.log('Deploying ios build to server')
   }
}

Let’s collect the project:

const androidBuilder = new AndroidBuilder()
androidBuilder.build()

// Running android tests
// Linting the android code
// Assembling the android build
// Deploying android build to server

const iosBuilder = new IosBuilder()
iosBuilder.build()

// Running ios tests
// Linting the ios code
// Assembling the ios build
// Deploying ios build to server

Related JAVA Tutorials For Beginners

JavaScript Introduction Tutorials
Introduction to JavaScript
Javascript Code editors
Javascript Reference and Specifications
Javascript Developer Console
Javascript Basics
JavaScript Hello World.!
External JavaScript Files
JavaScript Code Structure
JavaScript Variables
Use Strict in JavaScript

Leave a Comment