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