The previous chapter provided examples of the three different categories of design patterns. Some of these design patterns are relevant or required in the web development context. I have identified a few timeless patterns that can be helpful when applied in JavaScript. This chapter explores JavaScript implementations of different classic and modern design patterns. Every section is dedicated to one of the three categories—creational, structural, and behavioral. Let us begin with creational patterns.
A constructor is a special method used to initialize a newly created object once the memory has been allocated for it. With ES2015+, the syntax for creating classes with constructors was introduced to JavaScript. This enables the creation of objects as an instance of a class using the default constructor.
In JavaScript, almost everything is an object, and classes are syntactic sugar for JavaScript’s prototypal approach to inheritance. With classic JavaScript, we were most often interested in object constructors. Figure 7-1 illustrates the pattern.
Object constructors are used to create specific types of objects—both preparing the object for use and accepting arguments to set the values of member properties and methods when the object is first created.
The three common ways to create new objects in JavaScript are as follows:
// Each of the following options will create a new empty object
const
newObject
=
{};
// or
const
newObject
=
Object
.
create
(
Object
.
prototype
);
// or
const
newObject
=
new
Object
();
Here, we have declared each object as a constant, which creates a read-only block-scoped variable.
In the final example, the Object
constructor creates an object wrapper for a specific value, or where no value is passed, it creates an empty object and returns it.
You can now assign keys and values to an object in the following ways:
// ECMAScript 3 compatible approaches
// 1. Dot syntax
// Set properties
newObject
.
someKey
=
"Hello World"
;
// Get properties
var
key
=
newObject
.
someKey
;
// 2. Square bracket syntax
// Set properties
newObject
[
"someKey"
]
=
"Hello World"
;
// Get properties
var
key
=
newObject
[
"someKey"
];
// ECMAScript 5 only compatible approaches
// For more information see: http://kangax.github.com/es5-compat-table/
// 3. Object.defineProperty
// Set properties
Object
.
defineProperty
(
newObject
,
"someKey"
,
{
value
:
"for more control of the property's behavior"
,
writable
:
true
,
enumerable
:
true
,
configurable
:
true
});
// 4. If this feels a little difficult to read, a short-hand could
// be written as follows:
var
defineProp
=
function
(
obj
,
key
,
value
){
config
.
value
=
value
;
Object
.
defineProperty
(
obj
,
key
,
config
);
};
// To use, we then create a new empty "person" object
var
person
=
Object
.
create
(
null
);
// Populate the object with properties
defineProp
(
person
,
"car"
,
"Delorean"
);
defineProp
(
person
,
"dateOfBirth"
,
"1981"
);
defineProp
(
person
,
"hasBeard"
,
false
);
// 5. Object.defineProperties
// Set properties
Object
.
defineProperties
(
newObject
,
{
"someKey"
:
{
value
:
"Hello World"
,
writable
:
true
},
"anotherKey"
:
{
value
:
"Foo bar"
,
writable
:
false
}
});
// Getting properties for 3. and 4. can be done using any of the
// options in 1. and 2.
You can even use these methods for inheritance as follows:
// ES2015+ keywords/syntax used: const
// Usage:
// Create a race car driver that inherits from the person object
const
driver
=
Object
.
create
(
person
);
// Set some properties for the driver
defineProp
(
driver
,
'topSpeed'
,
'100mph'
);
// Get an inherited property (1981)
console
.
log
(
driver
.
dateOfBirth
);
// Get the property we set (100mph)
console
.
log
(
driver
.
topSpeed
);
As discussed earlier in Chapter 5, JavaScript classes were introduced in ES2015, allowing us to define templates for JavaScript objects and implement encapsulation and inheritance using JavaScript.
To recap, classes must include and declare a method named constructor()
, which will be used to instantiate a new object. The keyword new
allows us to call the constructor. The keyword this
inside a constructor references the new object created. The following example shows a basic constructor:
class
Car
{
constructor
(
model
,
year
,
miles
)
{
this
.
model
=
model
;
this
.
year
=
year
;
this
.
miles
=
miles
;
}
toString
()
{
return
`
${
this
.
model
}
has done
${
this
.
miles
}
miles`
;
}
}
// Usage:
// We can create new instances of the car
let
civic
=
new
Car
(
'Honda Civic'
,
2009
,
20000
);
let
mondeo
=
new
Car
(
'Ford Mondeo'
,
2010
,
5000
);
// and then open our browser console to view the output of
// the toString() method being called on these objects
console
.
log
(
civic
.
toString
());
console
.
log
(
mondeo
.
toString
());
This is a simple version of the Constructor pattern but it suffers from some problems. One is that it makes inheritance difficult, and the other is that functions such as toString()
are redefined for each new object created using the Car
constructor. This isn’t optimal because all of the instances of the Car
type should ideally share the same function.
Prototypes in JavaScript allow you to easily define methods for all instances of a particular object, be it a function or a class. When we call a JavaScript constructor to create an object, all the properties of the constructor’s prototype are then made available to the new object. In this fashion, you can have multiple Car
objects that access the same prototype. We can thus extend the original example as follows:
class
Car
{
constructor
(
model
,
year
,
miles
)
{
this
.
model
=
model
;
this
.
year
=
year
;
this
.
miles
=
miles
;
}
}
// Note here that we are using Object.prototype.newMethod rather than
// Object.prototype to avoid redefining the prototype object
// We still could use Object.prototype for adding new methods,
// because internally we use the same structure
Car
.
prototype
.
toString
=
function
()
{
return
`
${
this
.
model
}
has done
${
this
.
miles
}
miles`
;
};
// Usage:
let
civic
=
new
Car
(
'Honda Civic'
,
2009
,
20000
);
let
mondeo
=
new
Car
(
'Ford Mondeo'
,
2010
,
5000
);
console
.
log
(
civic
.
toString
());
console
.
log
(
mondeo
.
toString
());
All Car
objects will now share a single instance of the toString()
method.
Modules are an integral piece of any robust application’s architecture and typically help keep the units of code for a project cleanly separated and organized.
Classic JavaScript had several options for implementing modules, such as:
Object literal notation
The Module pattern
AMD modules
CommonJS modules
We have already discussed modern JavaScript modules (also known as “ES modules” or “ECMAScript modules”) in Chapter 5. We will primarily use ES modules for the examples in this section.
Before ES2015, CommonJS modules or AMD modules were popular alternatives because they allowed you to export the contents of a module. We will be exploring AMD, CommonJS, and UMD modules later in the book in Chapter 10. First, let us understand the Module pattern and its origins.
The Module pattern is based partly on object literals, so it makes sense to refresh our knowledge of them first.
In object literal notation, an object is described as a set of comma-separated name/value pairs enclosed in curly braces ({}
). Names inside the object may be either strings or identifiers followed by a colon. It would be best if you did not use a comma after the final name/value pair in the object, as this may result in errors:
const
myObjectLiteral
=
{
variableKey
:
variableValue
,
functionKey
()
{
// ...
}
};
Object literals don’t require instantiation using the new
operator but shouldn’t be used at the start of a statement because the opening {
may be interpreted as the beginning of a new block. Outside of an object, new members may be added to it using the assignment as follows myModule.property = "someValue";
.
Here is a complete example of a module defined using object literal notation:
const
myModule
=
{
myProperty
:
'someValue'
,
// object literals can contain properties and methods.
// e.g., we can define a further object for module configuration:
myConfig
:
{
useCaching
:
true
,
language
:
'en'
,
},
// a very basic method
saySomething
()
{
console
.
log
(
'Where is Paul Irish debugging today?'
);
},
// output a value based on the current configuration
reportMyConfig
()
{
console
.
log
(
`Caching is:
${
this
.
myConfig
.
useCaching
?
'enabled'
:
'disabled'
}
`
);
},
// override the current configuration
updateMyConfig
(
newConfig
)
{
if
(
typeof
newConfig
===
'object'
)
{
this
.
myConfig
=
newConfig
;
console
.
log
(
this
.
myConfig
.
language
);
}
},
};
// Outputs: What is Paul Irish debugging today?
myModule
.
saySomething
();
// Outputs: Caching is: enabled
myModule
.
reportMyConfig
();
// Outputs: fr
myModule
.
updateMyConfig
({
language
:
'fr'
,
useCaching
:
false
,
});
// Outputs: Caching is: disabled
myModule
.
reportMyConfig
();
Using object literals provided a way to encapsulate and organize code. Rebecca Murphey has written about this topic in depth should you wish to read into object literals further.
The Module pattern was initially defined to provide private and public encapsulation for classes in conventional software engineering.
At one point, organizing a JavaScript application of any reasonable size was a challenge. Developers would rely on separate scripts to split and manage reusable chunks of logic, and it wasn’t surprising to find 10 to 20 scripts being imported manually in an HTML file to keep things tidy. Using objects, the Module pattern was just one way to encapsulate logic in a file with both public and “private” methods. Over time, several custom module systems came about to make this smoother. Now, developers can use JavaScript modules to organize objects, functions, classes, or variables such that they can be easily exported or imported into other files. This helps prevent conflicts between classes or function names included in different modules. Figure 7-2 illustrates the Module pattern.
The Module pattern encapsulates the “privacy” state and organization using closures. It provides a way of wrapping a mix of public and private methods and variables, protecting pieces from leaking into the global scope and accidentally colliding with another developer’s interface. With this pattern, you expose only the public API, keeping everything else within the closure private.
This gives us a clean solution where the shielding logic does the heavy lifting while we expose only an interface we wish other parts of our application to use. The pattern uses an immediately invoked function expression (IIFE) where an object is returned. See Chapter 11 for more on IIFEs.
Note that there isn’t an explicitly true sense of “privacy” inside JavaScript because it doesn’t have access modifiers, unlike some traditional languages. You can’t technically declare variables as public or private, so we use function scope to simulate this concept. Within the Module pattern, variables or methods declared are available only inside the module itself, thanks to closure. However, variables or methods defined within the returning object are available to everyone.
A workaround to implement privacy of variables in returned objects uses WeakMap()
discussed later in this chapter in “Modern Module Pattern with WeakMap”. WeakMap()
takes only objects as keys and cannot be iterated. Thus, the only way to access the object inside a module is through its reference. Outside the module, you can access it only through a public method defined within it. Thus, it ensures privacy for the object.
From a historical perspective, the Module pattern was originally developed in 2003 by several people, including Richard Cornford. Douglas Crockford later popularized it in his lectures. Another piece of trivia is that some of its features may appear quite familiar if you’ve ever played with Yahoo’s YUI library. The reason for this is that the Module pattern was a strong influence on YUI when its components were created.
Let’s begin looking at implementing the Module pattern by creating a self-contained module.
We use the import
and export
keywords in our implementation. To recap our previous discussion, export
allows you to provide access to module features outside the module. At the same time, import
enables us to import bindings exported by a module to our script:
let
counter
=
0
;
const
testModule
=
{
incrementCounter
()
{
return
counter
++
;
},
resetCounter
()
{
console
.
log
(
`counter value prior to reset:
${
counter
}
`
);
counter
=
0
;
},
};
// Default export module, without name
export
default
testModule
;
// Usage:
// Import module from path
import
testModule
from
'./testModule'
;
// Increment our counter
testModule
.
incrementCounter
();
// Check the counter value and reset
// Outputs: counter value prior to reset: 1
testModule
.
resetCounter
();
Here, the other parts of the code cannot directly read the value of our
incrementCounter()
or resetCounter()
. The counter
variable is entirely shielded from our global scope, so it acts just like a private variable would—its existence is limited to within the module’s closure so that the two functions are the only code able to access its scope. Our methods are effectively namespaced, so in the test section of our code, we need to prefix any calls with the module’s name (e.g., testModule
).
When working with the Module pattern, we may find it helpful to define a simple template we can use to get started with it. Here’s one that covers namespacing, public, and private variables:
// A private counter variable
let
myPrivateVar
=
0
;
// A private function that logs any arguments
const
myPrivateMethod
=
foo
=>
{
console
.
log
(
foo
);
};
const
myNamespace
=
{
// A public variable
myPublicVar
:
'foo'
,
// A public function utilizing privates
myPublicFunction
(
bar
)
{
// Increment our private counter
myPrivateVar
++
;
// Call our private method using bar
myPrivateMethod
(
bar
);
},
};
export
default
myNamespace
;
What follows is another example, where we can see a shopping basket implemented using this pattern. The module itself is completely self-contained in a global variable called basketModule
. The basket
array in the module is kept private, so other parts of our application cannot directly read it. It exists only within the module’s closure, and so the only methods able to access it are those with access to its scope (i.e.,
addItem()
, getItem()
, etc.):
// privates
const
basket
=
[];
const
doSomethingPrivate
=
()
=>
{
//...
};
const
doSomethingElsePrivate
=
()
=>
{
//...
};
// Create an object exposed to the public
const
basketModule
=
{
// Add items to our basket
addItem
(
values
)
{
basket
.
push
(
values
);
},
// Get the count of items in the basket
getItemCount
()
{
return
basket
.
length
;
},
// Public alias to a private function
doSomething
()
{
doSomethingPrivate
();
},
// Get the total value of items in the basket
// The reduce() method applies a function against an accumulator and each
// element in the array (from left to right) to reduce it to a single value.
getTotal
()
{
return
basket
.
reduce
((
currentSum
,
item
)
=>
item
.
price
+
currentSum
,
0
);
},
};
export
default
basketModule
;
Inside the module, you may have noticed that we return an object
. This gets automatically assigned to basketModule
so that we can interact with it as follows:
// Import module from path
import
basketModule
from
'./basketModule'
;
// basketModule returns an object with a public API we can use
basketModule
.
addItem
({
item
:
'bread'
,
price
:
0.5
,
});
basketModule
.
addItem
({
item
:
'butter'
,
price
:
0.3
,
});
// Outputs: 2
console
.
log
(
basketModule
.
getItemCount
());
// Outputs: 0.8
console
.
log
(
basketModule
.
getTotal
());
// However, the following will not work:
// Outputs: undefined
// This is because the basket itself is not exposed as a part of our
// public API
console
.
log
(
basketModule
.
basket
);
// This also won't work as it exists only within the scope of our
// basketModule closure, not in the returned public object
console
.
log
(
basket
);
These methods are effectively namespaced inside basketModule
. All our functions are wrapped in this module, giving us several advantages, such as:
The freedom to have private functions that can be consumed only by our module. They aren’t exposed to the rest of the page (only our exported API is), so they’re considered truly private.
Given that functions are usually declared and named, it can be easier to show call stacks in a debugger when we’re attempting to discover what function(s) threw an exception.
Over time, designers have introduced different variations of the Module pattern suited to their needs.
This pattern variation demonstrates how you can pass globals (e.g., utility functions or external libraries) as arguments to a higher-order function in a module. This effectively allows us to import and locally alias them as we wish:
// utils.js
export
const
min
=
(
arr
)
=>
Math
.
min
(...
arr
);
// privateMethods.js
import
{
min
}
from
"./utils"
;
export
const
privateMethod
=
()
=>
{
console
.
log
(
min
([
10
,
5
,
100
,
2
,
1000
]));
};
// myModule.js
import
{
privateMethod
}
from
"./privateMethods"
;
const
myModule
=
()
=>
({
publicMethod
()
{
privateMethod
();
},
});
export
default
myModule
;
// main.js
import
myModule
from
"./myModule"
;
const
moduleInstance
=
myModule
();
moduleInstance
.
publicMethod
();
This next variation allows us to declare globals without consuming them and could similarly support the concept of global imports seen in the last example:
// module.js
const
privateVariable
=
"Hello World"
;
const
privateMethod
=
()
=>
{
// ...
};
const
module
=
{
publicProperty
:
"Foobar"
,
publicMethod
:
()
=>
{
console
.
log
(
privateVariable
);
},
};
export
default
module
;
We’ve seen why the Constructor pattern can be useful, but why is the Module pattern a good choice? For starters, it’s a lot cleaner for developers coming from an object-oriented background than the idea of true encapsulation, at least from a JavaScript perspective. With import Mixins, developers can manage dependencies between modules and pass globals as needed, making the code more maintainable and modular.
Secondly, it supports private data—so, in the Module pattern, we have access to only the values that we explicitly exported using the export
keyword. Values we didn’t expressly export are private and available only within the module. This reduces the risk of accidentally polluting the global scope. You don’t have to fear that you will accidentally overwrite values created by developers using your module that may have had the same name as your private value: it prevents naming collisions and global scope pollution.
With the Module pattern, we can encapsulate parts of our code that should not be publicly exposed. They make working with multiple dependencies and namespaces less risky. Note that a transpiler such as Babel is needed to use ES2015 modules in all JavaScript runtimes.
The disadvantages of the Module pattern are that we access both public and private members differently. When we wish to change the visibility, we must make changes to each place we use the member.
We also can’t access private members in methods we added to the object later. That said, in many cases, the Module pattern is still quite helpful and, when used correctly, certainly has the potential to improve the structure of our application.
Other disadvantages include the inability to create automated unit tests for private members and additional complexity when bugs require hot fixes. It’s simply not possible to patch privates. Instead, one must override all public methods interacting with the buggy privates. Developers can’t easily extend privates either, so it’s worth remembering that privates are not as flexible as they may initially appear.
For further reading on the Module pattern, see Ben Cherry’s excellent in-depth article.
Introduced to JavaScript in ES6, the WeakMap
object is a collection of key-value pairs in which the keys are weakly referenced. The keys must be objects, and the values can be arbitrary. The object is essentially a map where keys are held weakly. This means that keys will be a target for garbage collection (GC) if there is no active reference to the object. Examples 7-1, 7-2, and 7-3 look at an implementation of the Module pattern that uses the WeakMap
object.
let
_counter
=
new
WeakMap
();
class
Module
{
constructor
()
{
_counter
.
set
(
this
,
0
);
}
incrementCounter
()
{
let
counter
=
_counter
.
get
(
this
);
counter
++
;
_counter
.
set
(
this
,
counter
);
return
_counter
.
get
(
this
);
}
resetCounter
()
{
console
.
log
(
`counter value prior to reset:
${
_counter
.
get
(
this
)
}
`
);
_counter
.
set
(
this
,
0
);
}
}
const
testModule
=
new
Module
();
// Usage:
// Increment our counter
testModule
.
incrementCounter
();
// Check the counter value and reset
// Outputs: counter value prior to reset: 1
testModule
.
resetCounter
();
const
myPrivateVar
=
new
WeakMap
();
const
myPrivateMethod
=
new
WeakMap
();
class
MyNamespace
{
constructor
()
{
// A private counter variable
myPrivateVar
.
set
(
this
,
0
);
// A private function that logs any arguments
myPrivateMethod
.
set
(
this
,
foo
=>
console
.
log
(
foo
));
// A public variable
this
.
myPublicVar
=
'foo'
;
}
// A public function utilizing privates
myPublicFunction
(
bar
)
{
let
privateVar
=
myPrivateVar
.
get
(
this
);
const
privateMethod
=
myPrivateMethod
.
get
(
this
);
// Increment our private counter
privateVar
++
;
myPrivateVar
.
set
(
this
,
privateVar
);
// Call our private method using bar
privateMethod
(
bar
);
}
}
const
basket
=
new
WeakMap
();
const
doSomethingPrivate
=
new
WeakMap
();
const
doSomethingElsePrivate
=
new
WeakMap
();
class
BasketModule
{
constructor
()
{
// privates
basket
.
set
(
this
,
[]);
doSomethingPrivate
.
set
(
this
,
()
=>
{
//...
});
doSomethingElsePrivate
.
set
(
this
,
()
=>
{
//...
});
}
// Public aliases to a private function
doSomething
()
{
doSomethingPrivate
.
get
(
this
)();
}
doSomethingElse
()
{
doSomethingElsePrivate
.
get
(
this
)();
}
// Add items to our basket
addItem
(
values
)
{
const
basketData
=
basket
.
get
(
this
);
basketData
.
push
(
values
);
basket
.
set
(
this
,
basketData
);
}
// Get the count of items in the basket
getItemCount
()
{
return
basket
.
get
(
this
).
length
;
}
// Get the total value of items in the basket
getTotal
()
{
return
basket
.
get
(
this
)
.
reduce
((
currentSum
,
item
)
=>
item
.
price
+
currentSum
,
0
);
}
}
You can use the Module pattern when building applications with JavaScript libraries such as React. Let’s say you have a large number of custom components created by your team. In that case, you can separate each component in its own file, essentially creating a module for every component. Here is an example of a button component customized from the material-ui button component and exported as a module:
import
React
from
"react"
;
import
Button
from
"@material-ui/core/Button"
;
const
style
=
{
root
:
{
borderRadius
:
3
,
border
:
0
,
color
:
"white"
,
margin
:
"0 20px"
},
primary
:
{
background
:
"linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)"
},
secondary
:
{
background
:
"linear-gradient(45deg, #2196f3 30%, #21cbf3 90%)"
}
};
export
default
function
CustomButton
(
props
)
{
return
(
<
Button
{...
props
}
style
=
{{
...
style
.
root
,
...
style
[
props
.
color
]
}}
>
{
props
.
children
}
<
/Button>
);
}
Now that we are a little more familiar with the Module pattern, let’s look at a slightly improved version: Christian Heilmann’s Revealing Module pattern.
The Revealing Module pattern came about as Heilmann was frustrated that he had to repeat the name of the main object when he wanted to call one public method from another or access public variables. He also disliked switching to object literal notation for the things he wished to make public.
His efforts resulted in an updated pattern where we can simply define all functions and variables in the private scope and return an anonymous object with pointers to the private functionality we wished to reveal as public.
With the modern way of implementing modules in ES2015+, the scope of functions and variables defined in the module is already private. Also, we use export
and import
to reveal whatever needs to be revealed.
An example of the use of the Revealing Module pattern with ES2015+ is as follows:
let
privateVar
=
'Rob Dodson'
;
const
publicVar
=
'Hey there!'
;
const
privateFunction
=
()
=>
{
console
.
log
(
`Name:
${
privateVar
}
`
);
};
const
publicSetName
=
strName
=>
{
privateVar
=
strName
;
};
const
publicGetName
=
()
=>
{
privateFunction
();
};
// Reveal public pointers to
// private functions and properties
const
myRevealingModule
=
{
setName
:
publicSetName
,
greeting
:
publicVar
,
getName
:
publicGetName
,
};
export
default
myRevealingModule
;
// Usage:
import
myRevealingModule
from
'./myRevealingModule'
;
myRevealingModule
.
setName
(
'Matt Gaunt'
);
In this example, we reveal the private variable privateVar
through its public get and set methods, publicSetName
and publicGetName
.
You can also use the pattern to reveal private functions and properties with a more specific naming scheme:
let
privateCounter
=
0
;
const
privateFunction
=
()
=>
{
privateCounter
++
;
}
const
publicFunction
=
()
=>
{
publicIncrement
();
}
const
publicIncrement
=
()
=>
{
privateFunction
();
}
const
publicGetCount
=
()
=>
privateCounter
;
// Reveal public pointers to
// private functions and properties
const
myRevealingModule
=
{
start
:
publicFunction
,
increment
:
publicIncrement
,
count
:
publicGetCount
};
export
default
myRevealingModule
;
// Usage:
import
myRevealingModule
from
'./myRevealingModule'
;
myRevealingModule
.
start
();
A disadvantage of this pattern is that if a private function refers to a public function, that public function can’t be overridden if a patch is necessary. This is because the private function will continue to refer to the private implementation, and the pattern doesn’t apply to public members, only to functions.
Public object members, which refer to private variables, are also subject to the no-patch rule.
As a result, modules created with the Revealing Module pattern may be more fragile than those created with the original Module pattern, and you should take care when using it.
The Singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. Classically, you can implement the Singleton pattern by creating a class with a method that creates a new instance of the class only if one doesn’t already exist. If an instance already exists, it simply returns a reference to that object.
Singletons differ from static classes (or objects) in that we can delay their initialization because they require certain information that may not be available during initialization time. Any code that is unaware of a previous reference to the Singleton class cannot easily retrieve it. This is because it is neither the object nor “class” that a Singleton returns; it’s a structure. Think of how closured variables aren’t actually closures—the function scope that provides the closure is the closure.
ES2015+ allows us to implement the Singleton pattern to create a global instance of a JavaScript class that is instantiated once. You can expose the Singleton instance through a module export. This makes access to it more explicit and controlled and differentiates it from other global variables. You cannot create a new class instance but can read/modify the instance using public get and set methods defined in the class.
We can implement a Singleton as follows:
// Instance stores a reference to the Singleton
let
instance
;
// Private methods and variables
const
privateMethod
=
()
=>
{
console
.
log
(
'I am private'
);
};
const
privateVariable
=
'Im also private'
;
const
randomNumber
=
Math
.
random
();
// Singleton
class
MySingleton
{
// Get the Singleton instance if one exists
// or create one if it doesn't
constructor
()
{
if
(
!
instance
)
{
// Public property
this
.
publicProperty
=
'I am also public'
;
instance
=
this
;
}
return
instance
;
}
// Public methods
publicMethod
()
{
console
.
log
(
'The public can see me!'
);
}
getRandomNumber
()
{
return
randomNumber
;
}
}
// [ES2015+] Default export module, without name
export
default
MySingleton
;
// Instance stores a reference to the Singleton
let
instance
;
// Singleton
class
MyBadSingleton
{
// Always create a new Singleton instance
constructor
()
{
this
.
randomNumber
=
Math
.
random
();
instance
=
this
;
return
instance
;
}
getRandomNumber
()
{
return
this
.
randomNumber
;
}
}
export
default
MyBadSingleton
;
// Usage:
import
MySingleton
from
'./MySingleton'
;
import
MyBadSingleton
from
'./MyBadSingleton'
;
const
singleA
=
new
MySingleton
();
const
singleB
=
new
MySingleton
();
console
.
log
(
singleA
.
getRandomNumber
()
===
singleB
.
getRandomNumber
());
// true
const
badSingleA
=
new
MyBadSingleton
();
const
badSingleB
=
new
MyBadSingleton
();
console
.
log
(
badSingleA
.
getRandomNumber
()
!==
badSingleB
.
getRandomNumber
());
// true
// Note: as we are working with random numbers, there is a mathematical
// possibility both numbers will be the same, however unlikely.
// The preceding example should otherwise still be valid.
What makes the Singleton is the global access to the instance. The GoF book describes the applicability of the Singleton pattern as follows:
There must be exactly one instance of a class, and it must be accessible to clients from a well-known access point.
The sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code.
The second of these points refers to a case where we might need code, such as:
constructor
()
{
if
(
this
.
_instance
==
null
)
{
if
(
isFoo
())
{
this
.
_instance
=
new
FooSingleton
();
}
else
{
this
.
_instance
=
new
BasicSingleton
();
}
}
return
this
.
_instance
;
}
Here, the constructor
becomes a little like a Factory method, and we don’t need to update each point in our code accessing it. FooSingleton
(in this example) would be a subclass of BasicSingleton
and implement the same interface.
Why is deferring execution considered significant for a Singleton? In C++, it serves as isolation from the unpredictability of the dynamic initialization order, returning control to the programmer.
It is essential to note the difference between a static instance of a class (object) and a Singleton. While you can implement a Singleton as a static instance, it can also be constructed lazily, without the need for resources or memory until it is needed.
Suppose we have a static object that we can initialize directly. In that case, we need to ensure the code is always executed in the same order (e.g., in case objCar
needs objWheel
during its initialization), and this doesn’t scale when you have a large number of source files.
Both Singletons and static objects are useful but shouldn’t be overused—the same way we shouldn’t overuse other patterns.
In practice, it helps to use the Singleton pattern when exactly one object is needed to coordinate others across a system. The following is one example that uses the pattern in this context:
// options: an object containing configuration options for the Singleton
// e.g., const options = { name: "test", pointX: 5};
class
Singleton
{
constructor
(
options
=
{})
{
// set some properties for our Singleton
this
.
name
=
'SingletonTester'
;
this
.
pointX
=
options
.
pointX
||
6
;
this
.
pointY
=
options
.
pointY
||
10
;
}
}
// our instance holder
let
instance
;
// an emulation of static variables and methods
const
SingletonTester
=
{
name
:
'SingletonTester'
,
// Method for getting an instance. It returns
// a Singleton instance of a Singleton object
getInstance
(
options
)
{
if
(
instance
===
undefined
)
{
instance
=
new
Singleton
(
options
);
}
return
instance
;
},
};
const
singletonTest
=
SingletonTester
.
getInstance
({
pointX
:
5
,
});
// Log the output of pointX just to verify it is correct
// Outputs: 5
console
.
log
(
singletonTest
.
pointX
);
While the Singleton has valid uses, often, when we find ourselves needing it in JavaScript, it’s a sign that we may need to reevaluate our design. Unlike C++ or Java, where you have to define a class to create an object, JavaScript allows you to create objects directly. Thus, you can create one such object directly instead of defining a Singleton class. In contrast, using Singleton classes in JavaScript has some disadvantages:
If you’re importing a large module, you will be unable to recognize that a particular class is a Singleton. As a result, you may accidentally use it as a regular class to instantiate multiple objects and incorrectly update it instead.
Singletons can be more difficult to test due to issues ranging from hidden dependencies, difficulty creating multiple instances, difficulty in stubbing dependencies, and so on.
An everyday use case for Singletons would be to store data that will be required across the global scope, such as user credentials or cookie data that can be set once and consumed by multiple components. Implementing the correct execution order becomes essential so that data is always consumed after it becomes available and not the other way around. This may become challenging as the application grows in size and complexity.
Developers using React for web development can rely on the global state through state management tools such as Redux or React Context instead of Singletons. Unlike Singletons, these tools provide a read-only state rather than the mutable state.
Although the downsides to having a global state don’t magically disappear by using these tools, we can at least ensure that the global state is mutated the way we intend it to because components cannot update it directly.
The GoF refers to the Prototype pattern as one that creates objects based on a template of an existing object through cloning.
We can think of the Prototype pattern as being based on prototypal inheritance, where we create objects that act as prototypes for other objects. The prototype
object is effectively used as a blueprint for each object the constructor creates. For example, if the prototype of the constructor function used contains a property called name
(as per the code sample that follows), then each object created by that constructor will also have this same property. Refer to Figure 7-3 for an illustration.
Reviewing the definitions for this pattern in existing (non-JavaScript) literature, we may find references to classes once again. The reality is that prototypal inheritance avoids using classes altogether. There isn’t a “definition” object nor a core object in theory; we’re simply creating copies of existing functional objects.
One of the benefits of using the Prototype pattern is that we’re working with the prototypal strengths JavaScript has to offer natively rather than attempting to imitate features of other languages. With other design patterns, this isn’t always the case.
Not only is the pattern an easy way to implement inheritance, but it can also come with a performance boost. When defining functions in an object, they’re all created by reference (so all child objects point to the same functions), instead of creating individual copies.
With ES2015+, we can use classes and constructors to create objects. While this ensures that our code looks cleaner and follows object-oriented analysis and design (OOAD) principles, the classes and constructors get compiled down to functions and prototypes internally. This ensures that we are still working with the prototypal strengths of JavaScript and the accompanying performance boost.
For those interested, real prototypal inheritance, as defined in the ECMAScript 5 standard, requires the use of Object.create
(which we looked at earlier in this
section). To review, Object.create
creates an object with a specified prototype
and optionally contains specified properties (e.g., Object.create( prototype,
optionalDescriptorObjects )
).
We can see this demonstrated in the following example:
const
myCar
=
{
name
:
'Ford Escort'
,
drive
()
{
console
.
log
(
"Weeee. I'm driving!"
);
},
panic
()
{
console
.
log
(
'Wait. How do you stop this thing?'
);
},
};
// Use Object.create to instantiate a new car
const
yourCar
=
Object
.
create
(
myCar
);
// Now we can see that one is a prototype of the other
console
.
log
(
yourCar
.
name
);
Object.create
also allows us to easily implement advanced concepts such as differential inheritance, where objects are able to directly inherit from other objects. We saw earlier that Object.create
allows us to initialize object properties using the second supplied argument. For example:
const
vehicle
=
{
getModel
()
{
console
.
log
(
`The model of this vehicle is...
${
this
.
model
}
`
);
},
};
const
car
=
Object
.
create
(
vehicle
,
{
id
:
{
value
:
MY_GLOBAL
.
nextId
(),
// writable:false, configurable:false by default
enumerable
:
true
,
},
model
:
{
value
:
'Ford'
,
enumerable
:
true
,
},
});
Here, you can initialize the properties on the second argument of Object.create
using an object literal with a syntax similar to that used by the
Object.defineProperties
and Object.defineProperty
methods that we looked at previously.
It is worth noting that prototypal relationships can cause trouble when enumerating properties of objects and (as Crockford recommends) wrapping the contents of the loop in a hasOwnProperty()
check.
If we wish to implement the Prototype pattern without directly using Object.create
, we can simulate the pattern as per the previous example as follows:
class
VehiclePrototype
{
constructor
(
model
)
{
this
.
model
=
model
;
}
getModel
()
{
console
.
log
(
`The model of this vehicle is...
${
this
.
model
}
`
);
}
clone
()
{}
}
class
Vehicle
extends
VehiclePrototype
{
constructor
(
model
)
{
super
(
model
);
}
clone
()
{
return
new
Vehicle
(
this
.
model
);
}
}
const
car
=
new
Vehicle
(
'Ford Escort'
);
const
car2
=
car
.
clone
();
car2
.
getModel
();
This alternative does not allow the user to define read-only properties in the same manner (as the vehiclePrototype
may be altered if not careful).
A final alternative implementation of the Prototype pattern could be the following:
const
beget
=
(()
=>
{
class
F
{
constructor
()
{}
}
return
proto
=>
{
F
.
prototype
=
proto
;
return
new
F
();
};
})();
One could reference this method from the vehicle
function. However, note that
vehicle
here emulates a constructor since the Prototype pattern does not include any notion of initialization beyond linking an object to a prototype.
The Factory pattern is another creational pattern for creating objects. It differs from the other patterns in its category because it doesn’t explicitly require us to use a constructor. Instead, a Factory can provide a generic interface for creating objects, where we can specify the type of Factory object we want to create (Figure 7-4).
Imagine a UI factory where we want to create a type of UI component. Rather than creating this component directly using the new
operator or another creational constructor, we ask a Factory object for a new component instead. We inform the Factory what type of object is required (e.g., “Button”, “Panel”), and it instantiates it and returns it to us for use.
This is particularly useful if the object creation process is relatively complex, e.g., if it strongly depends on dynamic factors or application configuration.
The following example builds upon our previous snippets using the Constructor pattern logic to define cars. It demonstrates how a VehicleFactory
may be implemented using the Factory pattern:
// Types.js - Classes used behind the scenes
// A class for defining new cars
class
Car
{
constructor
({
doors
=
4
,
state
=
'brand new'
,
color
=
'silver'
}
=
{})
{
this
.
doors
=
doors
;
this
.
state
=
state
;
this
.
color
=
color
;
}
}
// A class for defining new trucks
class
Truck
{
constructor
({
state
=
'used'
,
wheelSize
=
'large'
,
color
=
'blue'
}
=
{})
{
this
.
state
=
state
;
this
.
wheelSize
=
wheelSize
;
this
.
color
=
color
;
}
}
// FactoryExample.js
// Define a vehicle factory
class
VehicleFactory
{
constructor
()
{
this
.
vehicleClass
=
Car
;
}
// Our Factory method for creating new Vehicle instances
createVehicle
(
options
)
{
const
{
vehicleType
,
...
rest
}
=
options
;
switch
(
vehicleType
)
{
case
'car'
:
this
.
vehicleClass
=
Car
;
break
;
case
'truck'
:
this
.
vehicleClass
=
Truck
;
break
;
// defaults to VehicleFactory.prototype.vehicleClass (Car)
}
return
new
this
.
vehicleClass
(
rest
);
}
}
// Create an instance of our factory that makes cars
const
carFactory
=
new
VehicleFactory
();
const
car
=
carFactory
.
createVehicle
({
vehicleType
:
'car'
,
color
:
'yellow'
,
doors
:
6
,
});
// Test to confirm our car was created using the vehicleClass/prototype Car
// Outputs: true
console
.
log
(
car
instanceof
Car
);
// Outputs: Car object of color "yellow", doors: 6 in a "brand new" state
console
.
log
(
car
);
We have defined the car and truck classes with constructors that set properties relevant to the respective vehicle. The VehicleFactory
can create a new vehicle object, Car
, or Truck
based on the vehicleType
passed.
There are two possible approaches to building trucks using the VehicleFactory
class.
In Approach 1, we modify a VehicleFactory
instance to use the Truck
class:
const
movingTruck
=
carFactory
.
createVehicle
({
vehicleType
:
'truck'
,
state
:
'like new'
,
color
:
'red'
,
wheelSize
:
'small'
,
});
// Test to confirm our truck was created with the vehicleClass/prototype Truck
// Outputs: true
console
.
log
(
movingTruck
instanceof
Truck
);
// Outputs: Truck object of color "red", a "like new" state
// and a "small" wheelSize
console
.
log
(
movingTruck
);
In Approach 2, we subclass VehicleFactory
to create a factory class that builds Trucks
:
class
TruckFactory
extends
VehicleFactory
{
constructor
()
{
super
();
this
.
vehicleClass
=
Truck
;
}
}
const
truckFactory
=
new
TruckFactory
();
const
myBigTruck
=
truckFactory
.
createVehicle
({
state
:
'omg...so bad.'
,
color
:
'pink'
,
wheelSize
:
'so big'
,
});
// Confirms that myBigTruck was created with the prototype Truck
// Outputs: true
console
.
log
(
myBigTruck
instanceof
Truck
);
// Outputs: Truck object with the color "pink", wheelSize "so big"
// and state "omg. so bad"
console
.
log
(
myBigTruck
);
The Factory pattern can be beneficial when applied to the following situations:
When our object or component setup involves a high level of complexity.
When we need a convenient way to generate different instances of objects depending on the environment we are in.
When we’re working with many small objects or components that share the same properties.
When composing objects with instances of other objects that need only satisfy an API contract (aka, duck typing) to work. This is useful for decoupling.
When applied to the wrong type of problem, this pattern can introduce a large amount of unnecessary complexity to an application. Unless providing an interface for object creation is a design goal for the library or framework we are writing, I would suggest sticking to explicit constructors to avoid undue overhead.
Since the process of object creation is effectively abstracted behind an interface, this can also introduce problems with unit testing, depending on just how complex this process might be.
It’s also worthwhile to be aware of the Abstract Factory pattern, which aims to encapsulate a group of individual factories with a common goal. It separates the details of implementing a set of objects from their general usage.
You can use an Abstract Factory when a system must be independent of how the objects it creates are generated, or it needs to work with multiple types of objects.
An example that is both simple and easier to understand is a vehicle factory,
which defines ways to get or register vehicle types. The Abstract Factory can be named AbstractVehicleFactory
. The Abstract Factory will allow the definition of types of vehicles like car
or truck
, and concrete factories will implement only
classes that fulfill the vehicle contract (e.g., Vehicle.prototype.drive
and
Vehicle.prototype.breakDown
):
class
AbstractVehicleFactory
{
constructor
()
{
// Storage for our vehicle types
this
.
types
=
{};
}
getVehicle
(
type
,
customizations
)
{
const
Vehicle
=
this
.
types
[
type
];
return
Vehicle
?
new
Vehicle
(
customizations
)
:
null
;
}
registerVehicle
(
type
,
Vehicle
)
{
const
proto
=
Vehicle
.
prototype
;
// only register classes that fulfill the vehicle contract
if
(
proto
.
drive
&&
proto
.
breakDown
)
{
this
.
types
[
type
]
=
Vehicle
;
}
return
this
;
}
}
// Usage:
const
abstractVehicleFactory
=
new
AbstractVehicleFactory
();
abstractVehicleFactory
.
registerVehicle
(
'car'
,
Car
);
abstractVehicleFactory
.
registerVehicle
(
'truck'
,
Truck
);
// Instantiate a new car based on the abstract vehicle type
const
car
=
abstractVehicleFactory
.
getVehicle
(
'car'
,
{
color
:
'lime green'
,
state
:
'like new'
,
});
// Instantiate a new truck in a similar manner
const
truck
=
abstractVehicleFactory
.
getVehicle
(
'truck'
,
{
wheelSize
:
'medium'
,
color
:
'neon yellow'
,
});
Structural patterns deal with class and object composition. For example, the concept of inheritance allows us to compose interfaces and objects so that they can obtain new functionality. Structural patterns provide the best methods and practices to organize classes and objects.
Following are the JavaScript structural patterns that we will discuss in this section:
When we put up a facade, we present an outward appearance to the world, which may conceal a very different reality. This inspired the name for the next pattern we’ll review—the Facade pattern. This pattern provides a convenient higher-level interface to a larger body of code, hiding its true underlying complexity. Think of it as simplifying the API being presented to other developers, a quality that almost always improves usability (see Figure 7-5).
Facades are a structural pattern that can often be seen in JavaScript libraries such as jQuery where, although an implementation may support methods with a wide range of behaviors, only a “facade,” or limited abstraction of these methods, is presented to the public for use.
This allows us to interact with the Facade directly rather than the subsystem behind the scenes. Whenever we use jQuery’s $(el).css()
or $(el).animate()
methods, we’re using a Facade: the simpler public interface that lets us avoid manually calling the many internal methods in jQuery core required to get some behavior working. This also circumvents the need to interact manually with DOM APIs and maintain state variables.
The jQuery core methods should be considered intermediate abstractions. The more immediate burden to developers is that the DOM API and Facades make the jQuery library so easy to use.
To build on what we’ve learned, the Facade pattern simplifies a class’s interface and decouples the class from the code that uses it. This allows us to interact indirectly with subsystems in a way that can sometimes be less error-prone than accessing the subsystem directly. A Facade’s advantages include ease of use and often a small-sized footprint in implementing the pattern.
Let’s take a look at the pattern in action. This is an unoptimized code example, but here we’re using a Facade to simplify an interface for listening to events across browsers. We do this by creating a common method that does the task of checking for the existence of features so that it can provide a safe and cross-browser-compatible solution:
const
addMyEvent
=
(
el
,
ev
,
fn
)
=>
{
if
(
el
.
addEventListener
)
{
el
.
addEventListener
(
ev
,
fn
,
false
);
}
else
if
(
el
.
attachEvent
)
{
el
.
attachEvent
(
`on
${
ev
}
`
,
fn
);
}
else
{
el
[
`on
${
ev
}
`
]
=
fn
;
}
};
In a similar manner, we’re all familiar with jQuery’s $(document).ready(…)
. Internally, this is powered by a method called bindReady()
, which is doing this:
function
bindReady
()
{
// Use the handy event callback
document
.
addEventListener
(
'DOMContentLoaded'
,
DOMContentLoaded
,
false
);
// A fallback to window.onload, that will always work
window
.
addEventListener
(
'load'
,
jQuery
.
ready
,
false
);
}
This is another example of a Facade where the rest of the world uses the limited interface exposed by $(document).ready(…)
, and the more complex implementation powering it is kept hidden from sight.
Facades don’t just have to be used on their own, however. You can also integrate them with other patterns, such as the Module pattern. As we can see next, our instance of the Module pattern contains a number of methods that have been privately defined. A Facade is then used to supply a much simpler API for accessing these methods:
// privateMethods.js
const
_private
=
{
i
:
5
,
get
()
{
console
.
log
(
`current value:
${
this
.
i
}
`
);
},
set
(
val
)
{
this
.
i
=
val
;
},
run
()
{
console
.
log
(
'running'
);
},
jump
()
{
console
.
log
(
'jumping'
);
},
};
export
default
_private
;
// module.js
import
_private
from
'./privateMethods.js'
;
const
module
=
{
facade
({
val
,
run
})
{
_private
.
set
(
val
);
_private
.
get
();
if
(
run
)
{
_private
.
run
();
}
},
};
export
default
module
;
// index.js
import
module
from
'./module.js'
;
// Outputs: "current value: 10" and "running"
module
.
facade
({
run
:
true
,
val
:
10
,
});
In this example, calling module.facade()
will trigger a set of private behavior within the module, but the users aren’t concerned with this. We’ve made it much easier for them to consume a feature without worrying about implementation-level details.
We have already introduced the ES2015+ features that allow us to extend a base or superclass and call the methods in the superclass. The child class that extends the superclass is known as a subclass.
Subclassing refers to inheriting properties for a new object from a base or superclass object. A subclass can still define its methods, including those that override methods initially defined in the superclass. The method in the subclass can invoke an overridden method in the superclass, known as method chaining. Similarly, it can invoke the superclass’s constructor, which is known as constructor chaining.
To demonstrate subclassing, we first need a base class that can have new instances of itself created. Let’s model this around the concept of a person:
class
Person
{
constructor
(
firstName
,
lastName
)
{
this
.
firstName
=
firstName
;
this
.
lastName
=
lastName
;
this
.
gender
=
"male"
;
}
}
// a new instance of Person can then easily be created as follows:
const
clark
=
new
Person
(
'Clark'
,
'Kent'
);
Next, we’ll want to specify a new class that’s a subclass of the existing Person
class. Let us imagine we want to add distinct properties to distinguish a Person
from a
Superhero
while inheriting the properties of the Person
superclass. As superheroes share many common traits with ordinary people (e.g., name, gender), this should ideally adequately illustrate how subclassing works:
class
Superhero
extends
Person
{
constructor
(
firstName
,
lastName
,
powers
)
{
// Invoke the superclass constructor
super
(
firstName
,
lastName
);
this
.
powers
=
powers
;
}
}
// A new instance of Superhero can be created as follows
const
SuperMan
=
new
Superhero
(
'Clark'
,
'Kent'
,
[
'flight'
,
'heat-vision'
]);
console
.
log
(
SuperMan
);
// Outputs Person attributes as well as power
The Superhero
constructor creates an instance of the Superhero
class, which is an extension of the Person
class. Objects of this type have attributes of the classes above it in the chain. If we had set default values in the Person
class, Superhero
could override any inherited values with values specific to its class.
In JavaScript, we can look at inheriting from Mixins to collect functionality through extension. Each new class we define can have a superclass from which it can inherit methods and properties. Classes can also define their own properties and methods. We can leverage this fact to promote function reuse, as shown in Figure 7-6.
Mixins allow objects to borrow (or inherit) functionality from them with minimal complexity. Thus, Mixins are classes with attributes and methods that can be easily shared across several other classes.
While JavaScript classes cannot inherit from multiple superclasses, we can still mix functionality from various classes. A class in JavaScript can be used as an expression as well as a statement. As an expression, it returns a new class each time it’s evaluated. The extends clause can also accept arbitrary expressions that return classes or constructors. These features enable us to define a Mixin as a function that accepts a superclass and creates a new subclass from it.
Imagine that we define a Mixin containing utility functions in a standard JavaScript class as follows:
const
MyMixins
=
superclass
=>
class
extends
superclass
{
moveUp
()
{
console
.
log
(
'move up'
);
}
moveDown
()
{
console
.
log
(
'move down'
);
}
stop
()
{
console
.
log
(
'stop! in the name of love!'
);
}
};
Here, we created a MyMixins
function that can extend a dynamic superclass. We will now create two classes, CarAnimator
and PersonAnimator
, from which MyMixins
can extend and return a subclass with methods defined in MyMixins
and those in the class being extended:
// A skeleton carAnimator constructor
class
CarAnimator
{
moveLeft
()
{
console
.
log
(
'move left'
);
}
}
// A skeleton personAnimator constructor
class
PersonAnimator
{
moveRandomly
()
{
/*...*/
}
}
// Extend MyMixins using CarAnimator
class
MyAnimator
extends
MyMixins
(
CarAnimator
)
{}
// Create a new instance of carAnimator
const
myAnimator
=
new
MyAnimator
();
myAnimator
.
moveLeft
();
myAnimator
.
moveDown
();
myAnimator
.
stop
();
// Outputs:
// move left
// move down
// stop! in the name of love!
As we can see, this makes mixing similar behavior into classes reasonably trivial.
The following example has two classes: a Car
and a Mixin
. What we’re going to do is augment (another way of saying extend) the Car
so that it can inherit specific methods defined in the Mixin
, namely driveForward()
and driveBackward()
.
This example will demonstrate how to augment a constructor to include functionality without the need to duplicate this process for every constructor function we may have:
// Car.js
class
Car
{
constructor
({
model
=
'no model provided'
,
color
=
'no color provided'
})
{
this
.
model
=
model
;
this
.
color
=
color
;
}
}
export
default
Car
;
// Mixin.js and index.js remain unchanged
// index.js
import
Car
from
'./Car.js'
;
import
Mixin
from
'./Mixin.js'
;
class
MyCar
extends
Mixin
(
Car
)
{}
// Create a new Car
const
myCar
=
new
MyCar
({});
// Test to make sure we now have access to the methods
myCar
.
driveForward
();
myCar
.
driveBackward
();
// Outputs:
// drive forward
// drive backward
const
mySportsCar
=
new
MyCar
({
model
:
'Porsche'
,
color
:
'red'
,
});
mySportsCar
.
driveSideways
();
// Outputs:
// drive sideways
Mixins assist in decreasing functional repetition and increasing function reuse in a system. Where an application is likely to require shared behavior across object instances, we can easily avoid duplication by maintaining this shared functionality in a Mixin and thus focusing on implementing only the functionality in our system, which is truly distinct.
That said, the downsides to Mixins are a little more debatable. Some developers feel that injecting functionality into a class or an object prototype is a bad idea as it leads to both prototype pollution and a level of uncertainty regarding the origin of our functions. In large systems, this may well be the case.
Even with React, Mixins were often used to add functionality to components before the introduction of ES6 classes. The React team discourages Mixins because it adds unnecessary complexity to a component, making it hard to maintain and reuse. The React team encouraged using higher-order components and Hooks instead.
I would argue that solid documentation can assist in minimizing the amount of confusion regarding the source of mixed-in functions. Still, as with every pattern, we should be okay if we take care during implementation.
Decorators are a structural design pattern that aims to promote code reuse. Like Mixins, you can think of them as another viable alternative to object subclassing.
Classically, decorators offered the ability to add behavior to existing classes in a system dynamically. The idea was that the decoration itself wasn’t essential to the base functionality of the class. Otherwise, we could bake it into the superclass itself.
We can use them to modify existing systems where we wish to add additional features to objects without heavily changing the underlying code that uses them. A common reason developers use them is that their applications may contain features requiring many distinct types of objects. Imagine defining hundreds of different object constructors for, say, a JavaScript game (see Figure 7-7).
The object constructors could represent distinct player types, each with differing capabilities. A Lord of the Rings game could require constructors for Hobbit
, Elf
,
Orc
, Wizard
, Mountain Giant
, Stone Giant
, and so on, but there could easily be hundreds of these. If we then factored in capabilities, imagine having to create subclasses for each combination of capability types, e.g., HobbitWithRing
, HobbitWithSword
,
HobbitWithRingAndSword
, and so on. This isn’t practical and certainly isn’t
manageable when we factor in an increasing number of different abilities.
The Decorator pattern isn’t heavily tied to how objects are created but instead focuses on the problem of extending their functionality. Rather than just relying on prototypal inheritance, we work with a single base class and progressively add decorator objects that provide additional capabilities. The idea is that rather than subclassing, we add (decorate) properties or methods to a base object, so it’s a little more streamlined.
We can use JavaScript classes to create the base classes that can be decorated. Adding new attributes or methods to object instances of the class in JavaScript is a straightforward process. With this in mind, we can implement a simplistic decorator, as shown in Examples 7-4 and 7-5.
// A vehicle constructor
class
Vehicle
{
constructor
(
vehicleType
)
{
// some sane defaults
this
.
vehicleType
=
vehicleType
||
'car'
;
this
.
model
=
'default'
;
this
.
license
=
'00000-000'
;
}
}
// Test instance for a basic vehicle
const
testInstance
=
new
Vehicle
(
'car'
);
console
.
log
(
testInstance
);
// Outputs:
// vehicle: car, model:default, license: 00000-000
// Let's create a new instance of vehicle, to be decorated
const
truck
=
new
Vehicle
(
'truck'
);
// New functionality we're decorating vehicle with
truck
.
setModel
=
function
(
modelName
)
{
this
.
model
=
modelName
;
};
truck
.
setColor
=
function
(
color
)
{
this
.
color
=
color
;
};
// Test the value setters and value assignment works correctly
truck
.
setModel
(
'CAT'
);
truck
.
setColor
(
'blue'
);
console
.
log
(
truck
);
// Outputs:
// vehicle:truck, model:CAT, color: blue
// Demonstrate "vehicle" is still unaltered
const
secondInstance
=
new
Vehicle
(
'car'
);
console
.
log
(
secondInstance
);
// Outputs:
// vehicle: car, model:default, license: 00000-000
Here, truck
is an instance of the class Vehicle
, and we also decorate it with additional methods setColor
and setModel
.
This type of simplistic implementation is functional, but it doesn’t demonstrate all the strengths that decorators offer. For this, we’re going to go through my variation of the Coffee example from an excellent book called Head First Design Patterns by Freeman et al., which is modeled around a MacBook purchase.
// The constructor to decorate
class
MacBook
{
constructor
()
{
this
.
cost
=
997
;
this
.
screenSize
=
11.6
;
}
getCost
()
{
return
this
.
cost
;
}
getScreenSize
()
{
return
this
.
screenSize
;
}
}
// Decorator 1
class
Memory
extends
MacBook
{
constructor
(
macBook
)
{
super
();
this
.
macBook
=
macBook
;
}
getCost
()
{
return
this
.
macBook
.
getCost
()
+
75
;
}
}
// Decorator 2
class
Engraving
extends
MacBook
{
constructor
(
macBook
)
{
super
();
this
.
macBook
=
macBook
;
}
getCost
()
{
return
this
.
macBook
.
getCost
()
+
200
;
}
}
// Decorator 3
class
Insurance
extends
MacBook
{
constructor
(
macBook
)
{
super
();
this
.
macBook
=
macBook
;
}
getCost
()
{
return
this
.
macBook
.
getCost
()
+
250
;
}
}
// init main object
let
mb
=
new
MacBook
();
// init decorators
mb
=
new
Memory
(
mb
);
mb
=
new
Engraving
(
mb
);
mb
=
new
Insurance
(
mb
);
// Outputs: 1522
console
.
log
(
mb
.
getCost
());
// Outputs: 11.6
console
.
log
(
mb
.
getScreenSize
());
In this example, our decorators are overriding the MacBook
superclass object’s .cost()
function to return the current price of the MacBook plus the cost of the upgrade.
It’s considered decoration because the original MacBook
objects constructor methods that are not overridden (e.g., screenSize()
), as well as any other properties we may define as a part of the MacBook
, remain unchanged and intact.
There isn’t a defined interface in the previous example. We’re shifting away from the responsibility of ensuring an object meets an interface when moving from the creator to the receiver.
We’re now going to examine a variation of the Decorator first presented in a JavaScript form in Pro JavaScript Design Patterns (PJDP) by Dustin Diaz and Ross Harmes.
Unlike some of the previous examples, Diaz and Harmes stick more closely to how decorators are implemented in other programming languages (such as Java or C++) using the concept of an “interface,” which we will define in more detail shortly.
This particular variation of the Decorator pattern is provided for reference purposes. If you find it overly complex, I recommend opting for one of the straightforward implementations covered earlier.
PJDP describes the Decorator pattern as one that is used to transparently wrap objects inside other objects of the same interface. An interface is a way of defining the methods an object should have. However, it doesn’t directly specify how you should implement those methods. Interfaces can also optionally indicate what parameters the methods take.
So, why would we use an interface in JavaScript? The idea is that they’re self-documenting and promote reusability. In theory, interfaces make code more stable by ensuring any change to the interface must also be propagated to the objects implementing them.
What follows is an example of an implementation of interfaces in JavaScript using duck-typing. This approach helps determine whether an object is an instance of a constructor/object based on the methods it implements:
// Create interfaces using a predefined Interface
// constructor that accepts an interface name and
// skeleton methods to expose.
// In our reminder example summary() and placeOrder()
// represent functionality the interface should
// support
const
reminder
=
new
Interface
(
'List'
,
[
'summary'
,
'placeOrder'
]);
const
properties
=
{
name
:
'Remember to buy the milk'
,
date
:
'05/06/2040'
,
actions
:
{
summary
()
{
return
'Remember to buy the milk, we are almost out!'
;
},
placeOrder
()
{
return
'Ordering milk from your local grocery store'
;
},
},
};
// Now create a constructor implementing these properties
// and methods
class
Todo
{
constructor
({
actions
,
name
})
{
// State the methods we expect to be supported
// as well as the Interface instance being checked
// against
Interface
.
ensureImplements
(
actions
,
reminder
);
this
.
name
=
name
;
this
.
methods
=
actions
;
}
}
// Create a new instance of our Todo constructor
const
todoItem
=
new
Todo
(
properties
);
// Finally test to make sure these function correctly
console
.
log
(
todoItem
.
methods
.
summary
());
console
.
log
(
todoItem
.
methods
.
placeOrder
());
// Outputs:
// Remember to buy the milk, we are almost out!
// Ordering milk from your local grocery store
Both classic JavaScript and ES2015+ do not support interfaces. However, we can create our Interface class. In the previous example,
Interface.ensureImplements
provides strict functionality checking, and you can find code for both this and the Interface
constructor.
The main concern with interfaces is that JavaScript does not have built-in support for them, which may lead to attempts at emulating features from other languages that might not be an ideal fit. However, you can utilize TypeScript if you really need interfaces, as it provides built-in support for them. Lightweight interfaces can be used without a significant performance cost in JavaScript, and we will explore abstract decorators using this same concept in the following section.
To demonstrate the structure of this version of the Decorator pattern, we’re going to imagine we have a superclass that models a MacBook
once again and a store that allows us to “decorate” our MacBook with a number of enhancements for an additional fee.
Enhancements can include upgrades to 4 GB or 8 GB of RAM (this can be much higher now, of course!), engraving, Parallels, or a case. Now, if we were to model this using an individual subclass for each combination of enhancement options, it might look something like this:
const
MacBook
=
class
{
//...
};
const
MacBookWith4GBRam
=
class
{};
const
MacBookWith8GBRam
=
class
{};
const
MacBookWith4GBRamAndEngraving
=
class
{};
const
MacBookWith8GBRamAndEngraving
=
class
{};
const
MacBookWith8GBRamAndParallels
=
class
{};
const
MacBookWith4GBRamAndParallels
=
class
{};
const
MacBookWith8GBRamAndParallelsAndCase
=
class
{};
const
MacBookWith4GBRamAndParallelsAndCase
=
class
{};
const
MacBookWith8GBRamAndParallelsAndCaseAndInsurance
=
class
{};
const
MacBookWith4GBRamAndParallelsAndCaseAndInsurance
=
class
{};
…and so on.
This solution would be impractical because a new subclass would be required for every possible combination of enhancements that are available. As we would prefer to keep things simple, without maintaining a large set of subclasses, let’s look at how we can use decorators to solve this problem better.
Rather than requiring all of the combinations we saw earlier, we will create only five new decorator classes. Methods called on these enhancement classes would be passed on to our MacBook
class.
In the following example, decorators transparently wrap around their components and can be interchanged as they use the same interface. Here’s the interface we’re going to define for the MacBook:
const
MacBook
=
new
Interface
(
'MacBook'
,
[
'addEngraving'
,
'addParallels'
,
'add4GBRam'
,
'add8GBRam'
,
'addCase'
,
]);
// A MacBook Pro might thus be represented as follows:
class
MacBookPro
{
// implements MacBook
}
// ES2015+: We still could use Object.prototype for adding new methods,
// because internally we use the same structure
MacBookPro
.
prototype
=
{
addEngraving
()
{},
addParallels
()
{},
add4GBRam
()
{},
add8GBRam
()
{},
addCase
()
{},
getPrice
()
{
// Base price
return
900.0
;
},
};
To make it easier for us to add many more options as needed later on, an abstract decorator class is defined with default methods required to implement the MacBook
interface, which the rest of the options will subclass. Abstract Decorators ensure that we can decorate a base class independently with as many decorators as needed in different combinations (remember the example earlier?) without needing to derive a class for every possible combination:
// MacBook decorator abstract decorator class
class
MacBookDecorator
{
constructor
(
macbook
)
{
Interface
.
ensureImplements
(
macbook
,
MacBook
);
this
.
macbook
=
macbook
;
}
addEngraving
()
{
return
this
.
macbook
.
addEngraving
();
}
addParallels
()
{
return
this
.
macbook
.
addParallels
();
}
add4GBRam
()
{
return
this
.
macbook
.
add4GBRam
();
}
add8GBRam
()
{
return
this
.
macbook
.
add8GBRam
();
}
addCase
()
{
return
this
.
macbook
.
addCase
();
}
getPrice
()
{
return
this
.
macbook
.
getPrice
();
}
}
In this sample, the MacBook
Decorator accepts an object (a MacBook
) to use as our base component. It uses the MacBook
interface we defined earlier, and each method is just calling the same method on the component. We can now create our option classes for what can be added by using the MacBook
Decorator:
// Let's now extend (decorate) the CaseDecorator
// with a MacBookDecorator
class
CaseDecorator
extends
MacBookDecorator
{
constructor
(
macbook
)
{
super
(
macbook
);
}
addCase
()
{
return
`
${
this
.
macbook
.
addCase
()
}
Adding case to macbook`
;
}
getPrice
()
{
return
this
.
macbook
.
getPrice
()
+
45.0
;
}
}
We are overriding the addCase()
and getPrice()
methods that we want to decorate, and we’re achieving this by first calling these methods on the original MacBook
and then simply appending a string or numeric value (e.g., 45.00
) to them accordingly.
As there’s been quite a lot of information presented in this section so far, let’s try to bring it all together in a single example that will hopefully highlight what we have learned:
// Instantiation of the macbook
const
myMacBookPro
=
new
MacBookPro
();
// Outputs: 900.00
console
.
log
(
myMacBookPro
.
getPrice
());
// Decorate the macbook
const
decoratedMacBookPro
=
new
CaseDecorator
(
myMacBookPro
);
// This will return 945.00
console
.
log
(
decoratedMacBookPro
.
getPrice
());
As decorators can modify objects dynamically, they’re a perfect pattern for changing existing systems. Occasionally, it’s just simpler to create decorators around an object instead of maintaining individual subclasses for each object type. This makes maintaining applications that may require many subclassed objects significantly more straightforward.
You can find a functional version of this example on JSBin.
Developers enjoy using this pattern as it can be used transparently and is somewhat flexible. As we’ve seen, objects can be wrapped or “decorated” with new behavior and continue to be used without worrying about the base object being modified. In a broader context, this pattern also avoids us needing to rely on large numbers of subclasses to get the same benefits.
There are, however, drawbacks that we should be aware of when implementing the pattern. It can significantly complicate our application architecture if poorly managed, as it introduces many small but similar objects into our namespace. The concern is that other developers unfamiliar with the pattern may have difficulty grasping why it’s being used, making it hard to manage.
Sufficient commenting or pattern research should assist with the latter. However, as long as we handle how widely we use the Decorator in our applications, we should be fine on both counts.
The Flyweight pattern is a classical structural solution for optimizing code that is repetitive, slow, and inefficiently shares data. It aims to minimize the use of memory in an application by sharing as much data as possible with related objects (e.g., application configuration, state, and so on—see Figure 7-8).
Paul Calder and Mark Linton first conceived the pattern in 1990 and named it after the boxing weight class that includes fighters weighing less than 112 lb. The name Flyweight is derived from this weight classification because it refers to the small weight (memory footprint) the pattern aims to help us achieve.
In practice, Flyweight data sharing can involve taking several similar objects or data constructs used by many objects and placing this data into a single external object. We can pass this object to those depending on this data rather than storing identical data across each one.
There are two ways in which you can apply the Flyweight pattern. The first is at the data layer, where we deal with the concept of sharing data between large quantities of similar objects stored in memory.
You can also apply the Flyweight at the DOM layer as a central event manager to avoid attaching event handlers to every child element in a parent container with similar behavior.
Traditionally, the Flyweight pattern has been used most at the data layer, so we’ll take a look at this first.
For this application, we need to be aware of a few more concepts around the classical Flyweight pattern. In the Flyweight pattern, there’s a concept of two states: intrinsic and extrinsic. Intrinsic information may be required by internal methods in our objects, without which they absolutely cannot function. Extrinsic information can, however, be removed and stored externally.
You can replace objects with the same intrinsic data with a single shared object created by a factory method. This allows us to reduce the overall quantity of implicit data being stored quite significantly.
The benefit is that we can keep an eye on objects that have already been instantiated so that new copies are only ever created if the intrinsic state differs from the object we already have.
We use a manager to handle the extrinsic states. You can implement this in various ways, but one approach is to have the manager object contain a central database of the extrinsic states and the flyweight objects to which they belong.
The Flyweight pattern hasn’t been heavily used in JavaScript recently, so many of the implementations we might use for inspiration come from the Java and C++ worlds.
Our first look at flyweights in code is my JavaScript implementation of the Java sample of the Flyweight pattern from Wikipedia.
This implementation makes use of three types of flyweight components:
Corresponds to an interface through which flyweights are able to receive and act on extrinsic states.
Actually implements the flyweight interface and stores the intrinsic states. Concrete flyweights need to be sharable and capable of manipulating the extrinsic state.
Manages flyweight objects and creates them too. It ensures that our flyweights are shared and manages them as a group of objects that can be queried if we require individual instances. If an object has already been created in the group, it returns it. Otherwise, it adds a new object to the pool and returns it.
These correspond to the following definitions in our implementation:
CoffeeOrder
: Flyweight
CoffeeFlavor
: Concrete flyweight
CoffeeOrderContext
: Helper
CoffeeFlavorFactory
: Flyweight factory
testFlyweight
: Utilization of our Flyweights
Duck punching allows us to extend the capabilities of a language or solution without necessarily needing to modify the runtime source. As this next solution requires a Java keyword (implements
) for implementing interfaces and isn’t found in JavaScript natively, let’s first duck-punch it.
Function.prototype.implementsFor
works on an object constructor and will accept a parent class (function) or object and either inherit from this using normal inheritance (for functions) or virtual inheritance (for objects):
// Utility to simulate implementation of an interface
class
InterfaceImplementation
{
static
implementsFor
(
superclassOrInterface
)
{
if
(
superclassOrInterface
instanceof
Function
)
{
this
.
prototype
=
Object
.
create
(
superclassOrInterface
.
prototype
);
this
.
prototype
.
constructor
=
this
;
this
.
prototype
.
parent
=
superclassOrInterface
.
prototype
;
}
else
{
this
.
prototype
=
Object
.
create
(
superclassOrInterface
);
this
.
prototype
.
constructor
=
this
;
this
.
prototype
.
parent
=
superclassOrInterface
;
}
return
this
;
}
}
We can use this to patch the lack of an implements
keyword by explicitly having a function inherit an interface. Next, CoffeeFlavor
implements the CoffeeOrder
interface and must contain its interface methods for us to assign the functionality powering these implementations to an object:
// CoffeeOrder interface
const
CoffeeOrder
=
{
serveCoffee
(
context
)
{},
getFlavor
()
{},
};
class
CoffeeFlavor
extends
InterfaceImplementation
{
constructor
(
newFlavor
)
{
super
();
this
.
flavor
=
newFlavor
;
}
getFlavor
()
{
return
this
.
flavor
;
}
serveCoffee
(
context
)
{
console
.
log
(
`Serving Coffee flavor
${
this
.
flavor
}
to
table
${
context
.
getTable
()
}
`
);
}
}
// Implement interface for CoffeeOrder
CoffeeFlavor
.
implementsFor
(
CoffeeOrder
);
const
CoffeeOrderContext
=
(
tableNumber
)
=>
({
getTable
()
{
return
tableNumber
;
},
});
class
CoffeeFlavorFactory
{
constructor
()
{
this
.
flavors
=
{};
this
.
length
=
0
;
}
getCoffeeFlavor
(
flavorName
)
{
let
flavor
=
this
.
flavors
[
flavorName
];
if
(
!
flavor
)
{
flavor
=
new
CoffeeFlavor
(
flavorName
);
this
.
flavors
[
flavorName
]
=
flavor
;
this
.
length
++
;
}
return
flavor
;
}
getTotalCoffeeFlavorsMade
()
{
return
this
.
length
;
}
}
// Sample usage:
const
testFlyweight
=
()
=>
{
const
flavors
=
[];
const
tables
=
[];
let
ordersMade
=
0
;
const
flavorFactory
=
new
CoffeeFlavorFactory
();
function
takeOrders
(
flavorIn
,
table
)
{
flavors
.
push
(
flavorFactory
.
getCoffeeFlavor
(
flavorIn
));
tables
.
push
(
CoffeeOrderContext
(
table
));
ordersMade
++
;
}
// Place orders
takeOrders
(
'Cappuccino'
,
2
);
// ...
// Serve orders
for
(
let
i
=
0
;
i
<
ordersMade
;
++
i
)
{
flavors
[
i
].
serveCoffee
(
tables
[
i
]);
}
console
.
log
(
' '
);
console
.
log
(
`total CoffeeFlavor objects made:
${
flavorFactory
.
getTotalCoffeeFlavorsMade
()
}
`
);
};
testFlyweight
();
Next, let’s continue our look at Flyweights by implementing a system to manage all books in a library. You could list the essential metadata for each book as follows:
ID
Title
Author
Genre
Page count
Publisher ID
ISBN
We’ll also require the following properties to track which member has checked out a particular book, the date they’ve checked it out, and the expected return date:
checkoutDate
checkoutMember
dueReturnDate
availability
We create a Book
class to represent each book as follows before any optimization using the Flyweight pattern. The constructor takes in all the properties related directly to the book and those required for tracking it:
class
Book
{
constructor
(
id
,
title
,
author
,
genre
,
pageCount
,
publisherID
,
ISBN
,
checkoutDate
,
checkoutMember
,
dueReturnDate
,
availability
)
{
this
.
id
=
id
;
this
.
title
=
title
;
this
.
author
=
author
;
this
.
genre
=
genre
;
this
.
pageCount
=
pageCount
;
this
.
publisherID
=
publisherID
;
this
.
ISBN
=
ISBN
;
this
.
checkoutDate
=
checkoutDate
;
this
.
checkoutMember
=
checkoutMember
;
this
.
dueReturnDate
=
dueReturnDate
;
this
.
availability
=
availability
;
}
getTitle
()
{
return
this
.
title
;
}
getAuthor
()
{
return
this
.
author
;
}
getISBN
()
{
return
this
.
ISBN
;
}
// For brevity, other getters are not shown
updateCheckoutStatus
(
bookID
,
newStatus
,
checkoutDate
,
checkoutMember
,
newReturnDate
)
{
this
.
id
=
bookID
;
this
.
availability
=
newStatus
;
this
.
checkoutDate
=
checkoutDate
;
this
.
checkoutMember
=
checkoutMember
;
this
.
dueReturnDate
=
newReturnDate
;
}
extendCheckoutPeriod
(
bookID
,
newReturnDate
)
{
this
.
id
=
bookID
;
this
.
dueReturnDate
=
newReturnDate
;
}
isPastDue
(
bookID
)
{
const
currentDate
=
new
Date
();
return
currentDate
.
getTime
()
>
Date
.
parse
(
this
.
dueReturnDate
);
}
}
This probably works fine initially for small collections of books. However, as the library expands to include a more extensive inventory with multiple versions and copies of each book, we may find the management system running more slowly. Using thousands of book objects may overwhelm the available memory, but we can optimize our system using the Flyweight pattern to improve this.
We can now separate our data into intrinsic and extrinsic states: data relevant to
the book object (title
, author
, etc.) is intrinsic, while the checkout data
(checkoutMember
, dueReturnDate
, etc.) is considered extrinsic. Effectively, this means that only one Book
object is required for each combination of book properties. It’s still a considerable number of objects, but significantly fewer than we had
previously.
An instance of the following book metadata combination will be created for all required copies of the book object with a particular title/ISBN:
// Flyweight optimized version
class
Book
{
constructor
({
title
,
author
,
genre
,
pageCount
,
publisherID
,
ISBN
})
{
this
.
title
=
title
;
this
.
author
=
author
;
this
.
genre
=
genre
;
this
.
pageCount
=
pageCount
;
this
.
publisherID
=
publisherID
;
this
.
ISBN
=
ISBN
;
}
}
As we can see, the extrinsic states have been removed. Everything to do with library checkouts will be moved to a manager, and as the object data is now segmented, we can use a factory for instantiation.
Let’s now define a very basic factory. We will have it check if a book with a particular title has been previously created inside the system—if it has, we’ll return it; if not, a new book will be created and stored so that it can be accessed later. This ensures that we create only a single copy of each unique intrinsic piece of data:
// Book Factory Singleton
const
existingBooks
=
{};
class
BookFactory
{
createBook
({
title
,
author
,
genre
,
pageCount
,
publisherID
,
ISBN
})
{
// Find if a particular book + metadata combination already exists
// !! or (bang bang) forces a boolean to be returned
const
existingBook
=
existingBooks
[
ISBN
];
if
(
!!
existingBook
)
{
return
existingBook
;
}
else
{
// if not, let's create a new instance of the book and store it
const
book
=
new
Book
({
title
,
author
,
genre
,
pageCount
,
publisherID
,
ISBN
});
existingBooks
[
ISBN
]
=
book
;
return
book
;
}
}
}
Next, we need to store the states that were removed from the Book
objects
somewhere—luckily, a manager (which we’ll be defining as a Singleton) can be used to encapsulate them. Combinations of a Book
object and the library member who’s checked it out will be called Book
record. Our manager will be storing both and will include checkout-related logic we stripped out during our Flyweight optimization of the Book
class:
// BookRecordManager Singleton
const
bookRecordDatabase
=
{};
class
BookRecordManager
{
// add a new book into the library system
addBookRecord
({
id
,
title
,
author
,
genre
,
pageCount
,
publisherID
,
ISBN
,
checkoutDate
,
checkoutMember
,
dueReturnDate
,
availability
})
{
const
bookFactory
=
new
BookFactory
();
const
book
=
bookFactory
.
createBook
({
title
,
author
,
genre
,
pageCount
,
publisherID
,
ISBN
});
bookRecordDatabase
[
id
]
=
{
checkoutMember
,
checkoutDate
,
dueReturnDate
,
availability
,
book
,
};
}
updateCheckoutStatus
({
bookID
,
newStatus
,
checkoutDate
,
checkoutMember
,
newReturnDate
})
{
const
record
=
bookRecordDatabase
[
bookID
];
record
.
availability
=
newStatus
;
record
.
checkoutDate
=
checkoutDate
;
record
.
checkoutMember
=
checkoutMember
;
record
.
dueReturnDate
=
newReturnDate
;
}
extendCheckoutPeriod
(
bookID
,
newReturnDate
)
{
bookRecordDatabase
[
bookID
].
dueReturnDate
=
newReturnDate
;
}
isPastDue
(
bookID
)
{
const
currentDate
=
new
Date
();
return
currentDate
.
getTime
()
>
Date
.
parse
(
bookRecordDatabase
[
bookID
].
dueReturnDate
);
}
}
The result of these changes is that all of the data extracted from the Book
class is now being stored in an attribute of the BookManager
Singleton (BookDatabase
)—something considerably more efficient than the large number of objects we were previously using. Methods related to book checkouts are also now based here as they deal with extrinsic rather than intrinsic data.
This process does add a little complexity to our final solution. However, it’s a minor concern compared to the performance issues that we have tackled. Data-wise, if we have 30 copies of the same book, we are now storing it only once. Also, every function takes up memory. With the Flyweight pattern, these functions exist in one place (on the manager) and not on every object, thus saving on memory use. For the unoptimized version of Flyweight mentioned previously, we store just a link to the function object as we used the Book
constructor’s prototype. Still, if we implemented it another way, functions would be created for every book instance.
The DOM supports two approaches that allow objects to detect events—either top-down (event capture) or bottom-up (event bubbling).
In event capture, the event is first captured by the outer-most element and propagated to the inner-most element. In event bubbling, the event is captured and given to the inner-most element and then propagated to the outer elements.
Gary Chisholm wrote one of the best metaphors for describing Flyweights in this context, and it goes a little like this:
Try to think of the flyweight in terms of a pond. A fish opens its mouth (the event), bubbles rise to the surface (the bubbling), a fly sitting on the top flies away when the bubble reaches the surface (the action). In this example we can easily transpose the fish opening its mouth to a button being clicked, the bubbles as the bubbling effect, and the fly flying away to some function being run.
Bubbling was introduced to handle situations in which a single event (e.g., a click
) may be handled by multiple event handlers defined at different levels of the DOM hierarchy. Where this happens, event bubbling executes event handlers defined for specific elements at the lowest level possible. From there on, the event bubbles up to containing elements before going to those even higher up.
Flyweights can be used to further tweak the event-bubbling process, as we will see in “Example: Centralized Event Handling”.
For our first practical example, imagine we have several similar elements in a document with similar behavior executed when a user action (e.g., click, mouse-over) is performed against them.
Usually, when constructing our accordion component, menu, or other list-based widgets, we bind a click
event to each link element in the parent container (e.g.,
$('ul li a').on(…)
). Instead of binding the click to multiple elements, we can easily attach a Flyweight to the top of our container, which can listen for events coming from below. These can then be handled using logic as simple or complex as required.
As the types of components mentioned often have the same repeating markup for each section (e.g., each section of an accordion), there’s a good chance the behavior of each element clicked will be pretty similar and relative to similar classes nearby. We’ll use this information to construct a basic accordion using the Flyweight in Example 7-6.
A stateManager
namespace is used here to encapsulate our Flyweight logic, while jQuery is used to bind the initial click to a container div
. An unbind
event is first applied to ensure that no other logic on the page attaches similar handles to the
container.
To establish exactly what child element in the container is clicked, we use a target
check, which provides a reference to the element that was clicked, regardless of its parent. We then use this information to handle the click
event without actually needing to bind the event to specific children when our page loads.
<
div
id
=
"container"
>
<
div
class
=
"toggle"
>
More Info (Address)<
span
class
=
"info"
>
This is more information</
span
>
</
div
>
<
div
class
=
"toggle"
>
Even More Info (Map)<
span
class
=
"info"
>
<
iframe
src
=
"MAPS_URL"
></
iframe
>
</
span
>
</
div
>
</
div
>
<
script
>
(
function
()
{
const
stateManager
=
{
fly
()
{
const
self
=
this
;
$
(
'#container'
)
.
off
()
.
on
(
'click'
,
'div.toggle'
,
function
()
{
self
.
handleClick
(
this
);
});
},
handleClick
(
elem
)
{
$
(
elem
)
.
find
(
'span'
)
.
toggle
(
'slow'
);
},
};
// Initialize event listeners
stateManager
.
fly
();
})();
<
/script>
The benefit here is that we’re converting many independent actions into shared ones (potentially saving on memory).
The Observer pattern allows you to notify one object when another object changes without requiring the object to know about its dependents. Often this is a pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to its state. In modern frameworks, the Observer pattern is used to inform components of state changes. Figure 7-9 illustrates this.
When a subject needs to notify observers about something interesting happening, it broadcasts a notification to the observers (which can include specific data related to the topic). When an observer no longer wishes to be notified of changes by the subject, they can be removed from the list of observers.
It’s helpful to refer back to published definitions of design patterns that are language agnostic to get a broader sense of their usage and advantages over time. The definition of the Observer pattern provided in the GoF book, Design Patterns: Elements of Reusable Object-Oriented Software, is:
One or more observers are interested in the state of a subject and register their interest with the subject by attaching themselves. When something changes in our subject that the observer may be interested in, a notify message is sent which calls the update method in each observer. When the observer is no longer interested in the subject’s state, they can simply detach themselves.
We can now expand on what we’ve learned to implement the Observer pattern with the following components:
Maintains a list of observers, facilitates adding or removing observers
Provides an update
interface for objects that need to be notified of a Subject’s changes in state
ConcreteSubject
Broadcasts notifications to observers on changes of state, stores the state of
ConcreteObservers
ConcreteObserver
Stores a reference to the ConcreteSubject
, implements an update
interface for the observer to ensure the state is consistent with the Subject’s
ES2015+ allows us to implement the Observer pattern using JavaScript classes for observers and subjects with methods for notify
and update
.
First, let’s model the list of dependent observers a subject may have using the
ObserverList
class:
class
ObserverList
{
constructor
()
{
this
.
observerList
=
[];
}
add
(
obj
)
{
return
this
.
observerList
.
push
(
obj
);
}
count
()
{
return
this
.
observerList
.
length
;
}
get
(
index
)
{
if
(
index
>
-
1
&&
index
<
this
.
observerList
.
length
)
{
return
this
.
observerList
[
index
];
}
}
indexOf
(
obj
,
startIndex
)
{
let
i
=
startIndex
;
while
(
i
<
this
.
observerList
.
length
)
{
if
(
this
.
observerList
[
i
]
===
obj
)
{
return
i
;
}
i
++
;
}
return
-
1
;
}
removeAt
(
index
)
{
this
.
observerList
.
splice
(
index
,
1
);
}
}
Next, let’s model the Subject
class that can add, remove, or notify observers on the observer list:
class
Subject
{
constructor
()
{
this
.
observers
=
new
ObserverList
();
}
addObserver
(
observer
)
{
this
.
observers
.
add
(
observer
);
}
removeObserver
(
observer
)
{
this
.
observers
.
removeAt
(
this
.
observers
.
indexOf
(
observer
,
0
));
}
notify
(
context
)
{
const
observerCount
=
this
.
observers
.
count
();
for
(
let
i
=
0
;
i
<
observerCount
;
i
++
)
{
this
.
observers
.
get
(
i
).
update
(
context
);
}
}
}
We then define a skeleton for creating new observers. We will overwrite the Update
functionality here later with custom behavior:
// The Observer
class
Observer
{
constructor
()
{}
update
()
{
// ...
}
}
In our sample application using the previous observer components, we now define:
A button for adding new observable checkboxes to the page.
A control checkbox will act as a subject, notifying other checkboxes that they should update to the checked state.
A container for the new checkboxes added.
We then define ConcreteSubject
and ConcreteObserver
handlers to add new observers to the page and implement the updating interface. For this, we use inheritance to extend our subject and observer classes. The ConcreteSubject
class encapsulates a checkbox and generates a notification when the main checkbox is clicked. ConcreteObserver
encapsulates each of the observing checkboxes and implements the Update
interface by changing the checked value of the checkboxes. What follows are the inline comments on how these work together in the context of our example.
Here is the HTML code:
<
button
id
=
"addNewObserver"
>
Add New Observer checkbox</
button
>
<
input
id
=
"mainCheckbox"
type
=
"checkbox"
/>
<
div
id
=
"observersContainer"
></
div
>
// References to our DOM elements
// Concrete Subject
class
ConcreteSubject
extends
Subject
{
constructor
(
element
)
{
// Call the constructor of the super class.
super
();
this
.
element
=
element
;
// Clicking the checkbox will trigger notifications to its observers
this
.
element
.
onclick
=
()
=>
{
this
.
notify
(
this
.
element
.
checked
);
};
}
}
// Concrete Observer
class
ConcreteObserver
extends
Observer
{
constructor
(
element
)
{
super
();
this
.
element
=
element
;
}
// Override with custom update behavior
update
(
value
)
{
this
.
element
.
checked
=
value
;
}
}
// References to our DOM elements
const
addBtn
=
document
.
getElementById
(
'addNewObserver'
);
const
container
=
document
.
getElementById
(
'observersContainer'
);
const
controlCheckbox
=
new
ConcreteSubject
(
document
.
getElementById
(
'mainCheckbox'
)
);
const
addNewObserver
=
()
=>
{
// Create a new checkbox to be added
const
check
=
document
.
createElement
(
'input'
);
check
.
type
=
'checkbox'
;
const
checkObserver
=
new
ConcreteObserver
(
check
);
// Add the new observer to our list of observers
// for our main subject
controlCheckbox
.
addObserver
(
checkObserver
);
// Append the item to the container
container
.
appendChild
(
check
);
};
addBtn
.
onclick
=
addNewObserver
;
}
In this example, we looked at how to implement and utilize the Observer pattern, covering the concepts of a Subject, Observer, ConcreteSubject
, and
ConcreteObserver
.
While it’s helpful to be aware of the Observer pattern, quite often in the JavaScript world, we’ll find it commonly implemented using a variation known as the Publish/Subscribe pattern. Although the two patterns are pretty similar, there are differences worth noting.
The Observer pattern requires that the observer (or object) wishing to receive topic notifications must subscribe this interest to the object firing the event (the subject), as seen in Figure 7-10.
The Publish/Subscribe pattern, however, uses a topic/event channel that sits between the objects wishing to receive notifications (subscribers) and the object firing the event (the publisher). This event system allows code to define application-specific events, which can pass custom arguments containing values needed by the subscriber. The idea here is to avoid dependencies between the subscriber and publisher.
This differs from the Observer pattern because it allows any subscriber implementing an appropriate event handler to register for and receive topic notifications broadcast by the publisher.
Here is an example of how one might use the Publish/Subscribe pattern if
provided with a functional implementation powering publish()
, subscribe()
, and
unsubscribe()
behind the scenes:
<!--
Add
this
HTML
to
your
page
-->
<
div
class
=
"messageSender"
><
/div>
<
div
class
=
"messagePreview"
><
/div>
<
div
class
=
"newMessageCounter"
><
/div>
// A simple Publish/Subscribe implementation
const
events
=
(
function
()
{
const
topics
=
{};
const
hOP
=
topics
.
hasOwnProperty
;
return
{
subscribe
:
function
(
topic
,
listener
)
{
if
(
!
hOP
.
call
(
topics
,
topic
))
topics
[
topic
]
=
[];
const
index
=
topics
[
topic
].
push
(
listener
)
-
1
;
return
{
remove
:
function
()
{
delete
topics
[
topic
][
index
];
},
};
},
publish
:
function
(
topic
,
info
)
{
if
(
!
hOP
.
call
(
topics
,
topic
))
return
;
topics
[
topic
].
forEach
(
function
(
item
)
{
item
(
info
!==
undefined
?
info
:
{});
});
},
};
})();
// A very simple new mail handler
// A count of the number of messages received
let
mailCounter
=
0
;
// Initialize subscribers that will listen out for a topic
// with the name "inbox/newMessage".
// Render a preview of new messages
const
subscriber1
=
events
.
subscribe
(
'inbox/newMessage'
,
(
data
)
=>
{
// Log the topic for debugging purposes
console
.
log
(
'A new message was received:'
,
data
);
// Use the data that was passed from our subject
// to display a message preview to the user
document
.
querySelector
(
'.messageSender'
).
innerHTML
=
data
.
sender
;
document
.
querySelector
(
'.messagePreview'
).
innerHTML
=
data
.
body
;
});
// Here's another subscriber using the same data to perform
// a different task.
// Update the counter displaying the number of new
// messages received via the publisher
const
subscriber2
=
events
.
subscribe
(
'inbox/newMessage'
,
(
data
)
=>
{
document
.
querySelector
(
'.newMessageCounter'
).
innerHTML
=
++
mailCounter
;
});
events
.
publish
(
'inbox/newMessage'
,
{
sender
:
'hello@google.com'
,
body
:
'Hey there! How are you doing today?'
,
});
// We could then at a later point unsubscribe our subscribers
// from receiving any new topic notifications as follows:
// subscriber1.remove();
// subscriber2.remove();
The general idea here is the promotion of loose coupling. Rather than single objects calling on the methods of other objects directly, they instead subscribe to a specific task or activity of another object and are notified when it occurs.
The Observer and Publish/Subscribe patterns encourage us to think hard about the relationships between different application parts. They also help us identify layers containing direct relationships that could be replaced with sets of subjects and observers. This effectively could be used to break down an application into smaller, more loosely coupled blocks to improve code management and potential for reuse.
Further motivation for using the Observer pattern is in situations where we need to maintain consistency between related objects without making classes tightly coupled. For example, when an object needs to be able to notify other objects without making assumptions regarding those objects.
Dynamic relationships can exist between observers and subjects when using either pattern. This provides excellent flexibility that may not be as easy to implement when disparate parts of our application are tightly coupled.
While it may not always be the best solution to every problem, these patterns remain one of the best tools for designing decoupled systems and should be considered an essential tool in any JavaScript developer’s utility belt.
Consequently, some of the issues with these patterns actually stem from their main benefits. In Publish/Subscribe, by decoupling publishers from subscribers, it can sometimes become difficult to obtain guarantees that particular parts of our applications are functioning as we may expect.
For example, publishers may assume that one or more subscribers are listening to them. Say that we’re using such an assumption to log or output errors regarding some application process. If the subscriber performing the logging crashes (or for some reason fails to function), the publisher won’t have a way of seeing this due to the decoupled nature of the system.
Another drawback of the pattern is that subscribers are entirely ignorant of the existence of each other and are blind to the cost of switching publishers. Due to the dynamic relationship between subscribers and publishers, it can be difficult to track an update dependency.
Publish/Subscribe fits in very well in JavaScript ecosystems, primarily because, at the core, ECMAScript implementations are event-driven. This is particularly true in browser environments, as the DOM uses events as its main interaction API for scripting.
That said, neither ECMAScript nor DOM provides core objects or methods for creating custom event systems in implementation code (except for perhaps the DOM3
CustomEvent
, which is bound to the DOM and is thus not generically applicable).
To better appreciate how many of the ordinary JavaScript implementations of the Observer pattern might work, let’s take a walkthrough of a minimalist version of Publish/Subscribe I released on GitHub under a project called “pubsubz”. This demonstrates the core concepts of subscribe and publish, and the idea of unsubscribing.
I’ve opted to base our examples on this code as it sticks closely to the method signatures and implementation approach I expect to see in a JavaScript version of the classic Observer pattern:
class
PubSub
{
constructor
()
{
// Storage for topics that can be broadcast
// or listened to
this
.
topics
=
{};
// A topic identifier
this
.
subUid
=
-
1
;
}
publish
(
topic
,
args
)
{
if
(
!
this
.
topics
[
topic
])
{
return
false
;
}
const
subscribers
=
this
.
topics
[
topic
];
let
len
=
subscribers
?
subscribers
.
length
:
0
;
while
(
len
--
)
{
subscribers
[
len
].
func
(
topic
,
args
);
}
return
this
;
}
subscribe
(
topic
,
func
)
{
if
(
!
this
.
topics
[
topic
])
{
this
.
topics
[
topic
]
=
[];
}
const
token
=
(
++
this
.
subUid
).
toString
();
this
.
topics
[
topic
].
push
({
token
,
func
,
});
return
token
;
}
unsubscribe
(
token
)
{
for
(
const
m
in
this
.
topics
)
{
if
(
this
.
topics
[
m
])
{
for
(
let
i
=
0
,
j
=
this
.
topics
[
m
].
length
;
i
<
j
;
i
++
)
{
if
(
this
.
topics
[
m
][
i
].
token
===
token
)
{
this
.
topics
[
m
].
splice
(
i
,
1
);
return
token
;
}
}
}
}
return
this
;
}
}
const
pubsub
=
new
PubSub
();
pubsub
.
publish
(
'/addFavorite'
,
[
'test'
]);
pubsub
.
subscribe
(
'/addFavorite'
,
(
topic
,
args
)
=>
{
console
.
log
(
'test'
,
topic
,
args
);
});
Here we have defined a basic PubSub
class that contains:
A list of topics with subscribers who have subscribed to it.
The Subscribe
method creates a new subscriber to a topic using the function to be called when publishing a topic and a unique token.
The Unsubscribe
method removes a subscriber from the list based on the token
value passed. The Publish
method publishes content on a given topic to all its subscribers by calling the registered
function.
We can now use the implementation to publish and subscribe to events of interest, as shown in Example 7-7.
// Another simple message handler
// A simple message logger that logs any topics and data received through our
// subscriber
const
messageLogger
=
(
topics
,
data
)
=>
{
console
.
log
(
`Logging:
${
topics
}
:
${
data
}
`
);
};
// Subscribers listen for topics they have subscribed to and
// invoke a callback function (e.g., messageLogger) once a new
// notification is broadcast on that topic
const
subscription
=
pubsub
.
subscribe
(
'inbox/newMessage'
,
messageLogger
);
// Publishers are in charge of publishing topics or notifications of
// interest to the application. e.g.:
pubsub
.
publish
(
'inbox/newMessage'
,
'hello world!'
);
// or
pubsub
.
publish
(
'inbox/newMessage'
,
[
'test'
,
'a'
,
'b'
,
'c'
]);
// or
pubsub
.
publish
(
'inbox/newMessage'
,
{
sender
:
'hello@google.com'
,
body
:
'Hey again!'
,
});
// We can also unsubscribe if we no longer wish for our subscribers
// to be notified
pubsub
.
unsubscribe
(
subscription
);
// Once unsubscribed, this for example won't result in our
// messageLogger being executed as the subscriber is
// no longer listening
pubsub
.
publish
(
'inbox/newMessage'
,
'Hello! are you still there?'
);
Next, let’s imagine we have a web application responsible for displaying real-time stock information.
The application might have a grid displaying the stock stats and a counter indicating the last update point. The application must update the grid and counter when the data model changes. In this scenario, our subject (which will be publishing topics/notifications) is the data model, and our subscribers are the grid and counter.
When our subscribers are notified that the model has changed, they can update themselves accordingly.
In our implementation, our subscriber will listen to the topic newDataAvailable
to find out if new stock information is available. If a new notification is published to this topic, it will trigger gridUpdate
to add a new row to our grid containing this information. It will also update the last updated counter to log the last time that data was added
(Example 7-8).
// Return the current local time to be used in our UI later
getCurrentTime
=
()
=>
{
const
date
=
new
Date
();
const
m
=
date
.
getMonth
()
+
1
;
const
d
=
date
.
getDate
();
const
y
=
date
.
getFullYear
();
const
t
=
date
.
toLocaleTimeString
().
toLowerCase
();
return
`
${
m
}
/
${
d
}
/
${
y
}
${
t
}
`
;
};
// Add a new row of data to our fictional grid component
const
addGridRow
=
data
=>
{
// ui.grid.addRow( data );
console
.
log
(
`updated grid component with:
${
data
}
`
);
};
// Update our fictional grid to show the time it was last
// updated
const
updateCounter
=
data
=>
{
// ui.grid.updateLastChanged( getCurrentTime() );
console
.
log
(
`data last updated at:
${
getCurrentTime
()
}
with
${
data
}
`
);
};
// Update the grid using the data passed to our subscribers
const
gridUpdate
=
(
topic
,
data
)
=>
{
if
(
data
!==
undefined
)
{
addGridRow
(
data
);
updateCounter
(
data
);
}
};
// Create a subscription to the newDataAvailable topic
const
subscriber
=
pubsub
.
subscribe
(
'newDataAvailable'
,
gridUpdate
);
// The following represents updates to our data layer. This could be
// powered by ajax requests that broadcast that new data is available
// to the rest of the application.
// Publish changes to the gridUpdated topic representing new entries
pubsub
.
publish
(
'newDataAvailable'
,
{
summary
:
'Apple made $5 billion'
,
identifier
:
'APPL'
,
stockPrice
:
570.91
,
});
pubsub
.
publish
(
'newDataAvailable'
,
{
summary
:
'Microsoft made $20 million'
,
identifier
:
'MSFT'
,
stockPrice
:
30.85
,
});
In the following movie rating example, we’ll use Ben Alman’s jQuery implementation of Publish/Subscribe to demonstrate how we can decouple a UI. Notice how submitting a rating only has the effect of publishing the fact that new user and rating data is available.
It’s left up to the subscribers to those topics to delegate what happens with that data. In our case, we’re pushing that new data into existing arrays and then rendering them using the Lodash library’s .template()
method for templating.
Example 7-9 has the HTML/Templates code.
<
script
id
=
"userTemplate"
type
=
"text/html"
>
<
li
><%-
name
%><
/li>
</
script
>
<
script
id
=
"ratingsTemplate"
type
=
"text/html"
>
<
li
><
strong
><%-
title
%><
/strong> was rated <%- rating %>/5</li>
</
script
>
<
div
id
=
"container"
>
<
div
class
=
"sampleForm"
>
<
p
>
<
label
for
=
"twitter_handle"
>
Twitter handle:</
label
>
<
input
type
=
"text"
id
=
"twitter_handle"
/>
</
p
>
<
p
>
<
label
for
=
"movie_seen"
>
Name a movie you've seen this year:</
label
>
<
input
type
=
"text"
id
=
"movie_seen"
/>
</
p
>
<
p
>
<
label
for
=
"movie_rating"
>
Rate the movie you saw:</
label
>
<
select
id
=
"movie_rating"
>
<
option
value
=
"1"
>
1</
option
>
<
option
value
=
"2"
>
2</
option
>
<
option
value
=
"3"
>
3</
option
>
<
option
value
=
"4"
>
4</
option
>
<
option
value
=
"5"
selected
>
5</
option
>
</
select
>
</
p
>
<
p
>
<
button
id
=
"add"
>
Submit rating</
button
>
</
p
>
</
div
>
<
div
class
=
"summaryTable"
>
<
div
id
=
"users"
><
h3
>
Recent users</
h3
></
div
>
<
div
id
=
"ratings"
><
h3
>
Recent movies rated</
h3
></
div
>
</
div
>
</
div
>
The JavaScript code is in Example 7-10.
;(
$
=>
{
// Pre-compile templates and "cache" them using closure
const
userTemplate
=
_
.
template
(
$
(
'#userTemplate'
).
html
());
const
ratingsTemplate
=
_
.
template
(
$
(
'#ratingsTemplate'
).
html
());
// Subscribe to the new user topic, which adds a user
// to a list of users who have submitted reviews
$
.
subscribe
(
'/new/user'
,
(
e
,
data
)
=>
{
if
(
data
)
{
$
(
'#users'
).
append
(
userTemplate
(
data
));
}
});
// Subscribe to the new rating topic. This is composed of a title and
// rating. New ratings are appended to a running list of added user
// ratings.
$
.
subscribe
(
'/new/rating'
,
(
e
,
data
)
=>
{
if
(
data
)
{
$
(
'#ratings'
).
append
(
ratingsTemplate
(
data
));
}
});
// Handler for adding a new user
$
(
'#add'
).
on
(
'click'
,
e
=>
{
e
.
preventDefault
();
const
strUser
=
$
(
'#twitter_handle'
).
val
();
const
strMovie
=
$
(
'#movie_seen'
).
val
();
const
strRating
=
$
(
'#movie_rating'
).
val
();
// Inform the application a new user is available
$
.
publish
(
'/new/user'
,
{
name
:
strUser
,
});
// Inform the app a new rating is available
$
.
publish
(
'/new/rating'
,
{
title
:
strMovie
,
rating
:
strRating
,
});
});
})(
jQuery
);
In our final example, we’ll take a practical look at how decoupling our code using Pub/Sub early in the development process can save us some potentially painful refactoring later.
Often in Ajax-heavy applications, we want to achieve more than just one unique action once we’ve received a response to a request. We could add all of the post-request logic into a success callback, but there are drawbacks to this approach.
Highly coupled applications sometimes increase the effort required to reuse functionality due to the increased interfunction/code dependency. Keeping our post-request logic hardcoded in a callback might be okay if we just try to grab a result set once. However, it’s not as appropriate when we want to make further Ajax calls to the same data source (and different end behavior) without rewriting parts of the code multiple times. Rather than going back through each layer that calls the same data source and generalizing them later on, we can use Pub/Sub from the start and save time.
Using observers, we can also easily separate application-wide notifications regarding different events down to whatever level of granularity we’re comfortable with—something that can be less elegantly done using other patterns.
Notice how in our upcoming sample, one topic notification is made when a user indicates that he wants to make a search query. Another is made when the request returns and actual data is available for consumption. It’s left up to the subscribers to then decide how to use knowledge of these events (or the data returned). The benefits of this are that, if we wanted, we could have 10 different subscribers using the data returned in different ways, but as far as the Ajax layer is concerned, it doesn’t care. Its sole duty is to request and return data and then pass it on to whoever wants to use it. This separation of concerns can make the overall design of our code a little cleaner.
The HTML/Templates code is shown in Example 7-11.
<
form
id
=
"flickrSearch"
>
<
input
type
=
"text"
name
=
"tag"
id
=
"query"
/>
<
input
type
=
"submit"
name
=
"submit"
value
=
"submit"
/>
</
form
>
<
div
id
=
"lastQuery"
></
div
>
<
ol
id
=
"searchResults"
></
ol
>
<
script
id
=
"resultTemplate"
type
=
"text/html"
>
<%
_
.
each
(
items
,
function
(
item
){
%>
<
li
><
img
src
=
"<%= item.media.m %>"
/><
/li>
<%
});
%>
</
script
>
Example 7-12 shows the JavaScript code.
(
$
=>
{
// Pre-compile template and "cache" it using closure
const
resultTemplate
=
_
.
template
(
$
(
'#resultTemplate'
).
html
());
// Subscribe to the new search tags topic
$
.
subscribe
(
'/search/tags'
,
(
e
,
tags
)
=>
{
$
(
'#lastQuery'
).
html
(
`Searched for:
${
tags
}
`
);
});
// Subscribe to the new results topic
$
.
subscribe
(
'/search/resultSet'
,
(
e
,
results
)
=>
{
$
(
'#searchResults'
)
.
empty
()
.
append
(
resultTemplate
(
results
));
});
// Submit a search query and publish tags on the /search/tags topic
$
(
'#flickrSearch'
).
submit
(
function
(
e
)
{
e
.
preventDefault
();
const
tags
=
$
(
this
)
.
find
(
'#query'
)
.
val
();
if
(
!
tags
)
{
return
;
}
$
.
publish
(
'/search/tags'
,
[
$
.
trim
(
tags
)]);
});
// Subscribe to new tags being published and perform a search query
// using them. Once data has returned publish this data for the rest
// of the application to consume. We used the destructuring assignment
// syntax that makes it possible to unpack values from data structures
// into distinct variables.
$
.
subscribe
(
'/search/tags'
,
(
e
,
tags
)
=>
{
$
.
getJSON
(
'http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?'
,
{
tags
,
tagmode
:
'any'
,
format
:
'json'
,
},
// The destructuring assignment as function parameter
({
items
})
=>
{
if
(
!
items
.
length
)
{
return
;
}
//shorthand property names in object creation,
// if variable name equal to object key
$
.
publish
(
'/search/resultSet'
,
{
items
});
}
);
});
})(
jQuery
);
A popular library that uses the observable pattern is RxJS. The RxJS documentation states that:
ReactiveX combines the Observer pattern with the Iterator pattern and functional programming with collections to fill the need for an ideal way of managing sequences of events.
With RxJS, we can create observers and subscribe to certain events! Let’s look at an example from their documentation, which logs whether a user was dragging in the document:
import
ReactDOM
from
"react-dom"
;
import
{
fromEvent
,
merge
}
from
"rxjs"
;
import
{
sample
,
mapTo
}
from
"rxjs/operators"
;
import
"./styles.css"
;
merge
(
fromEvent
(
document
,
"mousedown"
).
pipe
(
mapTo
(
false
)),
fromEvent
(
document
,
"mousemove"
).
pipe
(
mapTo
(
true
))
)
.
pipe
(
sample
(
fromEvent
(
document
,
"mouseup"
)))
.
subscribe
(
isDragging
=>
{
console
.
log
(
"Were you dragging?"
,
isDragging
);
});
ReactDOM
.
render
(
<
div
className
=
"App"
>
Click
or
drag
anywhere
and
check
the
console
!<
/div>,
document
.
getElementById
(
"root"
)
);
The Observer pattern helps decouple several different scenarios in application design. If you haven’t been using it, I recommend picking up one of the prewritten implementations mentioned here and giving it a try. It’s one of the easier design patterns to get started with but also one of the most powerful.
The Mediator pattern is a design pattern that allows one object to notify a set of other objects when an event occurs. The difference between the Mediator and Observer patterns is that the Mediator pattern allows one object to be notified of events that occur in other objects. In contrast, the Observer pattern allows one object to subscribe to multiple events that occur in other objects.
In the section on the Observer pattern, we discussed a way of channeling multiple event sources through a single object. This is also known as Publish/Subscribe or Event Aggregation. It’s common for developers to think of mediators when faced with this problem, so let’s explore how they differ.
The dictionary refers to a mediator as a neutral party that assists in negotiations and conflict resolution.1 In our world, a mediator is a behavioral design pattern that allows us to expose a unified interface through which the different parts of a system may communicate.
If it appears that a system has too many direct relationships between components, it may be time to have a central point of control that components communicate through instead. The mediator promotes loose coupling by ensuring that interactions between components are managed centrally instead of having components refer to each other explicitly. This can help us decouple systems and improve the potential for component reusability.
A real-world analogy could be a typical airport traffic control system. A tower (mediator) handles what planes can take off and land because all communications (notifications being listened out for or broadcast) take place from the aircraft to the control tower rather than from plane to plane. A centralized controller is key to the success of this system, and that’s the role a mediator plays in software design (Figure 7-11).
Another analogy would be DOM event bubbling and event delegation. If all subscriptions in a system are made against the document rather than individual nodes, the document effectively serves as a Mediator. Instead of binding to the events of the individual nodes, a higher-level object is given the responsibility of notifying subscribers about interaction events.
When it comes to the Mediator and Event Aggregator patterns, sometimes it may look like the patterns are interchangeable due to implementation similarities. However, the semantics and intent of these patterns are very different.
And even if the implementations both use some of the same core constructs, I believe there is a distinct difference between them. They should not be interchanged or confused in communication because of their differences.
A mediator is an object that coordinates interactions (logic and behavior) between multiple objects. It decides when to call which objects based on the actions (or inaction) of other objects and input.
You can write a mediator using a single line of code:
const
mediator
=
{};
Yes, of course, this is just an object literal in JavaScript. Once again, we’re talking about semantics here. The mediator’s purpose is to control the workflow between objects; we really don’t need anything more than an object literal to do this.
The following example shows a basic implementation of a mediator
object with some utility methods that can trigger and subscribe to events. The orgChart
object here is a mediator that assigns actions to be taken on the occurrence of a particular event. Here, a manager is assigned to the employee on completing the details of a new employee, and the employee record is saved:
const
orgChart
=
{
addNewEmployee
()
{
// getEmployeeDetail provides a view that users interact with
const
employeeDetail
=
this
.
getEmployeeDetail
();
// when the employee detail is complete, the mediator (the 'orgchart'
// object) decides what should happen next
employeeDetail
.
on
(
'complete'
,
employee
=>
{
// set up additional objects that have additional events, which are
// used by the mediator to do additional things
const
managerSelector
=
this
.
selectManager
(
employee
);
managerSelector
.
on
(
'save'
,
employee
=>
{
employee
.
save
();
});
});
},
// ...
};
I’ve often referred to this type of object as a “workflow” object in the past, but the truth is that it is a mediator. It is an object that handles the workflow between many other objects, aggregating the responsibility of that workflow knowledge into a single object. The result is a workflow that is easier to understand and maintain.
There are, without a doubt, similarities between the event aggregator and mediator examples that I’ve shown here. The similarities boil down to two primary items: events and third-party objects. These differences are superficial at best, though. When we dig into the pattern’s intent and see that the implementations can be dramatically different, the nature of the patterns becomes more apparent.
Both the event aggregator and mediator use events in the examples shown. An event aggregator obviously deals with events—it’s in the name, after all. The mediator only uses events because it makes life easy when dealing with modern JavaScript web app frameworks. There is nothing that says a mediator must be built with events. You can build a mediator with callback methods by handing the mediator reference to the child object or using several other means.
The difference, then, is why these two patterns are both using events. The event aggregator, as a pattern, is designed to deal with events. The mediator, though, uses them only because it’s convenient.
By design, the event aggregator and mediator employ a third-party object to streamline interactions. The event aggregator itself is a third party to the event publisher and the event subscriber. It acts as a central hub for events to pass through. The mediator is also a third party to other objects, though. So where is the difference? Why don’t we call an event aggregator a mediator? The answer primarily depends on where the application logic and workflow are coded.
In the case of an event aggregator, the third-party object is there only to facilitate the pass-through of events from an unknown number of sources to an unknown number of handlers. All workflow and business logic that needs to be kicked off is put directly into the object that triggers the events and the objects that handle the events.
In the mediator’s case, the business logic and workflow are aggregated into the mediator itself. The mediator decides when an object should have its methods called and attributes updated based on factors the mediator knows about. It encapsulates the workflow and process, coordinating multiple objects to produce the desired system behavior. The individual objects involved in this workflow know how to perform their task. But the mediator tells the objects when to perform the tasks by making decisions at a higher level than the individual objects.
An event aggregator facilitates a “fire and forget” model of communication. The object triggering the event doesn’t care if there are any subscribers. It just fires the event and moves on. A mediator might use events to make decisions, but it is definitely not “fire and forget.” A mediator pays attention to a known set of inputs or activities so that it can facilitate and coordinate other behavior with a known set of actors (objects).
Understanding the similarities and differences between an event aggregator and a mediator is essential for semantic reasons. It’s equally important to know when to use which pattern. The basic semantics and intent of the patterns inform the question of when, but experience in using the patterns will help you understand the more subtle points and nuanced decisions that must be made.
In general, an event aggregator is used when you either have too many objects to listen to directly or have entirely unrelated objects.
When two objects already have a direct relationship—for example, a parent view and a child view—there may be a benefit in using an event aggregator. Have the child view trigger an event, and the parent view can handle the event. This is most commonly seen in Backbone’s Collection and Model in JavaScript framework terms, where all Model events are bubbled up to and through its parent Collection. A Collection often uses model events to modify the state of itself or other models. Handling “selected” items in a collection is an excellent example.
jQuery’s on()
method as an event aggregator is a great example of too many objects to listen to. If you have 10, 20, or 200 DOM elements that can trigger a “click” event, it might be a bad idea to set up a listener on all of them individually. This could quickly deteriorate the performance of the application and user experience. Instead, using jQuery’s on()
method allows us to aggregate all events and reduce the overhead of 10, 20, or 200 event handlers down to 1.
Indirect relationships are also a great time to use event aggregators. In modern applications, it is ubiquitous to have multiple view objects that need to communicate but have no direct relationship. For example, a menu system might have a view that handles the menu item clicks. But we don’t want the menu to be directly tied to the content views showing all the details and information when a menu item is clicked—having the content and menu coupled together would make the code difficult to maintain in the long run. Instead, we can use an event aggregator to trigger menu:click:foo
events and have a “foo” object handle the click
event to show its content on the screen.
A mediator is best applied when two or more objects have an indirect working relationship, and business logic or workflow needs to dictate the interactions and coordination of these objects.
A wizard interface is an excellent example of this, as shown in the orgChart
example. Multiple views facilitate the entire workflow of the wizard. Rather than tightly coupling the view together by having them reference each other directly, we can decouple them and more explicitly model the workflow between them by introducing a mediator.
The mediator extracts the workflow from the implementation details and creates a more natural abstraction at a higher level, showing us at a much faster glance what that workflow is. We no longer have to dig into the details of each view in the workflow to see what the workflow is.
The crux of the difference between an event aggregator and a mediator, and why these pattern names should not be interchanged, is best illustrated by showing how they can be used together. The menu example for an event aggregator is the perfect place to introduce a mediator.
Clicking a menu item may trigger a series of changes throughout an application. Some of these changes will be independent of others, and using an event aggregator makes sense. Some of these changes may be internally related, though, and may use a mediator to enact those changes.
A mediator could then be set up to listen to the event aggregator. It could run its logic and process to facilitate and coordinate many objects related to each other but unrelated to the original event source:
const
MenuItem
=
MyFrameworkView
.
extend
({
events
:
{
'click .thatThing'
:
'clickedIt'
,
},
clickedIt
(
e
)
{
e
.
preventDefault
();
// assume this triggers "menu:click:foo"
MyFramework
.
trigger
(
`menu:click:
${
this
.
model
.
get
(
'name'
)
}
`
);
},
});
// ... somewhere else in the app
class
MyWorkflow
{
constructor
()
{
MyFramework
.
on
(
'menu:click:foo'
,
this
.
doStuff
,
this
);
}
static
doStuff
()
{
// instantiate multiple objects here.
// set up event handlers for those objects.
// coordinate all of the objects into a meaningful workflow.
}
}
In this example, when the MenuItem
with the right model is clicked, the menu:click:foo
event will be triggered. An instance of the MyWorkflow
class will handle this specific event and coordinate all of the objects it knows about to create the desired user experience and workflow.
We have thus combined an event aggregator and a mediator to create a meaningful experience in both code and application. We now have a clean separation between the menu and the workflow through an event aggregator, and we are still keeping the workflow clean and maintainable through a mediator.
Express.js is a popular web application server framework. We can add callbacks to certain routes that the user can access.
Say we want to add a header to the request if the user hits the root (/). We can add this header in a middleware callback:
const
app
=
require
(
"express"
)();
app
.
use
(
"/"
,
(
req
,
res
,
next
)
=>
{
req
.
headers
[
"test-header"
]
=
1234
;
next
();
});
The next()
method calls the next callback in the request-response cycle. We’d create a chain of middleware functions that sit between the request and the response or vice versa. We can track and modify the request object all the way to the response through one or multiple middleware functions.
The middleware callbacks will be invoked whenever the user hits a root endpoint (/):
const
app
=
require
(
"express"
)();
const
html
=
require
(
"./data"
);
app
.
use
(
"/"
,
(
req
,
res
,
next
)
=>
{
req
.
headers
[
"test-header"
]
=
1234
;
next
();
},
(
req
,
res
,
next
)
=>
{
console
.
log
(
`Request has test header:
${
!!
req
.
headers
[
"test-header"
]
}
`
);
next
();
}
);
app
.
get
(
"/"
,
(
req
,
res
)
=>
{
res
.
set
(
"Content-Type"
,
"text/html"
);
res
.
send
(
Buffer
.
from
(
html
));
});
app
.
listen
(
8080
,
function
()
{
console
.
log
(
"Server is running on 8080"
);
});
We will be covering the Facade pattern shortly, but for reference purposes, some developers may also wonder whether there are similarities between the Mediator and Facade patterns. They both abstract the functionality of existing modules, but there are some subtle differences.
The Mediator centralizes communication between modules where these modules explicitly reference it. In a sense, this is multidirectional. The Facade, however, defines a more straightforward interface to a module or system but doesn’t add any additional functionality. Other modules in the system aren’t directly aware of the concept of a facade and could be considered unidirectional.
The Command pattern aims to encapsulate method invocation, requests, or operations into a single object and allows us to both parameterize and pass method calls that can be executed at our discretion. In addition, it enables us to decouple objects invoking the action from the objects that implement them, giving us greater flexibility in swapping out concrete classes (objects).
Concrete classes are best explained in terms of class-based programming languages and are related to the idea of abstract classes. An abstract class defines an interface but doesn’t necessarily provide implementations for all its member functions. It acts as a base class from which others are derived. A derived class that implements the missing functionality is called a concrete class (see Figure 7-12). Base and concrete classes can be implemented in JavaScript (ES2015+) using the extends
keyword applicable to the JavaScript classes.
The general idea behind the Command pattern is that it provides a means to separate the responsibilities of issuing commands from anything executing commands, delegating this responsibility to different objects instead.
Implementation-wise, simple command objects bind the action and the object wishing to invoke the action. They consistently include an execution operation (such as run()
or execute()
). All command objects with the same interface can easily be swapped as needed, which is one of the vital benefits of the pattern.
To demonstrate the Command pattern, we will create a simple car purchasing service:
const
CarManager
=
{
// request information
requestInfo
(
model
,
id
)
{
return
`The information for
${
model
}
with ID
${
id
}
is foobar`
;
},
// purchase the car
buyVehicle
(
model
,
id
)
{
return
`You have successfully purchased Item
${
id
}
, a
${
model
}
`
;
},
// arrange a viewing
arrangeViewing
(
model
,
id
)
{
return
`You have booked a viewing of
${
model
}
(
${
id
}
) `
;
},
};
The CarManager
object is our command object responsible for issuing commands to request information about a car, buy a car, and arrange a viewing. It would be trivial to invoke our CarManager
methods by directly accessing the object. It would be forgivable to assume nothing is wrong with this—technically, it’s completely valid JavaScript. There are, however, scenarios where this may be disadvantageous.
For example, imagine if the core API behind the CarManager
changed. This would require all objects directly accessing these methods within our application to be modified. It is a type of coupling that effectively goes against the OOP methodology of loosely coupling objects as much as possible. Instead, we could solve this problem by abstracting the API away further.
Let’s now expand the CarManager
so that our Command pattern application results in the following: accept any named methods that can be performed on the
CarManager
object, passing along any data that might be used, such as the car model and ID.
Here is what we would like to be able to achieve:
CarManager
.
execute
(
'buyVehicle'
,
'Ford Escort'
,
'453543'
);
As per this structure, we should now add a definition for the carManager.execute
method as follows:
carManager
.
execute
=
function
(
name
)
{
return
(
carManager
[
name
]
&&
carManager
[
name
].
apply
(
carManager
,
[].
slice
.
call
(
arguments
,
1
))
);
};
Our final sample calls would thus look as follows:
carManager
.
execute
(
'arrangeViewing'
,
'Ferrari'
,
'14523'
);
carManager
.
execute
(
'requestInfo'
,
'Ford Mondeo'
,
'54323'
);
carManager
.
execute
(
'requestInfo'
,
'Ford Escort'
,
'34232'
);
carManager
.
execute
(
'buyVehicle'
,
'Ford Escort'
,
'34232'
);
With that, we can conclude our discussion of traditional design patterns you can use when designing classes, objects, and modules. I have tried incorporating an ideal mix of creational, structural, and behavioral patterns. We have also studied patterns created for classic OOP languages such as Java and C++ and adapted them for JavaScript.
These patterns will help us design many domain-specific objects (e.g., shopping cart, vehicle, or book) that make up our applications’ business model. In the next chapter, we will look at the larger picture of how we can structure applications so that this model delivers to the other application layers, such as the view or the presenter.