[Go to site: main page, start]

0% found this document useful (0 votes)
17 views703 pages

JavaScript Design Patterns Explained

The document is a comprehensive guide on JavaScript Design Patterns aimed at building scalable applications through object-oriented programming techniques. It covers various design patterns, including Creational, Structural, and Behavioral, along with their applications in modern frameworks like React and Vue. The book also emphasizes the importance of testing, common pitfalls, and real-world case studies to enhance understanding and practical implementation of these patterns.

Uploaded by

marc0000
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
17 views703 pages

JavaScript Design Patterns Explained

The document is a comprehensive guide on JavaScript Design Patterns aimed at building scalable applications through object-oriented programming techniques. It covers various design patterns, including Creational, Structural, and Behavioral, along with their applications in modern frameworks like React and Vue. The book also emphasizes the importance of testing, common pitfalls, and real-world case studies to enhance understanding and practical implementation of these patterns.

Uploaded by

marc0000
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Decoding

JavaScript Design
Patterns
Build scalable applications
with object-oriented
programming and advanced
pattern techniques

Rushabh Mulraj Shah

[Link]
First Edition 2025

Copyright © BPB Publications, India

eISBN: 978-93-65898-842

All Rights Reserved. No part of this publication may be reproduced, distributed


or transmitted in any form or by any means or stored in a database or retrieval
system, without the prior written permission of the publisher with the exception
to the program listings which may be entered, stored and executed in a
computer system, but they can not be reproduced by the means of publication,
photocopy, recording, or by any electronic and mechanical means.

LIMITS OF LIABILITY AND DISCLAIMER OF WARRANTY


The information contained in this book is true and correct to the best of author’s
and publisher’s knowledge. The author has made every effort to ensure the
accuracy of these publications, but the publisher cannot be held responsible for
any loss or damage arising from any information in this book.

All trademarks referred to in the book are acknowledged as properties of their


respective owners but BPB Publications cannot guarantee the accuracy of this
information.

[Link]
Dedicated to

Mulraj Dipchand Shah and Harsha Mulraj


Shah
My incredible parents, for defying traditions and
allowing me the freedom to chase 0s and 1s.
Your sacrifices and unwavering support have
been the bedrock of my journey.

Unnati Rushabh Shah


My wife, whose steadfast support and
unshakable belief in me have been my greatest
motivation through every success and setback.

And to the global JavaScript community, whose


contributions have shaped the web and inspired
the knowledge shared in this book.
About the Author

Rushabh Mulraj Shah is a passionate and accomplished


full-stack engineer with over 8 years of experience building
dynamic, high-performance web applications. Having
mastered both modern frontend and backend technologies,
Rushabh has contributed to cutting-edge projects at
renowned companies like Airbase, Fynd, Fractal, and
Capgemini. Additionally, he was the founding engineer of
[Link], an early-stage SaaS startup, where he played a
pivotal role in crafting its technological foundation.
As a dedicated mentor and educator, Rushabh has guided
over 1,000 students in mastering web development through
leading ed-tech platforms. He is also the author of two
widely acclaimed books on JavaScript and web components,
offering deep insights into JavaScript’s foundational
concepts and exploring the innovation of HTML web
components before the rise of modern frameworks like
React, Angular, and Vue.
Beyond his professional pursuits, Rushabh is an avid
traveler and a voracious reader with a keen interest in
history and fiction. His journey is driven by a passion for
innovation, education, and continuous exploration.
About the Reviewer

Martin Yanev is a highly accomplished software engineer


with a wealth of expertise spanning diverse industries,
including aerospace and medical technology. With an
illustrious career of over eight years, Martin has carved a
niche for himself in developing and seamlessly integrating
cutting-edge software solutions for critical domains such as
air traffic control and chromatography systems. Martin is
renowned as an esteemed instructor and a computer
science professor at Fitchburg State University. His
instructional prowess shines through as he imparts
knowledge and guidance, leveraging his extensive
proficiency in frameworks such as Flask, Django, Pytest, and
TensorFlow. Possessing a deep understanding of the
complete spectrum of OpenAI APIs, Martin exhibits mastery
in constructing, training, and fine-tuning AI systems.
Martin’s commitment to excellence is exemplified by his
dual master’s degrees in aerospace systems and software
engineering. With his exceptional track record and
multifaceted skill set, Martin continues to propel innovation
and drive transformative advancements in the field of
software engineering.
Acknowledgement

This book has been a long time in the making, with its
journey spanning both professional challenges and personal
growth. It would not have been possible without the
unwavering support of several remarkable individuals. To
begin with, I must express my deepest gratitude to my
family—Mr. Mulraj Dipchand Shah, my father; Mrs.
Harsha Mulraj Shah, my mother; and Mrs. Unnati
Rushabh Shah, my wife. Coming from a conservative
family, my parents had the courage to defy societal norms
and encourage me to pursue a career in technology—a path
no one in my extended family had dared to tread. Their
sacrifices and steadfast belief in my potential have been the
cornerstone of everything I have achieved. My wife, Unnati,
has been my rock through this journey. A dear friend for
over a decade before we married, she has always stood by
me with unshakable faith, cheering me on through minor
victories and major setbacks alike.
I owe a significant part of my career to John Thomas, a
former colleague and mentor who became a lighthouse
during one of the most turbulent phases of my corporate
journey. For an introverted individual with limited
connections, navigating the corporate world felt like scaling
a mountain, but John’s guidance helped me find direction
and purpose. I also want to thank Supriya Adep, a close
friend who, with her sharp judgment and candid advice—no
matter how unvarnished—has consistently helped me make
sound decisions. Both of you have left an indelible mark on
my personal and professional growth, and I am deeply
grateful.
Finally, none of this would have been possible without the
incredible team at BPB Publications. Their diligence,
encouragement, and constant follow-ups played a vital role
in bringing this book to life. I hope this work serves as a
resource for readers and helps ignite meaningful change in
the world of technology.
Preface

The world as we know it in the 21st century is digitally


powered. From buying groceries to applying for insurance,
learning a new skill, or getting a glimpse into the lives of
famous celebrities, the phenomenal realm of the web makes
it all possible. At the heart of this modern web revolution is
JavaScript—a technology that powers nearly every aspect of
web applications. From crafting dynamic user interfaces to
handling behind-the-scenes logic, computational analysis,
and rule definition, JavaScript plays a pivotal role.
However, building scalable, robust, and future-proof
applications requires more than a basic understanding of
JavaScript. This book empowers you to unlock JavaScript’s
full potential by exploring and implementing Design Patterns
that have stood the test of time. While these patterns are
not exclusive to JavaScript, we will apply them to this
language of the web, helping you create applications that
are resilient to the evolving demands of technology.
Through a systematic approach supported by real-world
examples, this book demystifies the intricate world of
JavaScript Design Patterns. Each chapter focuses on a
specific style of Design Pattern, offering detailed
explanations, practical use cases, and coding examples to
show how these patterns optimize code structure, enhance
maintainability, and improve overall application design.
The book is organized into 13 chapters, each building on the
last. The initial chapters introduce the three main categories
of Design Patterns—Creational, Structural, and Behavioral—
laying a strong foundation for understanding their
distinctions and appropriate use cases. As the journey
progresses, readers will tackle advanced challenges,
including the application of Design Patterns for
asynchronous behaviors and their integration with modern
front-end libraries and frameworks like React and Vue.
With the fundamentals in place, the book explores the
combined use of Design Patterns, demonstrating their
benefits for web architecture and test-driven development.
Finally, we delve into the process of creating Design
Patterns from scratch, highlighting common pitfalls to avoid
and analyzing real-world examples employed by tech giants
like Google and Airbnb.
Chapter 1: Introduction to JavaScript Design Patterns
– We begin our journey with a foundational introduction to
Design Patterns. The chapter starts by tracing the origins of
JavaScript, a powerful language that forms the backbone of
the web but is not without its shortcomings. Through
relevant real-world examples, we will highlight common
pitfalls developers encounter when writing JavaScript code.
This exploration naturally leads to understanding the need
for Design Patterns and their brief origins. We will introduce
the broad classification of Design Patterns into three
categories—Creational, Structural, and Behavioral—and
demonstrate their benefits. By revisiting the earlier
showcased problems, we will illustrate how Design Patterns
offer effective solutions, setting the stage for a deeper dive
into the first category: Creational Design Patterns.
Chapter 2: Creational Design Patterns – This chapter
explores Creational Design Patterns, beginning with a brief
revision of object-oriented programming (OOP)
concepts to establish a strong foundation. We introduce
Creational patterns and their role in addressing object
creation challenges. The chapter covers five key patterns—
Singleton, Factory, Abstract Factory, Builder, and Prototype
—detailing their problem-solving capabilities,
implementation steps, real-world applications, and pros and
cons. By the end of this chapter, you will have a practical
understanding of how to leverage these patterns to build
scalable and maintainable JavaScript applications.
Chapter 3: Structural Design Patterns – This chapter
introduces Structural Design Patterns, which focus on
organizing classes and objects to form larger, more
adaptable structures in software systems. We explore key
patterns, including Adapter, Bridge, Composite, Decorator,
Facade, Flyweight, and Proxy, examining their roles,
implementation, and applications. By the end of the
chapter, we summarize the distinctions between these
patterns and provide guidance on selecting the appropriate
pattern for different scenarios.
Chapter 4: Behavioral Design Patterns – This chapter
focuses on Behavioral Design Patterns, essential strategies
for managing object communication, algorithm coordination,
and object state representation in software systems. We
explore key patterns, including Chain of Responsibility,
Command, Iterator, Mediator, Memento, Observer, and
Strategy, highlighting their roles in streamlining interactions
and maintaining efficient workflows. By completing this
chapter, readers will have a comprehensive understanding
of the three primary categories of Design Patterns.
Chapter 5: Asynchronous Performance Design
Patterns – This chapter examines Design Patterns aimed at
enhancing the performance and efficiency of asynchronous
operations in JavaScript. We begin by exploring the
asynchronous behavior within JavaScript’s synchronous,
single-threaded architecture, laying the foundation for
understanding event handling. The chapter then dives into
patterns such as throttling, debouncing, parallelism,
concurrency, batch processing, and Promise optimization,
equipping readers with the tools to handle asynchronous
tasks effectively and create more responsive applications.
Chapter 6: Design Patterns in React – This chapter
focuses on applying JavaScript Design Patterns within the
React ecosystem to enhance code structure, scalability, and
maintainability. We begin by exploring React’s built-in
patterns, followed by an analysis of popular external
patterns designed specifically for React applications. Finally,
we leverage our understanding of Design Patterns to
address real-world challenges in React, providing practical
insights into their implementation and benefits.
Chapter 7: Design Patterns in Vue – This chapter
explores the application of Design Patterns in the Vue
ecosystem to improve code structure, scalability, and
maintainability. We begin by examining the Design Patterns
inherent to Vue, followed by a review of popular external
patterns tailored for Vue applications. Finally, we apply our
understanding of Design Patterns to practical scenarios in
Vue, offering actionable insights for building robust and
efficient applications.
Chapter 8: Advanced Design Patterns in JavaScript –
This chapter delves into advanced Design Patterns,
including the Module Pattern, MV* variations like MVC, MVP,
and MVVM, as well as strategies for combining multiple
patterns to address complex challenges. Additionally, we
explore optimization techniques to enhance the
effectiveness of these patterns, providing readers with the
tools to tackle sophisticated scenarios in JavaScript
development.
Chapter 9: Design Patterns for Web Architects – This
chapter examines the critical role of Design Patterns in web
architecture, focusing on their impact on scalability,
availability, and performance. We explore how these
patterns guide decision-making and address key challenges
in modern web development, equipping web architects with
the knowledge to design robust and efficient systems.
Chapter 10: Testing and Design Patterns – In this
chapter, we explore how JavaScript Design Patterns can
enhance the testing process by improving test structure,
maintainability, and efficiency. This chapter highlights the
importance of unit testing, leveraging Design Patterns to
create resilient and future-proof test cases. The chapter
discusses key testing concepts, methodologies such as unit
testing, and the role of frameworks like Jest. By integrating
Design Patterns into testing strategies, developers can
create more structured, maintainable, and effective test
suites, ultimately leading to higher-quality code and a better
user experience.
Chapter 11: Writing and Creating Design Patterns –
This chapter explores the art of crafting clear, reusable, and
impactful Design Patterns for software development. We
begin by understanding the authoring process, followed by
tips for writing effective patterns that contribute value to
the coding community. Finally, we discuss the steps to
publish and share Design Patterns, ensuring their
accessibility and utility for developers worldwide.
Chapter 12: Common Pitfalls and How to Avoid Them
– This chapter addresses the common pitfalls and
challenges developers face when working with JavaScript
and Design Patterns. It highlights mistakes, misconceptions,
and frequent stumbling blocks, offering practical strategies
to avoid these issues. Topics covered include implicit type
conversion, scope and variable hoisting, the “this” keyword
misconception, callback hell, memory leaks, and other
common obstacles. By recognizing and proactively
addressing these pitfalls, readers can create more robust,
maintainable, and scalable code.
Chapter 13: Case Studies – This chapter presents case
studies that showcase the practical implementation of
Design Patterns in real-world scenarios. We explore how
companies like Google, Netflix, Twitter, Amazon, and Airbnb
successfully applied Design Patterns to tackle specific
challenges and achieve significant outcomes in software
development. By examining these documented problems
and their solutions, readers gain insights into how Design
Patterns can be leveraged to solve complex issues in large-
scale systems.
Code Bundle and Coloured
Images
Please follow the link to download the
Code Bundle and the Coloured Images of the book:

[Link]
The code bundle for the book is also hosted on GitHub at
[Link]
JavaScript-Design-Patterns. In case there’s an update to
the code, it will be updated on the existing GitHub
repository.
We have code bundles from our rich catalogue of books and
videos available at [Link]
Check them out!

Errata
We take immense pride in our work at BPB Publications and
follow best practices to ensure the accuracy of our content
to provide with an indulging reading experience to our
subscribers. Our readers are our mirrors, and we use their
inputs to reflect and improve upon human errors, if any, that
may have occurred during the publishing processes
involved. To let us maintain the quality and help us reach
out to any readers who might be having difficulties due to
any unforeseen errors, please write to us at :
errata@[Link]
Your support, suggestions and feedbacks are highly
appreciated by the BPB Publications’ Family.

Did you know that BPB offers eBook versions of every book published, with
PDF and ePub files available? You can upgrade to the eBook version at
[Link] and as a print book customer, you are entitled to a
discount on the eBook copy. Get in touch with us at :
business@[Link] for more details.
At [Link], you can also read a collection of free technical
articles, sign up for a range of free newsletters, and receive exclusive
discounts and offers on BPB books and eBooks.

Piracy
If you come across any illegal copies of our works in any form on the internet,
we would be grateful if you would provide us with the location address or
website name. Please contact us at business@[Link] with a link to
the material.

If you are interested in becoming an author


If there is a topic that you have expertise in, and you are interested in either
writing or contributing to a book, please visit [Link]. We have
worked with thousands of developers and tech professionals, just like you, to
help them share their insights with the global tech community. You can make
a general application, apply for a specific hot topic that we are recruiting an
author for, or submit your own idea.

Reviews
Please leave a review. Once you have read and used this book, why not leave
a review on the site that you purchased it from? Potential readers can then
see and use your unbiased opinion to make purchase decisions. We at BPB
can understand what you think about our products, and our authors can see
your feedback on their book. Thank you!
For more information about BPB, please visit [Link].

Join our book’s Discord space


Join the book’s Discord Workspace for Latest updates,
Offers, Tech happenings around the world, New Release and
Sessions with the Authors:
[Link]
Table of Contents

1. Introduction to JavaScript Design Patterns


Introduction
Structure
Objectives
JavaScript: Origins
Inception of JavaScript
War of the browsers
Revival of JavaScript
Common pitfalls in JavaScript
Problem statement 1
Problem statement 2
Problem statement 3
Problem statement 4
Problem statement 5
Design Patterns
Origins of Design Patterns
A Pattern Language, 1977
The Gang of Four book, 1994
Classification of Design Patterns
Creational Design Patterns
Structural Design Patterns
Behavioural Design Patterns
Advantages of Design Patterns
Reusability
Maintainability
Scalability
Common vocabulary
Abstraction and encapsulation
Performance improvement
Proven solutions
Faster development
Facilitates evolution and adaptation
Solutions to problem statements
Problem statement 1
Problem statement 2
Problem statement 3
Problem statement 4
Problem statement 5
Conclusion
Points to remember
Exercises
Answers

2. Creational Design Patterns


Introduction
Structure
Objectives
Object-oriented programming
Classical object-oriented programming
Objects
Classes
Principles of OOP
Object-oriented programming in JavaScript
Object prototype
Constructors
Inheritance in JavaScript
Polymorphism in JavaScript
Class-based OOP vs. OOP in JavaScript
Introduction to Creational Design Patterns
Importance of Object creation
Problems with incorrect object creation
Memory leak
Circular dependencies
Uninitialized variables
Singleton Pattern
Problem scenario
Scenario #1
Scenario #2
Implementation
Code example
Applications
Database connection management
Caching
Maintaining application state
Pros and cons
Factory Method Pattern
Problem scenario
Implementation
Code example
Step 1: Define the product interface
Step 2: Implement concrete products
Step 3: Define the creator interface
Step 4: Implement concrete creators
Step 5: Client code
Applications
UI Component Libraries
Data access layers
Game development
Internationalization
Pros and cons
Abstract Factory Pattern
Problem scenario
Implementation
Step 1: Define abstract products
Step 2: Define Abstract Factory
Step 3: Implement concrete products
Step 4: Implement concrete factories
Step 5: Client code
Code example
Step 1: Define abstract products
Step 2: Define Abstract Factory
Step 3: Implement concrete products
Step 4: Implement concrete creators
Step 5: Client code
Applications
UI Component Libraries
Localization and internationalization
Document processing libraries
Pros and cons
Builder Pattern
Problem scenario
Implementation
Step 1: Define the product
Step 2: Define the builder interface
Step 3: Create concrete builders
Step 4: Create the director
Step 5: Client code
Code example
Step 1: Define the product
Step 2: Define the builder interface
Step 3: Create concrete builders
Step 4: Create the director
Step 5: Client code
Applications
Step-by-step object construction
Database query building
Building documents or reports
Product variants
Assembling composite objects
Pros and cons
Prototype Pattern
Problem scenario
Implementation
Step 1: Create an object prototype
Step 2: Create instances using the prototype
Step 3: Customize instances
Step 4: Test and use instances
Code example
Step 1: Create an object prototype
Step 2: Create instances using the prototype
Step 3: Customize instances
Step 4: Test and use instances
Applications
Reducing object initialization overhead
Dynamic object configuration
Managing object states
Role-based permissions
Pros and cons
Conclusion
Points to remember
Exercises
Answers

3. Structural Design Patterns


Introduction
Structure
Objectives
Introduction to Structural Design Patterns
Problem statement #1
Code issue
Problem statement #2
Code issue
Problem statement #3
Code issue
Structural Design Patterns
Organization of classes
Object composition
Code reusability
Flexibility and adaptability
Common design problems
Adapter Design Pattern
Problem scenario
Scenario #1
Scenario #2
Implementation
Step 1: Define the existing class
Step 2: Create the target interface
Step 3: Implement the Adapter
Step 4: Client code
Step 5: Usage
Code example
Step 1: Define the existing class
Step 2: Create the target interface
Step 3: Implement the Adapter
Step 4: Client code
Step 5: Usage
Applications
Integration legacy code
Third-party API integration
Microservices integration
Version upgrades
Internationalization
Pros and cons
Bridge Design Pattern
Problem scenario
Scenario #1
Scenario #2
Implementation
Step 1: Define the implementor interface
Step 2: Create concrete implementors
Step 3: Define the abstraction
Step 4: Create refined abstractions
Step 5: Client code
Code example
Step 1: Define the implementor interface
Step 2: Create concrete implementors
Step 3: Define the abstraction
Step 4: Create refined abstractions
Step 5: Client code
Applications
UI framework for cross-platform applications
Database abstraction layer
Messaging system for communication channels
Network communication framework
Pros and cons
Composite Design Pattern
Problem scenario
Implementation
Step 1: Define the component interface or the base class
Step 2: Implement leaf class (Individual components)
Step 3: Implement composite class
Step 4: Client code
Code example
Step 1: Define the component interface or the base class
Step 2: Implement leaf Class (Individual components)
Step 3: Implement composite class
Step 4: Client code
Applications
GUI components
Tree structures
Document Object Model manipulation
File system operations
Menu systems
E-commerce shopping cart
Pros and cons
Decorator Design Pattern
Problem scenario
Implementation
Step 1: Define the component interface
Step 2: Create concrete component
Step 3: Create the decorator class
Step 4: Create concrete decorators
Step 5: Usage
Code example
Step 1: Define the component interface
Step 2: Create concrete component
Step 3: Create the decorator class
Step 4: Create concrete decorators
Step 5: Usage
Applications
User interface components
Data validation
Authentication
Dynamic configuration
Event handling
Pros and cons
Façade Design Pattern
Problem scenario
Implementation
Step 1: Create the subsystem classes
Step 2: Create the façade classes
Step 3: Encapsulate the subsystem logic into the façade
Step 4: Expose simplified methods
Step 5: Client code uses the façade
Code example
Step 1: Create the subsystem classes
Step 2: Create the façade classes
Step 3: Encapsulate the subsystem logic into the façade
Step 4: Expose simplified methods
Step 5: Client code uses the façade
Applications
Working with multiple APIs
Browser compatibility issues
Legacy code integration
Resource management
Third-party library integration
Pros and cons
Flyweight Design Pattern
Problem scenario
Implementation
Step 1: Identify the intrinsic and extrinsic states
Step 2: Create the flyweight interface
Step 3: Implement concrete flyweight class
Step 4: Create a flyweight factory
Step 5: Client code
Code example
Step 1: Identify the intrinsic and extrinsic states
Step 2: Create the flyweight interface
Step 3: Implement concrete flyweight class
Step 4: Create a flyweight factory
Step 5: Client code
Applications
Rendering engine in a game
UI components in a web application
Document Object Model nodes
Collaborative editing tools
Pros and cons
Proxy Design Pattern
Problem scenario
Implementation
Step 1: Identify the subject (real object)
Step 2: Create the proxy
Step 3: Define the proxy interface
Step 4: Implement the real object
Step 5: Implement the proxy
Step 6: Utilize the proxy in client code
Code example
Step 1: Identify the subject (real object)
Step 2: Create the proxy
Step 3: Define the proxy interface
Step 4: Implement the real object
Step 5: Implement the proxy
Step 6: Utilize the proxy in client code
Applications
Lazy loading
Access control
Caching
Validation
Remote proxy (AJAX requests)
Pros and cons
Pattern selection tips
Adapter Pattern
Bridge Pattern
Decorator Pattern
Façade Pattern
Flyweight Pattern
Proxy Pattern
Conclusion
Points to remember
Exercises
Answers

4. Behavioral Design Patterns


Introduction
Structure
Objectives
Introduction to Behavioral Design Patterns
Problem statement #1
Code issue
Problem statement #2
Code issue
Behavioral Design Patterns
Chain of Responsibility Pattern
Problem scenario
Implementation
Step 1: Define a handler interface
Step 2: Create concrete handlers
Step 3: Implement the handle method
Step 4: Set up the chain
Step 5: Client code
Code example
Step 1: Define a handler interface
Step 2: Create concrete handlers
Step 3: Implement the handle method
Step 4: Set up the chain
Step 5: Client code
Applications
Middleware processing in web servers
Form validation
Request handling in [Link] middleware
Chatbot response processing
Workflow engines
Pros and cons
Command Pattern
Problem scenario
Implementation
Step 1: Define the command interface
Step 2: Implement concrete command classes
Step 3: Implement receiver classes
Step 4: Implement invoker classes
Step 5: Client code
Code example
Step 1: Define the Command interface
Step 2: Implement concrete command classes
Step 3: Implement receiver classes
Step 4: Implement invoker classes
Step 5: Client code
Applications
GUI applications
Smart home automation
Undo/redo functionality
Logging and auditing
Workflow management
Pros and cons
Iterator Pattern
Problem scenario
Implementation
Step 1: Define the iterator interface
Step 2: Implement a concrete iterator
Step 3: Create an aggregate object
Step 4: Use the iterator in client code
Code example
Step 1: Define the iterator interface
Step 2: Implement a concrete iterator
Step 3: Create an aggregate object
Step 4: Use the iterator in client code
Applications
Iterating over arrays
Traversing object properties
Navigating database results
Parsing and processing streams
Pros and cons
Mediator Pattern
Problem scenario
Implementation
Step 1: Define the mediator object
Step 2: Create entity objects
Step 3: Register entities with the mediator
Step 4: Communication through the mediator
Code example
Step 1: Define the mediator object
Step 2: Create entity objects
Step 3: Register entities with the mediator
Step 4: Communication through the mediator
Applications
Chat applications
Game development
Microservices communication
Real-time collaborative editing
Pros and cons
Memento Pattern
Problem scenario
Step 1: Create the originator class
Step 2: Create the memento class
Step 3: Create the caretaker class
Step 4: Client code
Code example
Step 1: Create the originator class
Step 2: Create the memento class
Step 3: Create the caretaker class
Step 4: Client code
Applications
Form state management
Text editors or code editors
Drawing applications
Game state management
Pros and cons
Observer Pattern
Problem scenario
Implementation
Step 1: Define the subject class
Step 2: Define the observer classes
Step 3: Implement subscription mechanism
Step 4: Implement notification mechanism
Step 5: Create, subscribe and trigger
Code example
Step 1: Define the subject class
Step 2: Define the observer classes
Step 3: Implement subscription mechanism
Step 4: Implement notification mechanism
Step 5: Create, subscribe and trigger
Applications
UI components and state management
Event handling
Model-View-Controller architecture
Cross-component communication
Pros and cons
Strategy Pattern
Problem scenario
Implementation
Step 1: Define the strategies
Step 2: Create the context class
Step 3: Use the context class with different strategies
Code example
Step 1: Define the strategies
Step 2: Create the context class
Step 3: Use the context class with different strategies
Applications
Sorting algorithms
Form validation
Authentication strategies
Search algorithms
Payment processing
Routing strategies
Pros and cons
Conclusion
Points to remember
Exercises
Answers

5. Asynchronous Performance Design Patterns


Introduction
Structure
Objectives
Asynchronous JavaScript
Synchronous JavaScript
Challenges with synchronous JavaScript
Blocking nature of JavaScript
Network operations
User experience
Concurrency and stability
Browser compatibility
Callback functions
Callback hell
Promises
Async/await
Generators and yield
Web APIs
Event handling in JavaScript
Events in JavaScript
Event propagation
Capturing phase
Bubbling phase
Event Loop
Throttling and debouncing
Throttling
Problem statement
Implementation
Applications
Scrolling events
Resize events
Input events
Network requests
UI updates
Polling
Pros and cons
Debouncing
Problem statement
Implementation
Applications
Pros and cons
Parallelism and concurrency
Parallelism
Web Workers
Applications
Pros and cons
Concurrency
Concurrency vs parallelism
Approaches
Batch processing
Problem statement
Implementation
Step 1: Define the batch processing function
Step 2: Implement the individual async task function
Step 3: Define the input data
Step 4: Execute batch processing
Applications
API requests
Database operations
Image processing
Data migration
Report generation
Job queues
Pros and cons
Promise optimization
Avoiding nested Promises
Promise chaining
Error handling
Parallel Promises
Promise reuse
Memory management
Performance considerations
Conclusion
Points to remember
Exercises
Answers

6. Design Patterns in React


Introduction
Structure
Objectives
Overview of React
Challenges faced in traditional JavaScript
Overreliance on DOM manipulation
Code reusability
Scalability
React
Built-in patterns in React
Components, state, and props
Class-based components
Functional components
State
Props
Components as Design Patterns
React Hooks
Types of Hooks
Hooks as Design Patterns
Higher-order components
HOCs as Design Patterns
Popular Design Patterns specific for React
Container/Component Pattern
Container components
Presentation components
Advantages
Render Props Pattern
Implementation
Advantages
Context API Pattern
Implementation
Advantages
Using JavaScript Design Patterns with React
Module Pattern
Factory Pattern
Observer Pattern
Decorator Pattern
Conclusion
Points to remember
Exercises
Answers

7. Design Patterns in Vue


Introduction
Structure
Objectives
Overview of Vue
Challenges faced in traditional JavaScript
Overreliance on DOM manipulation
Code reusability
Scalability
Vue
Design Patterns with Vue
Components and SFCs
Components as Design Patterns
State management using Vuex
Vuex as Design Patterns
Custom directives and mixins
Custom directive approach
Mixins approach
Custom directives and mixins as Design Patterns
Popular Design Patterns specific for Vue
Computed properties and watchers
Advantages
Scoped styles
Advantages
Dynamic component loading
Using JavaScript Design Patterns with Vue
Singleton Pattern
Module Pattern
Factory Pattern
Observer Pattern
Conclusion
Points to remember
Exercises
Answers

8. Advanced Design Patterns in JavaScript


Introduction
Structure
Objectives
Introduction to advanced Design Patterns
Module Pattern
Revealing Module Pattern
Implementation
Advantages
Disadvantages
MV* patterns
MVC pattern
Advantages
Disadvantages
MVP pattern
MVC vs. MVP Design Patterns
Advantages
Disadvantages
MVMM pattern
Advantages
Disadvantages
Combining Design Patterns
Singleton with Factory Design Patterns
Observer with Mediator Design Patterns
Decorator with Strategy Design Patterns
Conclusion
Points to remember
Exercises
Answers

9. Design Patterns for Web Architects


Introduction
Structure
Objectives
Web architecture and web architects
Building blocks of web architecture
The role of a web architect
Role of Design Patterns in designing systems
Applying Design Patterns to improve scalability
Module Pattern
Revealing Module Pattern
Singleton Pattern
Observer Pattern
Factory Pattern
Decorator Pattern
Strategy Pattern
Proxy Pattern
Applying Design Patterns to improve availability
Singleton Pattern
Observer Pattern
Decorator Pattern
Proxy Pattern
Factory Pattern
Applying Design Patterns to improve performance
Module Pattern
Singleton Pattern
Observer Pattern
Factory Pattern
Decorator Pattern
Proxy Pattern
Conclusion
Points to remember
Exercises
Answers

10. Testing and Design Patterns


Introduction
Structure
Objectives
Overview
Testing
Types of testing
Importance of testing in JavaScript
Writing unit tests for Design Patterns
Singleton Pattern
Factory pattern
Observer pattern
Strategy Pattern
Decorator Pattern
Adapter Pattern
Builder Pattern
Prototype Pattern
Mediator Pattern
Chain of Responsibility Pattern
Summary
Singleton Pattern
Factory pattern
Observer pattern
Strategy Pattern
Decorator Pattern
Adapter Pattern
Builder Pattern
Prototype Pattern
Mediator Pattern
Chain of Responsibility Pattern
Planning unit tests for JavaScript Design Patterns
Understand the Design Pattern
Identify key components and responsibilities
Define test cases
Mock dependencies
Automate tests
Ensure coverage
Conclusion
Points to remember
Exercises
Answers
11. Writing and Creating Design Patterns
Introduction
Structure
Objectives
Design Pattern authoring process
Analysis of problem scenarios
Understanding the context
Deconstructing the problem
Tools and techniques for analysis
Defining the problem and context
Articulating the problem and context
Analyzing the impact and RCA
Design Pattern structure and elements
Key elements of a Design Pattern
Creating effective Design Patterns
Writing clear and concise pattern descriptions
Use simple and precise language
Focus on the core idea
Use visual aids
Provide real-world examples
Ensuring reusability and flexibility
Generalize the solution
Promote loose coupling
Facilitate extension
Review, refinement, and documentation
Peer review
Iterative refinement
Comprehension documentation
Publishing and sharing Design Patterns
Publishing platforms and repositories
Online pattern repositories
GitHub and GitLab
Personal blogs and websites
Technical journals and conferences
E-books and print books
Collaborative pattern development
Online collaboration tools
Open-source contributions
Online collaboration tools
Peer review and feedback
Community forum and discussion groups
Conclusion
Points to remember
Exercise
Answers

12. Common Pitfalls and How to Avoid Them


Introduction
Structure
Objectives
Implicit type conversion and coercion
Breaking consistency in comparisons
Unintended outcomes in Structural Patterns
Behavioral Pattern inconsistencies
Scope and variable hosting
Accidental global variables
Unexpected behavior due to hoisting
Scope binding in callback functions
The this keyword misconception
Misunderstanding the dynamic nature of this
Callback hell and asynchronous operations
Callback hell and nested callbacks
Memory leaks and performance bottlenecks
Memory leak in Observer Pattern (event listeners)
Frequent DOM manipulation leading to performance
bottleneck
Inefficient data handling
Inefficient DOM manipulation
Excessive style changes causing multiple reflows
Unnecessary DOM reads and writes
Direct DOM access in large applications
Error handling and debugging
Lack of proper error handling
Silent failures in Design Patterns like Observer
Security vulnerabilities
Singleton Pattern with exposed data
Cross-site scripting vulnerabilities in the Observer
Pattern
Cross-site request forgery in the Factory Pattern
Anti-pattern and code smells
The God Object anti-pattern
Spaghetti code
Premature optimization
Conclusion
Points to remember
Exercise
Answers

13. Case Studies


Introduction
Structure
Objectives
Case study examples
Singleton Pattern at Google
Problems faced before using the Singleton Pattern
How the Singleton Pattern solved the problem
Metrics to define how it helped
Factory Pattern at Netflix
Problems faced before using the Factory Pattern
How the Factory Pattern solved the problem
Metrics to define how it helped
Observer Pattern at Twitter
Problems faced before using the Observer Pattern
How the Observer Pattern solved the problem
Metrics to define how it helped
Composite Pattern at Amazon
Problems faced before using the Composite Pattern
How the Composite Pattern solved the problem
Metrics to define how it helped
Strategy Pattern at Airbnb
Problems faced before using the Strategy Pattern
How the Strategy Pattern solved the problem
Metrics to define how it helped
Analysis and insights
Success stories and impacts
Case study summaries
Success metrics
Business impacts
Lessons learned
Conclusion
Points to remember
Questions
Answers

Index
CHAPTER 1
Introduction to JavaScript
Design Patterns

Introduction
In the digital world of the 21st century, JavaScript has
scripted itself to be the cornerstone that powers almost the
entire web landscape. Understanding and implementing
JavaScript is vital for meaningful contributions in today’s
Internet-centric society. However, mastery of JavaScript
does not solely lie in rote-learning its syntax or
functionalities. To truly master JavaScript, one needs to
learn the art of optimizing and structuring the code
effectively. In this chapter, we are going to get a brief
understanding of JavaScript and its rise to prominence,
which makes it a pivotal part of building the web. We will
then move ahead to understand the common pitfalls a
JavaScript developer might run into. These pitfalls will
finally lead us to our primary focus – Design Patterns.
Throughout this chapter, you will gain insight into the idea
of Design Patterns, its origins, its contribution in helping us
code JavaScript better, and its monumental significance in
helping build scalable applications.
Structure
This chapter will cover the following topics:
• JavaScript: Origins
• Common pitfalls in JavaScript
• Design Patterns
• Origins of Design Patterns
• Advantages of Design Patterns
• Solutions to problem statements

Objectives
Upon completing this chapter, you will possess a
comprehensive grasp of Design Patterns and their
paramount importance in coding practices. Additionally,
you will refresh your understanding of JavaScript’s roots
and its latest advancements, as of May 2024. You will also
gain profound insights into prevalent pitfalls encountered
by JavaScript developers and discern how the
implementation of Design Patterns can effectively rectify
these issues.

JavaScript: Origins
Before we begin this section, please note that this section
of the chapter will be strictly talking about the origin of
JavaScript and its ascension to become the de facto
language of the web. We will not be doing a detailed
understanding of the core concepts of JavaScript and its
affiliations. The primary reason to understand JavaScript’s
origins is to ascertain the problems faced by the web
community before its inception and set the base for
understanding Design Patterns for JavaScript. If you wish
to refresh your knowledge of JavaScript, we recommend
you go through the basics of the language before restarting
this book. It is of paramount importance to have a crystal-
clear understanding of the language before we embark on
our journey to learn Design Patterns.

Inception of JavaScript
In the early 90s, Netscape Communications Corporation
developed and released the Netscape Navigator, one of the
first web browsers with a graphical user interface. While its
popularity soared, it was still bound to display static
websites with limited interactivity. To address this
limitation, Netscape brought in Brendan Eich, an American
computer programmer, to develop a language which can
introduce website interactivity. A year later, Eich
successfully created a new scripting language that
perfectly served this purpose. Cashing in on the dot-com
boom, the rise of the Internet, and the popularity of Java,
Eich allegedly decided to call this new scripting language
JavaScript.

War of the browsers


The marketing ploy by Brendan Eich worked, and
JavaScript became an instant hit. While Netscape scored a
crucial goal by releasing a language that can handle web
interactivity, it also motivated its competitor, Microsoft, to
release a competitive scripting language that could work
only on its newly launched browser, Microsoft Explorer.
They called it the JScript. This ensuing browser war made
it difficult for web developers to make their websites work
well for both the browsers, who were gaining popularity
steadily.
Soon, Netscape submitted its scripting language,
JavaScript, to the European Computer Manufacturers
Association (ECMA) for standardization that could make it
imperative for all browser vendors to conform. It did lead
to the first official release of the ECMAScript language
specification in 1997, resulting in a practice that is still
followed by and adhered to by scripting languages like
JavaScript and TypeScript. Microsoft had stopped
collaborating with ECMA after a few initial participations,
and soon, the entire trend of client-side scripting was
slowly fading into the background.

Revival of JavaScript
It would take ten more years and the introduction of some
fascinating new technologies like AJAX and jQuery to finally
revive JavaScript from its impending doom. In 2008, Google
debuted its Chrome browser, which surpassed the records
for usage statistics set before. Its brand-new V8 JavaScript
engine and just-in-time (JIT) compilation made it
imperative for other browser vendors to revamp their
engines. It ultimately culminated in all disparate parties
coming together for a conference, and the ECMA officially
released the ECMAScript 5 (ES5) in December 2009.
While ES5 played a pivotal role in standardizing JavaScript
and remains a widely supported version, modern JavaScript
has evolved significantly with newer standards like ES6
(2015) and beyond, which introduce advanced features and
capabilities to the language. The interest in client-side
scripting increased manifold, which led to the release of
the popular ECMAScript 6 (ES6) in 2015. At the time of
writing, in 2024, popular web frameworks like ReactJS,
[Link], and several other libraries are heavily built on ES6.
Since 2015, the World Wide Web Consortium (W3C)
organization meets once every year to release an improved
version of ECMAScript that makes writing JavaScript easier
and more efficient.
Common pitfalls in JavaScript
Being the de facto language of the web, JavaScript is
ubiquitous and versatile in its approach. However, writing
JavaScript code also presents challenges. Novice and
expert developers can often find themselves running into
problems they might not anticipate.
To demonstrate this, we will look at a few simple but
common problems every developer might have faced while
coding. We will first look at the problematic code and then
try to understand what the issue with the code could be. To
make it challenging, try to figure out the problem with the
code on yourself without looking at the description below
it.

Problem statement 1
In the following code, we have declared a global variable by
the name counter:
1. var counter = 0;
2.
3. function incrementCounter() {
4. counter++;
5. }
6.
7. function logCounter() {
8. [Link](counter);
9. }
10.
11. incrementCounter();
12. logCounter();
In the subsequent functions logCounter and
incrementCounter, we attempt to access and modify this
global variable counter. The problem with using a global
variable is that it is accessible to the entire application.
Intentionally or unintentionally, you might access or modify
this global variable in another file. In the case of large-
scale applications, it would become extremely difficult to
track changes and maintain global variables.

Problem statement 2
You might have encountered the following code while
studying asynchronous JavaScript and ES6:
1. getData(function(response1) {
2. getMoreData(response1, function(response2) {
3. getEvenMoreData(response2, function(response3) {
4. // ...and so on
5. });
6. });
7. });
There is no upper limit on how many callback functions you
can use in your code. As you can see from the coding
example above, the readability of the code decreases with
the increase in the number of nested callback functions. At
one point, it might reach a junction where the developer
might get lost with the number of requests to handle.

Problem statement 3
The following example looks pretty innocuous:
1. function createUser(name, age) {
2. return {
3. name: name,
4. age: age,
5. greet: function() {
6. return `Hello, my name is ${[Link]} and I am
${[Link]} years old.`;
7. }
8. };
9. }
10.
11. const user1 = createUser('Alice', 25);
12. const user2 = createUser('Bob', 30);
We have a function createUser that takes two arguments
name and age and returns an object. While this looks like
a proper solution, there are several problems:
• If multiple functions or parts of your code create the
same Person object, every function would need to
replicate or maintain consistency to create these
objects. A slight inconsistency might break your entire
application.
• The above code exposes the creation logic of the
object, and it is not encapsulated within a proper class
or a constructor. It is against the principles of object-
oriented programming (OOP) concepts.

Problem statement 4
Here, we have another example related to objects in the
following code:
1. const user = {
2. name: 'John',
3. age: 30,
4. };
5.
6. function updateUser(newUser) {
7. [Link](user, newUser);
8. }
9.
10. updateUser({ age: 31 });
11. [Link](user);
In this example, we have already declared an object. We
then create a function that takes the original object and
assigns it a new value. In this case, we are directly
mutating an object. It could be potentially dangerous. We
might not be able to track the mutations effectively, and a
wrong value might even crash the application.

Problem statement 5
In the following example, we have a function taking two
arguments and dividing them:
1. function divide(a, b) {
2. return a / b;
3. }
4.
5. try {
6. const result = divide(10, 0);
7. [Link](result);
8. } catch (error) {
9. [Link]('Error:', [Link]);
10. }

Note: In JavaScript, dividing by zero does not throw


an error but returns Infinity for positive numbers or -
Infinity for negative numbers. The explanation
highlights the importance of proper type-checking to
prevent unexpected results, such as attempting
operations with invalid inputs. While the catch block
ensures the application would not crash, type
validation before invoking the divide function would
enhance reliability by avoiding unintended outputs.

In the preceding section, we have highlighted only a


handful of prevalent errors frequently encountered by
JavaScript developers during coding. These errors have the
potential to cause application crashes, resulting in
diminished user engagement. It is apparent that these
issues are widespread, and encountering such scenarios is
common. Developers grappling with these familiar
challenges have devised solutions over many years. Over
time, these solutions have coalesced into recurring themes
or patterns when addressing specific problems.
Occasionally, these patterns can be applied to resolve
analogous issues. These sets of solutions addressing similar
problems are therefore known as Design Patterns.

Design Patterns
Now that we have seen a few live examples of problem
statements and how a common theme or pattern can help
resolve them, let us go ahead and formally describe Design
Patterns. We can think of Design Patterns as a vocabulary
for learning a language. Design Patterns provide a proven
solution for a particular set of problems. Having an in-
depth knowledge of Design Patterns gives us an insight into
common issues faced by developers over the years. As a
famous saying goes, Prevention is better than cure, that is,
by having prior knowledge about these recurring problems,
we are already one step ahead by not falling into the same
trap. Moreover, possessing a solution carved and shaped
over the years by developers who have constantly faced
this problem and figured out a solution by applying several
permutations, combinations, and logic, gives us an added
advantage in writing robust code that will not crash our
application.
To sum up the above statement, a common definition that
you will find across the web when you search for Design
Patterns would be something along these lines:

A reusable solution to a commonly occurring problem within


a given context in software design or development.

It is imperative to note here that Design Patterns do not


belong to a particular language, or, in other words, they
are not bound to solve a problem of a specific language.
Design Patterns are ideas or concepts that do not adhere to
a particular language. At the same time, it is not mandatory
to implement them in every occurrence of the problem
statement. They serve as guidelines rather than strict rules.
While these patterns may offer optimal solutions in certain
contexts, there is a risk of excessively complex code if
applied indiscriminately, potentially leading to over-
engineering. Additionally, you might not require most of
these Design Patterns in the latest frameworks where they
are already abstracted.

Origins of Design Patterns


To understand Design Patterns better, we first need to
understand the origins of Design Patterns. We need to
understand who were the brains behind the invention of
patterns and crucially, we need to comprehend their reason
for discovering and postulating these patterns. It is
essential that we understand the kind of problems that are
solved using patterns and the eventual usage of these
Design Patterns in JavaScript.
Technically, the concept of patterns has been known to
mankind for an eternity. In simple words, humans have
often discovered that one can apply the same solution to a
problem occurring recursively in several projects. Often,
people tend to give this solution a name, and we start using
this solution in every occurrence of such a problem.
For instance, let us take the real-life analogy of seasons.
Over a period of several years, native inhabitants of specific
geographical areas realised that the weather repeats itself
every few months. Eventually, this led to the coinage of the
term we now know as seasons. Put plainly, it is a
predictable sequence of weather patterns that occur at
specific intervals. Understanding the onset of each season
and the anticipated weather during that period enabled
individuals to effectively prepare and adapt to the
forthcoming conditions.

A Pattern Language, 1977


Historically, the term pattern was first used by an early
architect, Christopher Alexander. Known for his writings on
the challenges encountered in architectural and urban
design, he observed that specific design elements reliably
addressed particular conditions during the construction of
buildings and towns. For example, Alexander theorized the
optimal height of windows, the ideal number of levels
essential for a building’s Structural stability, the necessary
size of green spaces within a neighbourhood, among other
considerations.
Christopher Alexander, in collaboration with fellow
architects Sarah Ishikawa and Murray Silverstein, applied
his discoveries to develop a comprehensive pattern
language intended to assist people in designing on a larger
scale. They introduced this language in a paper titled A
Pattern Language in 1977, subsequently expanding it into a
book with the same title.

The Gang of Four book, 1994


In the 1990s, approximately two decades after Christopher
Alexander’s ground-breaking contributions, four software
engineers drew inspiration from his simple patterns to
integrate them into everyday coding practices. It is worth
noting that while patterns had been present in software
engineering for some time, they had not been formally
introduced or globally implemented. However, the book
penned by these four software engineers proved
revolutionary for the field. Erich Gamma, John Vlissides,
Ralph Johnson, and Richard Helm authored the seminal
work Design Patterns: Elements of Reusable Object-
Oriented Software in 1994. Within its pages, they outlined
a compendium of 23 object-oriented Design Patterns
designed to address common challenges encountered by
software engineers, aiming to mentor novice developers in
enhancing their coding expertise.
This book gained immense popularity and significance,
persisting as an iconic reference that numerous engineers
still regard as a guiding authority in comprehending Design
Patterns. Due to its extensive title, people began referring
to it as the book by the gang of four, later abbreviated to
The Gang of Four (GoF) book. Interestingly, although the
authors did not specifically tailor these concepts for
JavaScript, their applicability extends across various object-
oriented languages.

Classification of Design Patterns


Design Patterns can be differentiated based on their
complexity, level of intricacy, and the extent to which they
apply across the entire system under design.
To give you a simple analogy for the classification, think of
Design Patterns as tools in a toolbox. In a typical toolbox,
you will find several tools ranging from a simple
screwdriver to a more complex power drill. The application
of these tools varies based on the complexity of the tasks
they are involved in.
For instance, a screwdriver could be used for simpler tasks
like assembling furniture or tightening the loose screws of
an appliance. Similarly, a power drill with its multiple
attachments can be used for a variety of complex tasks that
range from drilling holes to sanding surfaces.
On similar lines, Design Patterns are divided or classified
based on their complexity and the extent to which they
need to be applied across the system. Following are the
primary three categories in which Design Patterns are
divided into:
• Creational Design Patterns
• Structural Design Patterns
• Behavioural Design Patterns

Creational Design Patterns


As the name goes, Creational Design Patterns concentrate
on handling the object-creation mechanism for
applications. These patterns help in controlling the process
of object creation to avoid adding complexity to the project.
Some patterns that fall under this category include
Singleton, Factory, Builder, and Prototype.

Structural Design Patterns


Structural Design Patterns deal with object composition.
These patterns help identify relationships between different
objects. These patterns are extremely crucial in situations
where a part of the system changes. In such a scenario,
these patterns ensure that changing a part of the system
does not impact the entire application. A few Structural
Design Patterns that we will be covering in further
chapters include Adapter, Decorator, Composite, and
Façade.
Behavioural Design Patterns
Behavioural Design Patterns are concerned with effective
communication between objects. These patterns help in
assigning responsibilities between objects. They abstract
the actions from objects that take the action. Examples of
behavioural Design Patterns that we will be covering in the
upcoming chapters include Observer, Strategy, Command,
and State.

Advantages of Design Patterns


We have now reached a stage where we have a better
understanding of Design Patterns and its origins. We have
seen its adaptation from The GoF book to JavaScript. We
have further seen its discreet classification to solve a
specific set of problems arising while writing JavaScript
code. To drive this point clear, let us try to understand the
advantages offered by Design Patterns to developers,
irrespective of their experience with JavaScript. Learning
and understanding Design Patterns can be advantageous
for developers for the following reasons:

Reusability
Design Patterns encapsulate solutions to recurring
problems in a generalized way. Once a pattern is
implemented and tested, it can be reused in different
scenarios, saving development time and effort. For
example, the Singleton Pattern provides a way to ensure a
class has only one instance, which can be reused across the
application.

Maintainability
Design Patterns promote a structured approach to
development. By following Design Patterns, code becomes
far more organised and modular. Since your code will be
more modular, it calls for separation of concerns. It will be
easier to maintain your code and handle errors in a better
fashion.

Scalability
Design Patterns encourage users to follow a module
approach, that is, to compartmentalise or divide your code
into smaller meaningful and independent sections. This
practice of dividing your code into meaningful atomic
pieces, in turn helps make your code scalable. Since your
code is meaningfully compartmentalised, it would be easier
to add new code or update the existing code without
making major modifications to the existing code, thereby
avoiding serious application crashes.

Common vocabulary
As you might have observed in the section Types of Design
Patterns, Design Patterns basically help in creating a
common set of words or phrases that can be easily
communicated between teams. In simpler words, it is
creating an instance of technical language that the
developers in a team can use to explain each other the
problems they intend to resolve. It is easier to communicate
with larger teams by using pattern names (like Factory,
Singleton) instead of trying to explain a lengthy piece of
code optimisation. It helps reduce misunderstandings and
improves collaboration within the team.

Abstraction and encapsulation


A major advantage of using Design Patterns is abstraction
by concentrating more on high-level designs rather than
specific implementations. This abstraction helps in hiding
complex implementation logic behind simpler interfaces. At
the same time, it allows for changes to be localised,
preventing widespread impact across the codebase.

Performance improvement
Let us take the example of a Design Pattern, Flyweight,
that we will be covering in upcoming chapters. Flyweight
Design Pattern calls for objects to share common data
resources. This can improve performance significantly by
avoiding duplication while dealing with a large number of
similar objects.

Proven solutions
The primary purpose behind employing Design Patterns is
their status as verified solutions. Formulated through years
of experimentation, refinement, and addressing recurring
challenges, these patterns bestow robustness upon your
code by relying on established and proven practices.

Faster development
Design Patterns offer tried and tested solutions to
recurring issues. This spares developers of the added time
and effort required to constantly seek resolutions for
repetitive problems, enabling them to focus more on
implementing business logic and resolving other critical
issues.

Facilitates evolution and adaptation


Design Patterns encourage flexibility in software systems.
When requirements change, patterns allow for easier
adaptation and evolution without fundamentally altering
the entire codebase. This adaptability is crucial for systems
to remain relevant and functional over time.
Solutions to problem statements
In the preceding section titled Common pitfalls in
JavaScript, we examined numerous challenges encountered
by JavaScript developers, emphasizing the errors within
those code snippets. Now equipped with an understanding
of what went awry and a foundational comprehension of
Design Patterns, let us endeavour to discover optimal
solutions for those problematic statements. It is important
to note that at this stage, we possess a broad awareness of
Design Patterns and their classifications. Detailed
exploration of each Design Pattern will follow in
subsequent chapters. However, for the sake of
comprehension, we will refer to the pattern names and
explore how the solutions are implemented here.

Problem statement 1
We have seen that in the following code, by directly
modifying the global variable, we are introducing possible
inconsistencies to the global object that could potentially
crash the application:
1. var counter = 0;
2.
3. function incrementCounter() {
4. counter++;
5. }
6.
7. function logCounter() {
8. [Link](counter);
9. }
10.
11. incrementCounter();
12. logCounter();
This particular problem can be resolved by Module Pattern
(Prototype Pattern). Let us first look at the solution before
understanding it in detail.
1. var CounterModule = (function() {
2. var counter = 0; // Encapsulated variable
3.
4. function incrementCounter() {
5. counter++;
6. }
7.
8. function logCounter() {
9. [Link](counter);
10. }
11.
12. return {
13. increment: incrementCounter,
14. log: logCounter
15. };
16. })();
17.
18. [Link]();
19. [Link]();
Solution explanation: The Module Pattern utilizes an
Immediately Invoked Function Expression (IIFE) to
create a private scope. In this example, the variable
counter is encapsulated within the function, making it
inaccessible from the outer scope. Only the explicitly
returned methods (increment and log) are exposed to the
outside, allowing controlled interaction with the private
counter variable. This approach helps prevent unintended
global variable modifications and promotes clean, modular
code.
Problem statement 2
In the following code, we have seen that deep nesting of
code can result in a call back hell that makes code difficult
to read, thereby increasing the chances of errors creeping
in:
1. getData(function(response1) {
2. getMoreData(response1, function(response2) {
3. getEvenMoreData(response2, function(response3) {
4. // ...and so on
5. });
6. });
7. });
Here is a solution to handle this niggling problem:
1. getData()
2. .then(response1 => getMoreData(response1))
3. .then(response2 => getEvenMoreData(response2))
4. .then(response3 => {
5. // ...and so on
6. })
7. .catch(error => {
8. // Handle errors
9. });
Solution explanation: This solution demonstrates the use
of Promises, introduced in ES6, to write asynchronous code
in a readable and sequential manner. While Promises
themselves are not directly part of the Module or Prototype
Patterns, they support modular and maintainable coding
practices by chaining operations and centralizing error
handling through the catch block. This approach improves
code clarity and minimizes the likelihood of errors being
overlooked, especially in complex asynchronous workflows.
Problem statement 3
In the preceding section, we have seen that while the code
might look appropriate, there are two major problems with
it: maintaining consistency for the object creation and the
exposure of the crucial object creation logic. Refer to the
following code:
1. // Creating objects without encapsulation
2. function createUser(name, age) {
3. return {
4. name: name,
5. age: age,
6. greet: function() {
7. return `Hello, my name is ${[Link]} and I am
${[Link]} years old.`;
8. }
9. };
10. }
11.
12. const user1 = createUser('Alice', 25);
13. const user2 = createUser('Bob', 30);
Now, let us have a look at the solution:
1. // Constructor function for creating objects
2. function User(name, age) {
3. [Link] = name;
4. [Link] = age;
5. }
6.
7. [Link] = function() {
8. return `Hello, my name is ${[Link]} and I am
${[Link]} years old.`;
9. };
10.
11. const user1 = new User('Alice', 25);
12. const user2 = new User('Bob', 30);
Solution explanation: In this above case, we use an
instance of the Creational Design Pattern called the
Constructor Pattern and a Behavioural Design Pattern
called the Observer Pattern. By introducing a constructor
in the above code, we add encapsulation to the object
instance. Now, developers no longer need to create
separate functions to generate an object as the User
instance will effectively do the job. Additionally, by directly
using the prototype property of an object, it allows us to
add the greet method directly to the object’s constructor.

Problem statement 4
We have seen that in the following code, we are making the
mistake of directly mutating an object:
1. // Mutable object
2. const user = {
3. name: 'John',
4. age: 30,
5. };
6.
7. function updateUser(newUser) {
8. [Link](user, newUser); // Mutating the original
object
9. }
10.
11. updateUser({ age: 31 });
12. [Link](user);
This could be hazardous and it will be difficult to track
these mutations that could potentially crash an application.
1. // Using [Link] to create a new object (shallow
copy)
2. const updatedUser = [Link]({}, user, { age: 31
});
3. [Link](updatedUser); // Output: { name: 'John',
age: 31 }
Solution explanation: To resolve the problem of directly
mutating an object, we have to ensure that we first create a
shallow copy of the object by passing an empty object {} as
the first argument in the [Link] method. This
ensures that we do not mutate the original object and avoid
situations that might crash the application.

Problem statement 5
In the following code, we will see that improper error
handling might cause an application to crash:
1. // Inadequate error handling
2. function divide(a, b) {
3. return a / b;
4. }
5.
6. try {
7. const result = divide(10, 0);
8. [Link](result); // This line will not execute due to
the error
9. } catch (error) {
10. [Link]('Error:', [Link]);
11. }
To resolve this problem, we have made the following
modifications to the code:
1. // Improved error handling with Try-Catch
2. function divide(a, b) {
3. if (b === 0) {
4. throw new Error('Division by zero is not allowed.');
5. }
6. return a / b;
7. }
8.
9. try {
10. const result = divide(10, 0);
11. [Link](result); // This line will not execute due to
the caught error
12. } catch (error) {
13. [Link]('Error:', [Link]);
14. }
Solution explanation: In the above solution, we have
ensured that the error is handled properly. In the original
code, the statement [Link](result); would not have
been executed because the application would crash
showing either an improper message on the UI or a blank
screen. Either of the scenarios would not be good from the
user experience perspective. We handled this problem by
introducing a condition in the original function which will
throw an error if the function attempts to divide by zero.
With this additional logic, we will avoid crashing the
application but directly navigate the code to the error
handling piece where we can gracefully avoid crashing the
application.
Having observed how Design Patterns effectively address
frequently encountered issues, further chapters will delve
into these categories extensively. We will explore the
diverse range of patterns in depth, comprehending their
applications in different scenarios and elucidating how they
contribute to enhancing our code.

Conclusion
In this chapter, we started by understanding the origins of
JavaScript and traversing through the common pitfalls
encountered by developers while coding in JavaScript. This
laid the groundwork for introducing Design Patterns, a
concept that has existed in the software engineering
industry for a long time but was formally introduced in
1994 by the Gang of Four in their seminal book, Design
Patterns: Elements of Reusable Object-Oriented Software.
We explored the high-level classification of these Design
Patterns and finally understood the benefits of introducing
them in our coding.
As we delve deeper into specific patterns and their
applications in upcoming chapters, remember that
mastering these principles is not merely about memorizing
solutions but embracing a mindset that fosters elegant,
adaptable, and scalable software design. This journey into
Design Patterns is an invitation to harness the collective
wisdom, empowering us to craft software systems that
stand the test of time and evolve gracefully in response to
changing requirements and technological advancements.

Points to remember
• Design Patterns for JavaScript are proven solutions
that have been carved and shaped over the years by
developers who have figured out solutions to commonly
recurring problems in JavaScript.
• Design Patterns are broadly divided into three
categories based on their complexity and the extent to
which they are used: Creational Patterns, Structural
Patterns, and Behavioural Patterns.
• These patterns can facilitate easy communication
between developers in the same team and provide a
common language for them to architect a software
solution effectively.
• Design Patterns can make your code reusable, and easy
to maintain, and assist you in scaling your applications
by improving their performance.
• Design Patterns are not about memorizing a solution
but embracing a mindset that fosters elegant,
adaptable, and scalable software design.

Exercises
1. What are behavioural Design Patterns primarily
concerned with?
a. Object creation mechanism
b. Understanding the relationships between multiple
objects.
c. Packaging of objects to be shared across the Internet.
d. Effective communication between objects.
2. Which of the following statement is true?
a. Design Patterns cannot be applied to object-oriented
programming languages.
b. Design Patterns are fairly new concepts that have
never been utilised or proven for their efficiency.
c. Effective modularisation achieved by implementing
Design Patterns can make your code scalable.
d. The implementation of Design Patterns is restricted to
only one problem at a given time and it can never be
reused ever again.
3. Which of the following statements depict a proper
usage of Design Patterns in JavaScript?
a. Declaring a global variable at the start of the code
and then directly accessing it to modify its content.
b. Handling errors properly to avoid a situation where a
line of code is not reachable.
c. There is no need to use a constructor to create
properties or keys for an object. We can make use of
functions to achieve this purpose.
d. Directly mutating an object without making its
shallow or deep copy.

Answers
1. b
2. c
3. b

Join our book’s Discord space


Join the book's Discord Workspace for Latest updates,
Offers, Tech happenings around the world, New Release and
Sessions with the Authors:
[Link]
CHAPTER 2
Creational Design Patterns

Introduction
Creational Design Patterns hold a prominent position in the
bedrock of JavaScript, a language firmly grounded in the
principles of OOP. Object creation stands as a critical yet
often underestimated facet of JavaScript programming.
Incorrectly creating objects not only amplifies code
complexity but also serves as a potential trigger for
application crashes. In this chapter, we aim to demystify the
intricacies surrounding object creation. We will explore
pivotal Creational Design Patterns such as Singleton,
Factory Method, Abstract Factory, Builder, and Prototype.
This exploration will provide a deep insight into establishing
adaptable and scalable object creation processes. These
patterns encapsulate creation logic, fostering the values of
code reusability, maintainability, and adaptability.

Structure
This chapter will cover the following topics:
• Object-oriented programming
• Introduction to Creational Design Patterns
• Singleton Pattern
• Factory Method Pattern
• Abstract Factory Pattern
• Builder Pattern
• Prototype Pattern

Objectives
Upon completing this chapter, you will have a greater
understanding of the role object creation plays in building
an application. With the help of the above Design Patterns,
you will be able to enhance your code quality and make your
application robust. By adopting proven object creation
mechanisms, you will foster code reusability and increase
the flexibility of your code.

Object-oriented programming
Before we explore Creational Design Patterns, it is crucial
to comprehend the context in which these patterns are
used. To achieve this understanding, it is imperative to
delve into the specifics of JavaScript as a language and how
it operates. The following sections will be based on two
programming paradigms supported by JavaScript. This
exploration will lay the groundwork for our deeper
investigation into the paradigm that ultimately leads us to a
comprehensive understanding of Creational Design
Patterns.
JavaScript supports both functional programming and OOP.
Functional programming, as the name suggests, is a
declarative programming paradigm based purely on
functions. It involves implementing functions sequentially to
solve complex problems.
OOP is a programming paradigm based on classes and
objects. In this style of coding, properties and actions are
grouped together. OOP was developed to make coding more
flexible and easier to maintain. It is the combination of
these paradigms that makes JavaScript such a robust
language for the Web.
With an eye on future reference, this section serves as a
refresher on some intricate aspects of JavaScript. Our focus
begins with a broader exploration of OOP and subsequently
delves into how OOP is applied in JavaScript. To underscore
the latter aspect, we will specifically delve into topics like
object prototypes and constructors. A solid understanding of
OOP is paramount as it forms the foundation for
comprehending Design Patterns in their entirety. If you feel
confident in your grasp of OOP concepts and their
implementation in JavaScript, you are welcome to skip the
following sections and directly proceed to the Singleton
Pattern, initiating our exploration into Creational Design
Patterns.

Classical object-oriented programming


Before we commence this section, please note that we will
provide a concise overview of the fundamental concepts of
OOP. This segment will focus solely on the principles
directly related to enhancing our comprehension of
Creational Design Patterns. Consider this section as a quick
refresher on OOP knowledge, emphasizing concepts that
are directly relevant to understanding Creational Design
Patterns more effectively.
In simple terms, OOP centers around the concept of objects
as an entity which contains data and logic to modify data to
structure software programs. Let us take a moment for a
quick review to understand objects, classes, and the four
principles of OOP.

Objects
To better understand the concept of objects from an OOP
perspective, let us use a real-life analogy of cars depicted in
the following figure:

Figure 2.1: A car as an object possessing properties and methods


An object, as depicted in the image above, is a distinct
entity that encapsulates properties and methods. Using the
example of a car in the image, a real-life analogy can help
illustrate this concept. A car, as an object, possesses various
properties such as a specific color, a defined number of
wheels, a steering wheel, an engine, and more. Additionally,
it can perform actions like turning left or right.

Classes
To comprehend the concept of JavaScript classes, let us
imagine a real-life analogy of a factory that produces cars:
Figure 2.2: A class acts like a factory for producing similar objects
It’s established that objects hold data resembling real-life
entities. Now, envision classes as factories responsible for
producing these objects. Alternatively, you can liken classes
to blueprints that guide the creation of objects. In essence,
classes serve as templates, and objects represent instances
generated from these templates. It is important to note that
numerous objects can be instantiated using a single class,
demonstrating the flexibility and efficiency of this OOP
approach.

Principles of OOP
OOP works on these four principles:
• Abstraction: Abstraction involves presenting solely the
crucial features or functionalities to the user while
concealing implementation specifics that are irrelevant
to the user. To give you a real-life analogy, a user is
unconcerned about the intricacies of how a car starts or
the internal mechanisms responsible for its movement
upon ignition. The user’s primary concern lies in the
action: pressing a button or turning the ignition key
should initiate the vehicle’s operation.
• Encapsulation: Encapsulation refers to the practice of
confining properties and methods within a class,
ensuring they remain inaccessible from outside the
class. To illustrate encapsulation, consider a real-life
scenario such as a bank account. Sensitive information
like the user’s account balance or account number is not
directly accessible. Instead, access to this data is
mediated through well-defined interfaces like withdraw
or deposit. These interfaces act as protective barriers,
preventing unauthorized access and safeguarding the
user’s confidential information.
• Inheritance: Inheritance embodies the idea of enabling
properties and methods from a parent class to be
accessible within its child class. An analogy that
illustrates inheritance in real life is the concept of a
family. Much like how children inherit physical
attributes such as eye color, hair color, or certain
Behavioral traits from their parents, in programming,
child classes inherit attributes and behaviors from their
parent class.
• Polymorphism: Polymorphism, derived from the Greek
words poly meaning many and morph meaning forms,
refers to the ability to take on different forms. A
straightforward example to understand polymorphism is
through musical instruments. Consider how the same
musical notes played on a piano and a guitar produce
distinct sounds. Likewise, in programming, an object or
a class can exhibit various behaviors and results while
referencing the same interface, demonstrating the
concept of polymorphism.

Object-oriented programming in JavaScript


Now that we have a solid understanding of OOP and its
principles, let us discuss how JavaScript incorporates OOP.
The implementation of OOP in JavaScript deviates slightly
from its traditional usage in other languages. This
distinction in approach adds a unique flavor to how OOP is
realized within the context of JavaScript.
In JavaScript, an object is connected to a prototype. Let us
explore the concept of a prototype to gain a clearer
understanding.

Object prototype
Put simply, prototypes serve as the mechanisms through
which JavaScript objects inherit features from one another.
Let us try a simple coding exercise to figure out this
enigmatic concept.
Open your preferred browser’s console and type in the
following code:
1. const person = {
2. name: "Rushabh",
3. greet() {
4. [Link](`Hi, my name is ${[Link]}!`);
5. }
6. }
7.
8. [Link]();
9. [Link](person);
Code explanation: In the above coding example, we can
see that we have a simple object called person. This object
has a property called name whose value is Rushabh (feel
free to replace it with your name) and a method called
greet which prints the property name in a template literal
format. We then call the greet method of the person object
and in the next line, we print the person object itself. When
you press enter or execute the above coding example, you
will get a result that looks something like this:
Figure 2.3: Viewing prototype structure on console
In Figure 2.3, we observe that the greet method has been
successfully called and it prints the correct result of Hi, my
name is Rushabh! However, in the next line, we can see
that although our object person had only one property and
one method, the object that has been printed on the console
shows several methods and properties that we have not
defined in our object initialization. Moreover, all these
properties and methods are accessible to us. For instance,
let us try calling the valueOf method shown in Figure 2.3
and the response that we receive looks like this:

Figure 2.4: Viewing the result of valueOf method execution of the person object
prototype on console
As illustrated in Figure 2.4, the execution of
[Link]() was successful, producing the exact
response observed in Figure 2.3. This demonstrates that the
object initialized earlier in this section acquired several pre-
defined properties and methods immediately upon creation.
The term used for these pre-defined properties and methods
integrated into the structure of an object is prototypes.
To restate it formally, every JavaScript object inherently
possesses a built-in property known as its prototype. In
accordance with its English definition, a prototype is an
original model upon which something is patterned.
In the upcoming section, we will delve into the concept of
constructors in OOP. By the conclusion of this section, we
will examine how the object prototype implements the
principle of inheritance in JavaScript.

Constructors
In the preceding sections of this chapter, we have observed
that classes in OOP serve as a kind of factory method for
generating objects. Similar to traditional OOP languages
like Java, classes in JavaScript feature a special method
called a constructor. This constructor is employed for
initializing an object instance of that particular class. Before
delving into a more detailed exploration, let us take a brief
look at an example of a constructor in JavaScript:
1. class Person {
2. constructor(name) {
3. [Link] = name;
4. }
5.
6. greet() {
7. [Link](`Hi, my name is ${[Link]}!`);
8. }
9. }
10.
11. const teacher = new Person("Rushabh");
12.
13. [Link]();
Drawing inspiration from the example utilized in the
preceding section to grasp object prototypes, we have
transformed the person variable from that example into a
class named Person. As evident from the above code
example, we have incorporated the greet method previously
used in the object prototype example. The notable
distinction between the two code examples lies in the
inclusion of a method named constructor. This
constructor method takes an argument called name and
assigns it to the local data variable name within the class.
This local variable name can subsequently be utilized
throughout the entire class.
In formal terms, a constructor is a special method invoked
during the initialization of a class. Its purpose is to facilitate
any custom initialization tasks that need to be executed
before any of the object’s methods are called.
The structure of a constructor changes if it is a derived
class or a class which is extending or inheriting its
properties from a parent class. In case of a derived class,
this is what the code looks like:
1. class Person {
2. constructor(name) {
3. [Link] = name;
4. }
5.
6. introduce() {
7. return (`Name of person: ${[Link]}`);
8. }
9. }
10. class Student extends Person {
11. constructor(name, id) {
12. super(name);
13. [Link] = id;
14. }
15.
16. introduce() {
17. return (`${[Link]()}, Student ID:
${[Link]}`);
18. }
19. }
20. let student1 = new Student('Mukul', 22);
21. [Link]([Link]());
When we execute this code in the browser’s console, we
receive the following output:

Figure 2.5: Output seen for a derived class constructor


Code explanation: As evident from Figure 2.5 and the
executed code example in the browser’s console, we have a
class named Person resembling previous code examples. It
incorporates a constructor to initialize the local variable
name whenever a new instance of the Person class is
created. In addition, we introduce a new class called
Student, connected to the Person class using the
JavaScript keyword extends. Within the Student class, we
employ the constructor method with two arguments, name
and id. Using the super function, we pass the name
parameter to it before assigning the id to the local instance
of the Student class. Both the Person and Student classes
possess their respective introduce methods, printing
different statements based on their individual local states.
When we create a new instance of the Student class and
print its introduce method, we obtain the response depicted
in Figure 2.5.
The extends keyword and the built-in JavaScript method
super establish a parent-child relationship between Person
and Student. By using extends, we effectively inherit the
properties of the Person class in the Student class.
Additionally, employing the super keyword ensures that the
name argument received in the Student class constructor
is appropriately supplied to the parent class Person and
initialized there. Ultimately, we utilize the super method
again in the introduce method of the Student class to
invoke the parent’s instance of the same method and
execute it.
Constructors are typically called when a developer uses the
new keyword to create a new instance of the class. The
constructors that we explored in this section can also be
deemed as a Design Pattern in itself.

Inheritance in JavaScript
In the preceding section, we saw a code example depicting
derived classes. In that example, we created two classes –
Person and Student. The Student class extends the
Person class, thereby deriving all its properties and
methods.
By using the extends keyword and the super method in the
code example from the preceding section, we essentially
implemented the OOP principle of inheritance in JavaScript.
To help you comprehend the importance of prototype and
constructor in the JavaScript brand of OOP, let us enter the
previous code again in our browser console with a minor
difference. Instead of executing the introduce method of
student1, we will instead print the student1 variable on
our web console:
1. class Person {
2. constructor(name) {
3. [Link] = name;
4. }
5.
6. introduce() {
7. return (`Name of person: ${[Link]}`);
8. }
9. }
10. class Student extends Person {
11. constructor(name, id) {
12. super(name);
13. [Link] = id;
14. }
15.
16. introduce() {
17. return (`${[Link]()}, Student ID:
${[Link]}`);
18. }
19. }
20. let student1 = new Student('Mukul', 22);
21. [Link](student1);
When we run this code, our output looks like this:
Figure 2.6: Viewing the structure of a derived class instance on the web
console
In Figure 2.6, we can find the object instance created by the
Student class. More importantly, the object has two
prototypes assigned to it. The first one possesses the
constructor method and the introduce method belonging
to the Student class, while the second one possesses the
constructor and the introduce method belonging to the
Person class.
This illustrates the intricate hierarchy and the levels of
inheritance accessible to an object derived from a parent
class. Such an object not only encapsulates the data and
methods specific to its own instance but also inherits the
data and actions from the entire hierarchy above it.
When you try to access a property of an object, JavaScript
will try to find that property in the object itself. If it is not
able to find that property on the object, it will then search
for this property on the prototype of the object. If the
property still cannot be found, it will search the prototype of
the prototype, and so on, until the value is found. In case
the property is not found until the end, JavaScript then
returns undefined.

Polymorphism in JavaScript
Polymorphism is a key concept in object-oriented
programming, allowing a single method name to be used in
different forms based on the context. In JavaScript,
polymorphism is achieved through method overriding in
classes that involve inheritance. For example, consider the
following:
1. class Person {
2. constructor(name) {
3. [Link] = name;
4. }
5.
6. introduce() {
7. return `Hello, my name is ${[Link]}.`;
8. }
9. }
10.
11. class Student extends Person {
12. constructor(name, age) {
13. super(name);
14. [Link] = age;
15. }
16.
17. introduce() {
18. return `Hi, I'm ${[Link]}, and I'm ${[Link]}
years old.`;
19. }
20. }
21.
22. // Example usage:
23. const student = new Student("Mukul", 22);
24. const person = new Person("Raj");
25. const introducePeople = [student, person];
26.
27. [Link](person =>
[Link]([Link]()));
In this example, both the Person and Student classes have
an introduce method, but their implementations differ. The
introduce method in the Person class provides a generic
greeting, whereas the introduce method in the Student
class adds age-specific details. This demonstrates
polymorphism, as the method behaves differently depending
on the object type. This flexibility makes the code adaptable
and allows you to handle diverse objects in a unified
manner.

Class-based OOP vs. OOP in JavaScript


With a comprehensive understanding of OOP principles, the
introduction of Object prototype in JavaScript, and the
utilization of constructors in JavaScript, we are well-
equipped to discern the distinctions in the implementation
of OOP between its classical stance and its manifestation in
JavaScript.
Class-based OOP OOP in JavaScript

In class-based OOP, classes and In JavaScript, we do not need to rely on


objects are two different entities the creation of class instance to generate
where objects are always created an object. Objects can be created using a
as an instance of the class. To function or an object literal. This makes
instantiate an object, we need to working with objects in JavaScript more
use the new keyword to create a lightweight than classical OOP.
fresh instance of the class and
thereby, create an object.

In class-based OOP, since objects In JavaScript, when a subclass is


are generated using a class generated, a single object is created,
instance, the concept of object amalgamating the properties of the
prototype does not exist. subclass with those further up the
hierarchy. The prototyping mechanism
represents each level of the hierarchy with
a distinct object, connecting them through
the __proto__ property. This property
enables objects to delegate tasks to
another object. Remarkably, it is feasible
Class-based OOP OOP in JavaScript
to dynamically alter or entirely replace
these delegates during runtime, rendering
the system highly flexible to work with.

Table 2.1: Class-based OOP vs. OOP in JavaScript


While constructors and prototypes can be employed to
implement class-based OOP patterns in JavaScript, using
them directly for features like inheritance can be
challenging. Recognizing this challenge, JavaScript offers
additional features layered on top of the prototype model.
These features more directly align with the concepts of
class-based OOP, providing developers with a more intuitive
and robust way to implement such patterns.

Introduction to Creational Design Patterns


Creational Design Patterns in JavaScript are a segment of
Design Patterns that deal with creation of objects. In the
preceding sections, we conducted an in-depth
understanding of the role of objects in an OOP environment
and how OOP is implemented in JavaScript. Now that we
have a concise idea of the role objects play in JavaScript, we
can better understand the importance of correctly creating
objects.
Creational Design Patterns provide solutions for creating
objects in a way that is flexible, efficient, and scalable.
These patterns abstract the instantiation process, making
the system more independent of how its objects are created,
composed, and represented.
In a real-life analogy, the effectiveness of water as a fire
extinguisher becomes evident only in the presence of an
incendiary fire. Drawing a parallel, to grasp the significance
of Creational Design Patterns, let us initially delve into the
importance of object creation and the various challenges
that can emerge if specific principles are not adhered to
correctly.

Importance of Object creation


Object creation is a pivotal aspect of software development
that influences the overall structure, performance, and
maintainability of an application. The way objects are
created can significantly impact the application’s behavior
and resource utilization. Proper object creation contributes
to:
• Code organization: Well-structured object creation
promotes a clean and organized codebase, making it
easier to understand and maintain.
• Reusability: Proper object creation allows for the
creation of reusable components and modules, reducing
redundancy and improving code reuse.
• Encapsulation: Object creation supports encapsulation,
allowing the bundling of data and methods within
objects and restricting external access to internal
implementation details.
• Scalability: Effective object creation patterns facilitate
scalability, enabling the application to handle growing
complexities without sacrificing performance.

Problems with incorrect object creation


If objects are not correctly created, it can lead to numerous
problems that can potentially impact the stability of the
entire application. Here are a few instances along with
relevant examples that showcase incorrect object creation
and the problems it can lead to:

Memory leak
Incorrectly managed object creation, such as not properly
releasing resources or forgetting to unregister event
listeners, can lead to memory leaks. Over time, this can
cause the application to consume excessive memory and
eventually crash.
1. function EventListener() {
2. [Link] = [];
3.
4. [Link] = function (handler) {
5. [Link](handler);
6. };
7.
8. [Link] = function () {
9. [Link](handler => handler());
10. };
11. }
12.
13. // Usage with memory leak
14. const listener = new EventListener();
15. [Link](() => [Link]('Event
triggered'));
In the preceding code example, we add an event listener to
the object listener. However, we fail to unregister this event
listener even when it is no longer needed. Over time, if
several objects are created using this instance, there will be
several open event listeners which can consume excessive
memory and crash.

Circular dependencies
Poorly structured object creation may result in circular
dependencies, where objects depend on each other in a
loop. This can lead to initialization issues, making it
challenging to create instances of objects without
encountering errors.
1. // Module A
2. const A = require('./B');
3.
4. function ModuleA() {
5. [Link] = 'Module A';
6. [Link] = new [Link](); // Circular
dependency
7. }
8.
9. // Module B
10. const B = require('./A');
11.
12. function ModuleB() {
13. [Link] = 'Module B';
14. [Link] = new [Link](); // Circular
dependency
15. }
16.
17. // Usage
18. const instanceA = new [Link](); // Results in a
circular dependency error
In this code, modules A and B have a circular dependency
on each other. This will eventually cause problems when
creating instances.

Uninitialized variables
Objects that are not properly initialized can contain
undefined or null properties. Accessing these properties
without checking for their existence can result in runtime
errors and application crashes.
1. function UserProfile(name, age) {
2. [Link] = name;
3. [Link] = age;
4. }
5.
6. // Usage with uninitialized variable
7. const user = new UserProfile();
In the above code example, we are not passing any
arguments to the initialization of the class instance.
Therefore, the object user is essentially a ticking bomb. If
the user tries to access any of its properties, it might crash
the application.
These were a few of the potential problems that can be
faced by incorrect object creation. This segment basically
helps us understand the understated importance of creating
objects in the correct fashion. Creational Design Patterns
will help us achieve that.

Singleton Pattern
Let us begin our Creational Design Pattern journey by
learning about one of the most basic Design Patterns –
Singleton. Singleton Pattern is a Design Pattern that
postulates that a class should have only one instance, and
this instance can be accessed globally.

Problem scenario
There are two common problems faced by every developer
at some point in his/her career that can be potentially
solved by Singleton.

Scenario #1
The developer has introduced a global variable to store
crucial objects and properties, enabling accessibility
throughout the entire application. While this facilitates
widespread access to data, it also introduces a potential
issue. As the application grows in size, there arises the risk
of unintended modifications to the global object code. For
instance, consider a global object named user storing vital
data for an e-commerce application, including personal
details, delivery addresses, contact information, transaction
history, order details, and payment information such as card
details and CVV codes.
In a large-scale e-commerce application, numerous smaller
files collectively form the extensive codebase required for
the application’s operation. Imagine a scenario where a new
function is introduced, and the developer inadvertently
accesses the global variable user unintentionally overriding
it with experimental analytical data. This inadvertent
modification could lead to the deletion of all user
information, replacing it with irrelevant data. The
consequences of such accidental changes have the potential
to crash the application, as JavaScript may fail to access
properties stored in the user variable that were essential to
the application’s functioning.

Scenario #2
Consider a scenario where you are developing a web
application that involves managing user sessions. Each user
session corresponds to a unique object that encapsulates
the user’s data and state. However, you want to ensure that
there is only one session object per user, and subsequent
requests for the same user should return the existing
session object rather than creating a new one.
Without controlling the number of instances, you might
encounter issues such as duplicate user sessions,
inconsistent data, or unnecessary resource consumption. By
managing the number of instances, you can efficiently
handle user sessions and avoid potential problems related to
duplicate session data.
In essence, controlling the number of instances in this
scenario allows you to ensure that each user has only one
session object throughout their interaction with the web
application, maintaining consistency and efficient resource
usage.
In both the above scenarios, we can see that having a single
instance of an object which cannot be directly accessed or
modified can make our code robust and efficient. This is the
exact problem solved by the Singleton Pattern.

Implementation
There are several methods or approaches to implement the
Singleton Pattern in your code. In this segment, we will be
looking at one of the most efficient and effective ways to
add the Singleton Pattern to your JavaScript code. Let us
break down the implementation of Singleton Pattern into
the following steps:
1. Step 1: Private instance variable
Create a private variable inside the singleton module to
store the single instance.
2. Step 2: Private constructor function
Next, we will create a private constructor function that
creates a single instance. We need to add the logic for
creating the singleton instance in this function.
3. Step 3: Get instance method
Now that we have successfully encapsulated the
instance using private variables and a private
constructor method, now we need to discreetly provide
access to the single instance. To achieve this, we will
create a method, often named getInstance, which can
provide access to this singular instance. In this method,
we will check if the instance already exists. If it already
exists, we will not create a new instance.
4. Step 4: Usage
Finally, we will use this getInstance method to fetch the
instance of our singleton module. Subsequent calls to
this method should return the same instance created
initially.
It is important to note here that this is a basic framework or
a list of steps to create a Singleton Pattern to achieve our
purposes. However, JavaScript being a versatile language,
there can be several different ways to achieve the Singleton
Pattern, including but not limited to closures, object literals,
or classes. Feel free to use the above method or any of the
alternatives on the basis of your specific use case.

Code example
Now that we have listed down the steps to implement
Singleton Pattern in our code, let us go ahead and apply
these steps on an actual coding example:
1. class SingletonUser {
2. static #instance; // Creating a private static variable
3.
4. constructor(name) {
5. if(!SingletonUser.#instance) { //Ensuring that not
more than one instance can be created
6. SingletonUser.#instance = this;
7. [Link] = name;
8. }
9. return SingletonUser.#instance;
10. }
11.
12. static getInstance() { //Method to get instance of the
class
13. if(!SingletonUser.#instance) {
14. SingletonUser.#instance = this;
15. }
16. return SingletonUser.#instance;
17. }
18. }
19.
20. let user1 = new SingletonUser('Rushabh');
21. let user2 = new SingletonUser('Unnati');
22. [Link]("user1: ", user1);
23. [Link]("user2: ", user2);
24. [Link]("user1 === user2: ", user1 === user2);
25.
26. let user3 = [Link]();
27. let user4 = [Link]();
28. [Link]("user3: ", user3);
29. [Link]("user4: ", user4);
30. [Link]("user3 === user4: ", user3 === user4);
In the code example above, we have implemented the
Singleton Pattern by creating the SingletonUser class
following the outlined steps.
The initial step in our code is the creation of a private
variable named instance, utilizing the # notation provided
by JavaScript to ensure its privacy.
Subsequently, we establish the constructor, which is made
private through the introduction of an if condition. The
condition specifies that if an instance of the class already
exists, the code within the conditional is skipped, preventing
the reinitialization of properties and methods. Instead, it
returns the pre-existing instance.
Lastly, we have included a getInstance method for
developers seeking access to the class instance.
Let us run this code and see what output is generated:
Figure 2.7: Output for Singleton Pattern code
As depicted in Figure 2.7, despite the attempts to create
new instances of the class with different arguments passed
to the constructor, the output consistently retains and
displays the original instance created. Consequently, our
objective of establishing a single instance of a class using
the Singleton Pattern has been successfully achieved.

Applications
As we have seen in the above scenarios, the Singleton
Pattern is most useful in scenarios where you need to
ensure that a class has only one instance, and a global
access is provided to that instance. Here are some scenarios
where a Singleton Pattern may be appropriate to use:
Global configuration settings
Consider the example of spend management software like
Airbase where a user can keep track of the company
expenses, manage reimbursements, or handle international
transactions.
In online applications, maintaining a global configuration
settings feature is vital, allowing users to establish various
configurations applicable throughout the application. Within
these settings, users might define the base currency, select
a theme for the application’s appearance, determine the list
of approvers, and more. In such a scenario, applying the
Singleton Pattern becomes instrumental in ensuring that
these settings remain consistent and are not inadvertently
overridden.

Database connection management


For applications that have to rely on a remote database to
power the application, it often involves having a single point
to manage database applications. Singleton Patterns can
help ensure that only one instance handles the database
management.

Caching
In case of e-commerce applications, often there is a need to
store cached data to improve the performance and the
speed of the application. Applications often use caching to
store product information, product data, images, prices and
so on. Singleton Patterns can help maintain a single
instance of cache which can be overridden only in certain
situations.

Maintaining application state


If you are coming from a React or a Vue background, you
will have come across global state management tools like
Redux or Vuex. These tools help maintain a global state that
can be used across the entire application. It is extremely
useful for large or complicated applications. In such
scenarios, Singleton Pattern can help maintain a single
instance of the global application state.

Pros and cons


Here are some advantages of using Singleton Patterns in
your code:
• It ensures that only a single instance of the class is
created, thereby making the application more robust
and efficient.
• We get a single point of access to the instance that can
be accessed globally.
However, there are also certain disadvantages of using
Singleton Patterns:
• It violates the Single Responsibility Principle. The
Single Responsibility Principle (SRP) states that
each software module should have one and only one
reason to change. However, Singleton Patterns try to
solve two problems at the same time – ensuring that a
class has only one instance and assigning a global
access point to the singleton instance.
• Writing unit test cases for singleton classes poses
challenges. The difficulty arises due to the potential
impact of the order of execution on the values stored in
the global state, making the sequence of execution
crucial.
• During the unit testing process, there is an inherent risk
that another component or module might alter the global
state value or instance. In such situations, debugging
errors become challenging, as the source of the problem
may be obscured by the complexities of the global state
interactions.

Factory Method Pattern


Factory Method is a Creational Design Pattern that provides
an interface for creating objects in a super class, but allows
subclasses to alter the types of objects that will be created.

Problem scenario
Consider the example of a versatile logging system to
elucidate the problem addressed by the Factory Method
Design Pattern. For the purposes of this illustration, let us
assume we are developing a web application that
accommodates various types of log outputs. For example,
the application could support logging to a console, logging
to a file, and logging to a remote server.
Upon close examination of this scenario, it becomes
apparent that a substantial portion of the code within the
three distinct categories would share similarities. The
divergence arises primarily in the final stage where the
logging needs to be directed to a specific output.
Simultaneously, for scalability, it is desirable to construct an
application that allows the addition of new log formats in
the future without requiring extensive modifications to the
original code. This is where the factory method Design
Pattern comes into play, addressing the need for a flexible
and extensible logging system.

Implementation
In this section, we will outline the steps typically involved in
implementing a factory method Design Pattern in code. It is
understandable that these steps might initially appear
confusing and intimidating. However, as we delve into the
code example in the next section and correlate these steps
to it, the process will become clearer and more
comprehensible.
In this segment, we have three characters – a creator, a
product, and a client.
1. Step 1: Define the product interface
Create an interface or an abstract class that declares the
common interface for all the concrete products.
2. Step 2: Implement concrete products
Create concrete classes that implement the product
interface, representing the different types of products.
3. Step 3: Define the creator interface
Create an interface or an abstract class that declares the
factory method for creating products. Traditionally, you
can name it as createProduct. This class may also
include other methods that operate on the products.
Think of the factory method as a template or an empty
method that will be overridden by the creators to
generate a method suitable to their needs. The
important thing to note here is that all creators should be
referencing this common factory method.
4. Step 4: Implement concrete creators
Create concrete classes that implement the creator
interface. Each concrete creator class overrides the
factory method to produce a specific type of product.
5. Step 5: Client code
In the client code, create instances of concrete creator
classes and use them to create products. The client code
should call the factory method to obtain products
without specifying their concrete classes.
In this segment, we have now defined the five steps
required to create a factory method. Now, let us look at a
code example to understand the method to implement these
five steps in code.

Code example
In the previous section, we listed down the steps to
implement factory method Design Pattern for our codebase.
Now, let us try to correlate those steps with the help of a
code example. To grasp the implementation of the factory
method Design Pattern, let us initially acquaint ourselves
with the key players or main characters integral to this
Design Pattern. To facilitate our comprehension of these
characters and the Design Pattern as a whole, let us draw
parallels with the example of a real-life factory.

Step 1: Define the product interface


For the sake of our understanding, let us consider the
example of a factory building vehicles. To begin with, before
jumping into creating a factory for our vehicles, we first
need to lay down the blueprint or the skeleton of our
vehicle. To achieve that, we need to create an object for the
vehicles that we wish to produce. We shall replicate that
with the help of a class that would act like an interface for
our base vehicle:
1. class Vehicle {
2. constructor(type, model) {
3. [Link] = type;
4. [Link] = model;
5. }
6.
7. display() {
8. [Link](`${[Link]}: ${[Link]}`);
9. }
10. }
In the above example, we have created a simple class
Vehicle that defines two properties type and model.
Additionally, it also possesses a method display to display
its properties. For a real-life analogy, this class acts as a
base for building any kind of vehicle.

Step 2: Implement concrete products


In our example, two products that our vehicle-building
factory can produce are a car and a bicycle. Considering
that a car and a bicycle are both types of vehicles, we will
create two classes: Car and Bicycle. These classes should
inherit their properties from the parent class, Vehicle.
1. class Car extends Vehicle {
2. constructor(model) {
3. super('Car', model);
4. }
5. }
6.
7. class Bicycle extends Vehicle {
8. constructor(model) {
9. super('Bicycle', model);
10. }
11. }
In this code example, the Vehicle class serves as an
abstract base class that defines the common structure and
behavior shared by all vehicles. The Car and Bicycle
classes are concrete implementations that extend the
Vehicle class. The super method in their constructors
ensures that the type and model are passed to the Vehicle
class for initialization. This setup leverages the OOP
principle of inheritance to reuse and organize shared
properties while allowing specialized implementations in the
derived classes.

Step 3: Define the creator interface


Up to this point, we have established the base class,
Vehicle, providing a unified interface for the two products
that our vehicle-building factory can generate. Furthermore,
we have crafted the classes for these two products,
ensuring that these newly created classes inherit the
properties and methods encapsulated in the Vehicle class.
However, it is important to note that at this juncture, we
have not yet created any factory. We have solely set down
the rules or the skeleton for the vehicles we need to
produce.
Having established the guidelines for our products, let us
construct the base factory that serves as a shared factory
for creating both our products. It is crucial to recognize that
the rationale behind creating a base factory or a creator is
to provide a framework for adding more products in the
future while adhering to the principles or rules we have
defined. This approach enables us to seamlessly incorporate
new products into our collection of vehicles without
necessitating extensive code modifications.
1. class VehicleFactory {
2. createVehicle() {
3. throw new Error('createVehicle method must be
overridden');
4. }
5.
6. assemble() {
7. const vehicle = [Link]();
8. [Link](`Assembling ${[Link]}`);
9. return vehicle;
10. }
11. }
In the above code example, we have created a class for our
vehicle factory. As mentioned earlier, at this stage of our
process, we wish to create a function that can be overridden
by the individual factory methods for building their
individual products. At the same time, it can contain several
important properties or rules that might be commonly
required by the concrete creator factories for the individual
products, car and bicycle.
Hence, we have a function called createVehicle that can be
overridden by the child factories for building individual cars
and bicycles. At the same time, we have also created a
function called assemble that do not require any
modifications by the child factories. This function assemble
can be used by both the concrete creator factories (car and
bicycle) and they do not need to override it.

Step 4: Implement concrete creators


Up to this point, we have now defined the guidelines for our
products – cars and bicycles. Since they both belong to the
category of vehicles, we have ensured that both are derived
from a common product interface. We have also created a
base factory that produces all sorts of vehicles. This will
serve as the parent class for the individual factories
required to build cars and bicycles.
At this point now, from a real-life analogy perspective, we
would require a separate set of practices and equipment for
building a car and another separate set for building a
bicycle. Think of these individual sets as a sub-factory of
sorts within the main factory. Essentially, these sub-
factories are the creators of the individual cars and bicycles
that we wish to produce. We shall now create these
concrete creator instances:
1. class CarFactory extends VehicleFactory {
2. createVehicle() {
3. return new Car('Sedan');
4. }
5. }
6.
7. class BicycleFactory extends VehicleFactory {
8. createVehicle() {
9. return new Bicycle('Mountain Bike');
10. }
11. }
In the above example, we have now created two classes -
CarFactory and BicycleFactory, that inherit the properties
and methods of the parent class, VehicleFactory. These
two classes will serve as the factories required for building
the cars and bicycles respectively. You will have also
noticed that we have overridden the crucial function of
createVehicle as each of these factories might follow a
separate process of creating their individual products. We
have not overridden the assemble function as both the
concrete creators will require the same content from the
assemble function.

Step 5: Client code


Finally, we have everything in place to start building our
cars and bicycles. With the guidelines to build the products,
individual product classes, and the factory method ready,
we can now assemble the desired products using the client
code:
1. const carFactory = new CarFactory();
2. const bicycleFactory = new BicycleFactory();
3.
4. const car = [Link]();
5. const bicycle = [Link]();
6.
7. [Link](); // Output: Car: Sedan
8. [Link](); // Output: Bicycle: Mountain Bike
In this code:
1. Factory instances: We create instances of CarFactory
and BicycleFactory.
2. Assemble method: The assemble method is called,
which internally uses the createVehicle method of the
respective factory classes.
3. Overriding behavior: Each concrete factory
(CarFactory and BicycleFactory) overrides the
createVehicle method to produce their respective
products. For example, CarFactory produces a Car
instance with the model Sedan.
When the display method is invoked on the assembled
objects, it utilizes the inherited behavior from the parent
Vehicle class to output the relevant details of the product
(e.g., Car: Sedan or Bicycle: Mountain Bike).
This structured approach might feel like over-engineering
for small code examples, but it becomes indispensable as
applications scale, ensuring flexibility, maintainability, and
adherence to the Factory Method Design Pattern.

Applications
As we have seen in the above scenario, the factory method
is an intricate Design Pattern extremely useful for scenarios
where you need to have a base plan for creating several
spinoffs. In other words, a common factory that will serve to
create several products having some similar properties but
created in a different individual way. Let us have a look at
some of the common applications of the Factory Method
Pattern.

UI Component Libraries
One of the best places where the factory method Design
Pattern shines is probably, the case of building UI
Component Libraries. The Factory Method Pattern can be
used to allow users to create various UI elements (e.g.,
buttons, forms, dialogs) without exposing the complexities
of the underlying implementation.

Data access layers


As the term states, this is a layer where the data can be
accessed by a component. We might have different data
storage implementations like SQL database, NoSQL
database, and so on. A factory method can be used to create
the appropriate data access objects based on the storage
type.

Game development
In game development, the Factory Method Pattern can be
useful for creating different types of game objects (e.g.,
characters, weapons, enemies) based on the current game
state.

Internationalization
For applications that support multiple languages, a factory
method can be used to create language-specific instances or
components.

Pros and cons


Here are some of the advantages of using Factory Method
Patterns in your code:
• Factory Method Design Pattern avoids tight coupling
between the creator and the concrete products. By
achieving this, it makes our code more flexible and
allows it to be scalable.
• It follows the SRP, which states that each software
module should have one and only one reason to change.
In this case, the product creation can be moved to one
separate place in the program, thereby making the code
easier to support.
• The biggest advantage is that we can introduce new
types of products into the program without breaking the
existing client code.
However, there is one major disadvantage in using Factory
Method Design Pattern:
As we have seen before, there is always a chance of the
code becoming more complicated since we are introducing
a host of new subclasses to implement the pattern. It would
make sense to introduce this on larger applications that
involve several components belonging to a similar class or
possessing similar properties.
Abstract Factory Pattern
The Abstract Factory Design Pattern is a Creational Design
Pattern that provides an interface for creating families of
related or dependent objects without specifying their
concrete classes. This pattern is particularly useful when
you need to ensure that the created products are
compatible and should be used together.
The Abstract Factory Pattern could be thought of as an
advanced version of Factory Method Design Pattern,
wherein the Factory Method Pattern plays a small role in its
implementation. It is not necessary to use these two
patterns together, but since they have a close resemblance,
it could potentially be used in sync to solve a problem.

Problem scenario
To grasp the implementation of the Abstract Factory
Pattern, let us delve into the example of a furniture shop.
Picture a furniture shop that specializes in crafting products
specifically designed for a living room. In this scenario, the
shop excels in creating remarkable and durable chairs,
couches, and coffee tables tailored for a living room setting.
Let us better understand the example of an Abstract Factory
Pattern using a set of furniture depicted in the following
figure:
Figure 2.8: Different styles of the same object
The furniture shop produces three distinct styles or variants
for its chairs, couches, and coffee tables: Art Deco, Modern,
and Bohemian. Each of these styles or variants is visually
unique, as illustrated in the image above.
Consider a scenario where a customer places an order for a
set of Art Deco chairs for their living room. It becomes
evident that, to maintain a cohesive and harmonious style in
the living room, the customer would prefer to order a couch
in the Art Deco style as well. A modern-styled couch might
not complement the artistic chairs of the Art Deco style as
effectively.
Therefore, we need to find a way to group these individual
furniture objects as a loosely-couple family. From a
technical standpoint, there is always a chance that the
furniture store would wish to add newer variants and styles
of furniture. Therefore, we would like to build a robust code
that would not require major changes if new products or
new styles are added to the system.

Implementation
In this section, we will delineate the steps typically
associated with implementing an Abstract Factory Design
Pattern. Given our prior coverage of the Factory Method
Pattern earlier in this chapter, you may find the steps
outlined below to be less intricate in nature. Nevertheless,
in the upcoming section, we will once again utilize a real-life
analogy to enhance our understanding of these steps.

Step 1: Define abstract products


Create interfaces or abstract classes for the products.
Unlike Factory Method Design Pattern, we will have
variants of these products. Hence, these classes will define
the common interface that concrete products (child of
products) must implement.

Step 2: Define Abstract Factory


Once we have set the rules or built the framework for our
products, now we need to create an Abstract Factory
interface that declares the creation methods for the abstract
products.

Step 3: Implement concrete products


Create concrete classes or subclasses that will implement
the interfaces of the abstract products.

Step 4: Implement concrete factories


At this point, we have initially defined the abstract products
(chair, couch, coffee table) and subsequently outlined the
Abstract Factory responsible for creating these products.
Moving on to the third step, we have defined the classes for
our concrete products (Art Deco chair, Art Deco couch, Art
Deco coffee table, Modern chair, Modern couch, and so
forth). Now, the next step is to create concrete factories
capable of producing these grouped furniture items. These
concrete factories extend the Abstract Factory and furnish
implementations for creating specific products.
Step 5: Client code
Write client code that uses the Abstract Factory and the
products without knowing their concrete classes.
In this section, we have outlined the five-step process
essential for creating an Abstract Factory Pattern. To
enhance our understanding of this Design Pattern, let us
delve into a code example in the next segment.

Code example
In the previous section, we looked at the five-step process to
implement the Abstract Factory Design Pattern in our code.
In this segment, we will look at the example of a UI
component library to drill home the point clearly.
First things first, let us revise the main characters that we
have in this Design Pattern. We have the following
characters:
• Products
• A base factory having common properties for building
products
• Variants of products
• Concrete or specialized factories for building variants of
products
Now that we have the list of characters at our disposable,
let us write a code that implements Abstract Factory Design
Pattern!

Step 1: Define abstract products


For the sake of our comprehension, we will draw upon the
real-life analogy of a simple UI component library. This
library is adept at building buttons and checkboxes
(products) for two distinct styles of UI – modern and basic
(concrete factories). Let us commence by defining our
abstract products:
1. // Abstract UI Component A: Button
2. class Button {
3. render() {
4. throw new Error('Method render must be
implemented.');
5. }
6. }
7.
8. // Abstract UI Component B: Checkbox
9. class Checkbox {
10. render() {
11. throw new Error('Method render must be
implemented.');
12. }
13. }
In the above code example, we have defined two abstract
products – a Button and a Checkbox. The abstract classes
for these products do not have any properties. However,
they both possess their individual method – render.

Step 2: Define Abstract Factory


Next, let us create the Abstract Factory that can generate
UI components for us. As we have read in the previous
segment, this is the step where we create a factory with an
eye for posterity. This factory method should be robust
enough to handle new entrants or new styles in the future
without impacting the original code extensively.
1. // Abstract UI Component Factory
2. class UIComponentFactory {
3. createButton() {
4. throw new Error('Method createButton must be
implemented.');
5. }
6.
7. createCheckbox() {
8. throw new Error('Method createCheckbox must be
implemented.');
9. }
10. }
In the above code example, we have created our base
factory – UIComponentFactory. It possesses two methods –
createButton and createCheckbox for generating the two
different products that we require.

Step 3: Implement concrete products


Since we have already defined our products in the first step,
let us create the variants for our products. In this example,
we will be considering two variants each for a button and a
checkbox. For button, we will be implementing a rounded
button and a flat button, while for a checkbox, we will be
implementing a fancy checkbox and a basic checkbox. The
rounded button and the fancy checkbox will adhere to the
modern style of UI components while the flat button and the
basic checkbox will belong to the basic style of UI
components.
1. // Concrete UI Component A1: RoundedButton
2. class RoundedButton extends Button {
3. render() {
4. [Link]('Rendering a rounded button.');
5. }
6. }
7.
8. // Concrete UI Component B1: FancyCheckbox
9. class FancyCheckbox extends Checkbox {
10. render() {
11. [Link]('Rendering a fancy checkbox.');
12. }
13. }
14.
15. // Concrete UI Component A2: FlatButton
16. class FlatButton extends Button {
17. render() {
18. [Link]('Rendering a flat button.');
19. }
20. }
21.
22. // Concrete UI Component B2: BasicCheckbox
23. class BasicCheckbox extends Checkbox {
24. render() {
25. [Link]('Rendering a basic checkbox.');
26. }
27. }
In the above code, we have implemented the four variants
by extending their respective abstract product classes. Now,
we have a RoundedButton class and a FlatButton class
that implement the render method of their parent class
Button. Similarly, we have the FancyCheckbox class and
the BasicCheckbox class extending the Checkbox class
and implementing its render method.

Step 4: Implement concrete creators


We have finally reached the point where we will be creating
our concrete creators, the specialized factory methods for
creating the family of products. Without much further ado,
let us dive into the code:
1. // Concrete UI Component Factory 1: ModernUIFactory
2. class ModernUIFactory extends UIComponentFactory {
3. createButton() {
4. return new RoundedButton();
5. }
6.
7. createCheckbox() {
8. return new FancyCheckbox();
9. }
10. }
11.
12. // Concrete UI Component Factory 2: ClassicUIFactory
13. class ClassicUIFactory extends UIComponentFactory {
14. createButton() {
15. return new FlatButton();
16. }
17.
18. createCheckbox() {
19. return new BasicCheckbox();
20. }
21. }
In the provided code example, we have introduced two
concrete creators – ModernUIFactory and
ClassicUIFactory. These specialized factory methods
extend the original factory method UIComponentFactory
that was created in step 2. The fundamental aim is to
leverage the Abstract Factory method to generate two
distinct groups or families of products. Each family is
capable of creating its own version of products, namely
buttons and checkboxes.
Examining the code, you will notice that the
ModernUIFactory's createButton method yields a new
instance of the RoundedButton product, while the
createCheckbox method returns a new instance of the
FancyCheckbox product. Conversely, the
ClassicUIFactory's createButton method produces a new
instance of the FlatButton product, and the
createCheckbox method returns a new instance of the
BasicCheckbox product. This illustrates the flexibility and
extensibility achieved through the implementation of the
Abstract Factory Pattern.

Step 5: Client code


Now equipped with all the necessary classes and factory
methods, let us delve into the client code. Before delving
into the intricacies of the code, let us take a glance at the
example:
1. function renderUI(factory) {
2. const button = [Link]();
3. const checkbox = [Link]();
4.
5. [Link]();
6. [Link]();
7. }
8.
9. // Usage
10. const modernUIFactory = new ModernUIFactory();
11. renderUI(modernUIFactory);
12.
13. const classicUIFactory = new ClassicUIFactory();
14. renderUI(classicUIFactory);
The client code (renderUI) uses the Abstract Factory to
create and render UI components without knowing the
concrete classes. This allows for the creation of different
styles of UI components (e.g., modern, classic) by simply
using the appropriate concrete factory.

Applications
Due to the similarities between the Abstract Factory and the
Factory Method Design Patterns, their application shares
several commonalities. Much like the Factory Method
Design Pattern, the Abstract Factory facilitates the creation
of a foundational template that can be overridden to
produce concrete classes. The key distinction lies in the
Abstract Factory’s ability to generate families of products, a
capability beyond the narrower scope of the Factory Method
Pattern. In fact, the Factory Method Pattern can, to some
extent, contribute to the implementation of an Abstract
Factory Design Pattern. Let us explore instances where the
Abstract Factory Design Pattern excels:

UI Component Libraries
As demonstrated in the Code example segment for the
Abstract Factory Design Pattern, it proves to be an ideal
pattern for constructing an entire component library with
various themes. The Abstract Factory Design Pattern
extends the capabilities of the Factory Method Design
Pattern by not only creating multiple components sharing a
common implementation but also generating a family of
components with shared features yet distinctive
appearances.

Localization and internationalization


When developing a multilingual application, you may need
to create families of objects (e.g., date formatters, currency
converters) that are specific to different regions. Abstract
Factory can assist in managing these variations.

Document processing libraries


In libraries that handle document processing (e.g.,
generating PDFs), different document formats may require
different implementations. Abstract Factory can be used to
create families of objects for handling various document
types.
Pros and cons
Owing to the similarities in their approach and their
application, much of the advantages and disadvantages of
the Abstract Factory Design Pattern have an uncanny
resemblance to those of Factory Method Design Pattern. Let
us have a look at it:
• First and foremost, the most important advantage of
Abstract Factory Pattern is that we are assured that the
products we are getting from the factory are compatible
with each other.
• Abstract Factory Pattern also avoids tight coupling
between the creator and the concrete products, making
our code more flexible and scalable.
• It follows the SRP by providing solution to only one
problem. It only deals with placing the creation code
into one place, thereby improving code readability and
support.
• Finally, it is extremely beneficial for scaling an
application. We can add new variants of products
without breaking the existing client code.
However, like the Factory Method Pattern, Abstract Factory
Pattern introduces complications in the code by adding a lot
of new classes and interfaces.

Builder Pattern
The Builder Pattern is a Creational Design Pattern that
enables the step-by-step construction of complex objects.
This pattern empowers the creation of diverse types and
representations of an object utilizing the same construction
code.

Problem scenario
Builder Pattern works best for complex objects that require
step-by-step initialization of many properties and contain
several nested objects. Let us correlate this with a real-life
analogy of building a house, depicted in the picture below
where every house looks different from a Structural point of
view:

Figure 2.9: All houses might not have identical features


As we are aware, not all houses share identical features.
Some houses may boast two windows, while others might
have four or more. Certain houses may include a backyard
or a swimming pool, while others may lack these amenities.
To construct a basic house, the initial steps involve erecting
four walls and a floor. Subsequently, a door is installed, and
windows are incorporated. Finally, the construction is
capped off with the addition of the roof, completing the
house.
If we were to convert this into code, we would start by
creating a class House that can generate the object we
require. It would have the basic properties – walls, floor,
door, windows, roof. That should suffice building a simple
house. However, what if someone wishes to build a bigger
house with more amenities like plumbing, swimming pool,
backyard, or more?
In the following figure, we have depicted several different
styles of houses based on the user’s preferences:

Figure 2.10: Houses and its variant approaches


To achieve this, we can follow two approaches:
• To accommodate the diverse permutations of
parameters, we extend the base class House and
generate multiple subclasses. However, this approach
can lead to an overwhelming number of subclasses. For
instance, distinct subclasses might emerge for a
backyard, a swimming pool, plumbing, and more. This
creates an endless cycle where each new feature or
parameter necessitates the creation of a new subclass.
• Another approach involves abstaining from creating any
subclasses. Instead, all parameters are added as
properties in the constructor. This means that with each
constructor call, an array of numerous parameters is
expected to construct the precise path. However, as
illustrated in figure 2.10, this approach results in
convoluted and unwieldy constructor calls. For example,
a significant portion of houses may not require
additional amenities, yet they are compelled to include
the mandatory constructor arguments, leading to a
cumbersome and inelegant usage.
Therefore, we need to find an elegant solution that can solve
this precarious problem. We need to ensure the given
solution works well with the step-by-step process and it is
adept at handling several parameters.

Implementation
To solve the problem that we witnessed in the Problem
scenario segment of the Builder Design Pattern, we can
separate the construction code into a separate class
altogether and call it builders.
Subsequently, we break down the entire construction
process into a sequence of distinct steps – such as
buildWall, buildDoor, buildWindows, etc. To assemble
the complex House object, we execute a series of these
steps on the builder object. Importantly, it is not mandatory
to invoke all of these steps. Depending on preferences, one
can opt not to execute a specific step. For example, basic
house builders may exclude the construction step for a
swimming pool.
Let us segregate the entire process into steps for easy
understanding:

Step 1: Define the product


We will start by defining a class that will generate the
complex object for the product we wish to build. Within this
class, we incorporate fundamental properties and methods
essential for every product, regardless of its variants.

Step 2: Define the builder interface


Next, we create a builder class that defines the interface for
assembling the product. Within this class, methods are
designated for constructing each component of the product.
Additionally, it incorporates a method to retrieve the final
product. This class serves as the base class that can be
extended by concrete builders to formulate their own
versions of the construction steps. For example, one builder
may utilize wood to construct a door, while another might
opt for steel. We establish a base method in this interface
that can be expanded and overridden by concrete builders
to craft their unique versions of the construction process.

Step 3: Create concrete builders


Now, we proceed to construct concrete builders that extend
the builder interface established in the preceding step.
These concrete builders have the capability to furnish their
own implementations for assembling the product.

Step 4: Create the director


In this step, we introduce a new player – the director. The
director class assumes the responsibility of coordinating
the entire construction process. It takes a builder as a
parameter and employs it to assemble the product.

Step 5: Client code


We have now reached the final part of our process. In this
step, we culminate all the code to construct the product. We
create an instance of the concrete builder, an instance of
the director, and use the director to create the product.
It is important to note here that this pattern also allows us
the flexibility to build a different variant of the construction
process while building the product. For instance, we can
have three builders using the same construction steps but
different raw materials. The first one might use wood, the
second utilizes stone, while the third uses precious
diamonds. Following the same steps, the first one will
deliver a regular house, the second one will create a castle,
while the third one can build a majestic palace.
In the next segment, we will see a code example with a real-
life analogy to understand how this pattern can help make
our code more efficient.

Code example
In the preceding section, we meticulously outlined the steps
required to implement the builder Design Pattern in our
code. Now, let us delve into a code example to solidify our
understanding of this concept. For a clearer comprehension
of this concept, we will employ the example of constructing
a computer from scratch.

Step 1: Define the product


We will start by creating the class for defining the final
product that we wish to create – a computer.
1. // Product: Computer
2. class Computer {
3. constructor() {
4. [Link] = null;
5. [Link] = null;
6. [Link] = null;
7. [Link] = null;
8. }
9.
10. show() {
11. [Link](`Computer Specs: CPU - ${[Link]},
GPU - ${[Link]}, Memory - ${[Link]}, Storage -
${[Link]}`);
12. }
13. }
In the provided code example, we have crafted the class
Computer to represent our product. Within the
constructor method, we have initialized the properties that
we desire our computer to possess. Furthermore, a show
method has been defined to showcase the final
specifications of our computer product.

Step 2: Define the builder interface


Next, we will define the abstract class for the builder that
will serve as the base class for the concrete builders. Our
builder class will have all the steps required to build our
final product – a computer.
1. // Builder Interface: ComputerBuilder
2. class ComputerBuilder {
3. constructor() {
4. [Link] = new Computer();
5. }
6.
7. buildCPU() {}
8. buildGPU() {}
9. buildMemory() {}
10. buildStorage() {}
11. getResult () {
12. return [Link];
13. }
14. }
In the above code example, we have defined the class
ComputerBuilder that will serve as the builder of our
product, computer. In the constructor method, we have
created a new instance of our product class, Computer and
assigned it to the local variable, computer. This variable
will then be returned whenever the method getResult is
triggered. Additionally, we have defined empty methods for
all the steps that are required to build our product. If you
correlate this example with our product class, Computer,
you will observe that we have created a separate step for
every property our product might possess – buildCPU,
buildGPU, buildMemory, and buildStorage. These
methods will be overridden by the concrete builders that
will extend this builder class.

Step 3: Create concrete builders


Next, we advance to the creation of our specialized builder.
To streamline the example, we will focus on a single
concrete builder – a gaming computer builder. However,
this framework can be extended to craft numerous other
computers tailored to diverse requirements.
1. // ConcreteBuilder: GamingComputerBuilder
2. class GamingComputerBuilder extends
ComputerBuilder {
3. buildCPU() {
4. [Link] = "Intel Core i9";
5. }
6.
7. buildGPU() {
8. [Link] = "NVIDIA GeForce RTX 3080";
9. }
10.
11. buildMemory() {
12. [Link] = "32GB DDR4 RAM";
13. }
14.
15. buildStorage() {
16. [Link] = "1TB SSD";
17. }
18. }
In this instance, we have introduced a concrete builder
extending our base builder class – ComputerBuilder. As
evident in the provided code example, we have overridden
the vacant methods representing the essential steps for
constructing our computer. At each step, we have leveraged
the overridden methods to specify the type of CPU, GPU,
Memory, and Storage essential for this gaming computer.

Step 4: Create the director


Our final step in defining the players for our code is to
create the director. This class will serve as an actual
director in a real-life analogy who oversees the work done
by the company. In this example, the director will take a
builder as an argument and then trigger the various steps
required to build our product.
1. // Director: ComputerAssemblyDirector
2. class ComputerAssemblyDirector {
3. constructor(builder) {
4. [Link] = builder;
5. }
6.
7. assembleComputer() {
8. [Link]();
9. [Link]();
10. [Link]();
11. [Link]();
12. }
13. }
In the provided example, we have introduced the director
class, ComputerAssemblyDirector. It is crucial to
understand that this class acts as the coordinator,
overseeing the construction of distinct versions of the
computer. In our director class, a concrete builder is
accepted as an argument and assigned to the local variable
builder to facilitate object construction. Furthermore, a
method named assembleComputer has been defined to
initiate the various steps specified within the concrete
builder. Consequently, multiple concrete builders can be
employed within this singular director class to generate
diverse versions of computers.

Step 5: Client code


In the concluding segment of our code, we will leverage the
various classes defined in the preceding steps to construct
our ultimate computer.
1. const gamingComputerBuilder = new
GamingComputerBuilder();
2. const assemblyDirector = new
ComputerAssemblyDirector(gamingComputerBuilder);
3.
4. [Link]();
5. const gamingComputer =
[Link]();
6. [Link]();
In this example, we commence by creating a new instance
of the GamingComputerBuilder and assigning it to the
variable gamingComputerBuilder. With our concrete
builder instance in place, we then supply it to our director
class, ComputerAssemblyDirector. This is accomplished
by creating a new instance of ComputerAssemblyDirector
and passing the gamingComputerBuilder as an argument,
subsequently assigned to the variable assemblyDirector.
To conclude, we invoke the assembleComputer method of
our director class. Following this, we execute the getResult
method, originally belonging to the ComputerBuilder base
class and extended by our GamingComputerBuilder, to
retrieve the result. This result is stored within the
gamingComputer variable, ultimately representing an
instance of our product, Computer. The show method can
thus be invoked to display the specifications of our product.
If this sounded like a mouthful of words, try to understand
one sentence at a time and try to figure out its root.
With this, we have successfully implemented the Builder
Design Pattern to create a complex object in our code. To
show you the versatility of this pattern, let us add two more
variants of computer to this code:
1. // ConcreteBuilder: OfficeComputerBuilder
2. class OfficeComputerBuilder extends ComputerBuilder
{
3. buildCPU() {
4. [Link] = "Intel Core i5";
5. }
6.
7. buildGPU() {
8. [Link] = "Integrated Graphics";
9. }
10.
11. buildMemory() {
12. [Link] = "16GB DDR4 RAM";
13. }
14.
15. buildStorage() {
16. [Link] = "500GB HDD";
17. }
18. }
19.
20. // ConcreteBuilder: BasicComputerBuilder
21. class BasicComputerBuilder extends ComputerBuilder
{
22. buildCPU() {
23. [Link] = "Intel Core i3";
24. }
25.
26. // No dedicated GPU for BasicComputer
27. // This method is intentionally left empty
28. buildGPU() {}
29.
30. buildMemory() {
31. [Link] = "8GB DDR4 RAM";
32. }
33.
34. buildStorage() {
35. [Link] = "256GB SSD";
36. }
37. }
38.
39. const officeComputerBuilder = new
OfficeComputerBuilder();
40. const officeDirector = new
ComputerAssemblyDirector(officeComputerBuilder);
41. [Link]();
42. const officeComputer =
[Link]();
43. [Link]();
44.
45. const basicComputerBuilder = new
BasicComputerBuilder();
46. const basicComputerDirector = new
ComputerAssemblyDirector(basicComputerBuilder);
47. [Link]();
48. const basicComputer =
[Link]();
49. [Link]();
In the provided code example, two additional concrete
builders – an OfficeComputerBuilder and a
BasicComputerBuilder – have been introduced. Both
extend our base builder class, ComputerBuilder. Two
noteworthy observations can be drawn from the above code:
• It underscores the efficacy of the Builder Design
Pattern. With just one director class and one builder
class outlining the steps for building a computer, and
enumerating all possible parameters a computer may
possess, we can construct any type of computer by
extending the ComputerBuilder and assigning specific
specifications.
• In scenarios where a particular parameter is
unnecessary – for example, the BasicComputerBuilder
does not require a GPU – we can leave the method
implementation for that parameter empty in its concrete
builder. This allows us to build the desired product
without utilizing all parameters.

Applications
Due to its step-by-step building process and its separation of
construction logic from the implementation, the Build
pattern is extremely useful in scenarios where complex
objects need to be created in gradually incremental phases.
Since we utilize an abstract builder, this makes the Builder
Design Pattern useful for creating variants of products
belonging to the same category. Let us look at the scenarios
where Build pattern excels in implementation:

Step-by-step object construction


When an object needs to be constructed in a step-by-step
manner, and we want to separate the construction logic
from the representation, we can utilize this pattern. This is
especially useful when dealing with objects that have a
large number of parameters.

Database query building


When constructing database queries dynamically and we
want to provide a flexible and fluent interface for building
queries with various conditions, filters, and sorting options,
we can use the Builder Pattern to incrementally create
queries.

Building documents or reports


In scenarios where we need to generate documents or
reports with various sections, headers, footers, and content,
the Builder Pattern can help in constructing different types
of documents with specific structures.

Product variants
As we have seen in the examples for Builder Pattern before,
this pattern aids in dealing with product variants or
configurations, such as different types of computers, cars,
or electronic devices. It helps us in developing a flexible
way to create these variations without cluttering the client
code.

Assembling composite objects


We can use the Builder Pattern in constructing composite
objects, where an object is composed of multiple sub-objects
or parts. It can help in assembling these parts into a
coherent whole.

Pros and cons


Here are some of the advantages of using Builder Pattern in
your code:
• Flexibility: Builder Pattern offers us the flexibility to
construct objects step-by-step, defer the entire
construction process to a separate code, or run steps
recursively. The separation of construction and
implementation parts makes it feasible for us to make
our code cleaner and more efficient.
• Reusability: It helps us reuse the same construction
code when building various representations of the
products.
• SRP: Builder Pattern follows SRP because it solves only
one problem at a time – that of isolating the complex
construction code from the business logic piece of the
product.
Like the preceding Creational Design Patterns we explored,
Builder Pattern also suffers from the case of increased
complexity of the code. In this pattern, we extensively
create a separate builder and extend it to its subclasses.
The addition of these new classes could potentially
complicate the code.

Prototype Pattern
The Prototype Design Pattern, a member of the Creational
Design Patterns, facilitates the replication of existing
objects without necessitating code dependency on their
classes. The primary purpose of the Prototype Pattern is to
generate new objects by duplicating an established object,
referred to as the prototype. This proves highly
advantageous in situations where crafting a new object is
more intricate or resource-intensive compared to
duplicating an existing one.

Problem scenario
In coding scenarios, there are instances where you may
encounter the need to create an identical copy of an object.
Consider the context of a Role Management System within a
company, where various user roles exist, such as
Administrator, Employee, or Customer Support.
For illustrative purposes, let us imagine that the company
initially had only one role – the Employee role. As the
company expanded, introducing the Administrator role
became a logical step. In such a situation, it becomes
imperative to generate an exact replica of the Employee role
for the Administrator role and subsequently incorporate the
additional permissions specific to an admin.
A common approach to do this would be to create a new
object of the same class and then copy all the fields from the
original object to the new object. There are three problems
with this approach:
1. Some of the object’s fields might be private and not
visible from the outside.
2. We need to know the name of the object’s class to
replicate it, which makes us completely dependent on
the class itself.
3. If the code is already implementing a Design Pattern,
there is a chance that you might know the interface of
the object but not the actual concrete class or the base
class. Finding and replicating that logic would not only
be time-consuming but it would further complicate the
code.
Therefore, we need a solution that can delegate the entire
cloning process to a common interface which would solve all
the above problems.

Implementation
In the initial sections of this chapter, we delved into the
JavaScript object prototype, which inherently appends
certain properties to an initialized object. The fundamental
concept underlying an object prototype aligns closely with
what we are about to explore in this section. Let us
deconstruct the implementation of the pattern into clear
and distinct steps:

Step 1: Create an object prototype


Define a prototype object that will serve as a blueprint for
creating other objects. This object should include common
properties and methods that will be shared among
instances.

Step 2: Create instances using the prototype


Use the prototype to create instances by cloning it.
JavaScript provides various ways to perform object cloning,
such as using [Link]() or creating a new instance
and copying properties.

Step 3: Customize instances


If individual instances need customization, add or modify
properties and methods on those instances without affecting
the prototype.

Step 4: Test and use instances


Verify that instances have the expected properties and
methods. Use instances in your application as needed.

Code example
In the preceding section, we witnessed the seamless
integration of the Prototype Design Pattern into our code.
To reinforce our understanding, let us explore a real-life
analogy. In this scenario, we will establish a prototype for a
basic Shape object encompassing common properties such
as colour and methods like draw.
Step 1: Create an object prototype
We will initiate the process by crafting the prototype object
for the Shape. This prototype will encompass all the
essential properties and methods, forming the foundation
for its clones:
1. class Shape {
2. constructor(color) {
3. [Link] = color || "default";
4. }
5.
6. draw() {
7. [Link](`Drawing a ${[Link]} shape.`);
8. }
9. }
In the provided code snippet, a class named Shape has
been established. Within the constructor method, the class
accepts a color argument, which is then assigned to the
class property color. If the colour is not specified, the
property will default to the string default. Additionally, a
draw method has been included, responsible for displaying
the shape’s color in the console.

Step 2: Create instances using the prototype


Now, let us clone the prototype to create the instances.
1. const circle = new Shape("red");
2. const square = new Shape("blue");
3. const triangle = new Shape("green");
In the above code snippet, we have now created three
instances of the Shape class and assigned a colour to each
of them. With this, we now have three objects circle,
square, and triangle that are created using the prototype
Shape.
Step 3: Customize instances
In this step, we will be customizing the instances as per our
needs. So, let us go ahead and do that:
1. [Link] = 10;
2. [Link] = 15;
3. [Link] = true;
The code example illustrates instances created from the
prototype Shape where various properties are assigned to
each instance. It is essential to recognize that this step is
optional. The inclusion of this step demonstrates the ability
to add distinct properties to instances, different from both
the prototype and each other, without affecting the
prototype itself.

Step 4: Test and use instances


In the last step of the process, we test whether the
instances work as expected and then integrate them into
our code. Let us try it out.
1. [Link](); // Output: Drawing a red shape.
2. [Link](); // Output: Drawing a blue shape.
3. [Link](); // Output: Drawing a green shape.
As evident from the code snippet and the comments
indicating the output, each instance is functioning as
intended. If you print the properties of each instance on the
console, you will observe that they are constructed
differently. Furthermore, you can create a new instance and
examine it in the console to ensure that the original
prototype remains unaffected.

Applications
The object cloning advantage provided by the Prototype
Pattern is widely utilized in various scenarios in JavaScript.
Here are a few applications:
Reducing object initialization overhead
The Prototype Pattern allows you to create new objects by
copying existing ones, reducing the overhead of initializing
objects from scratch. This is particularly beneficial when
object creation involves complex or resource-intensive
operations.

Dynamic object configuration


If an object requires dynamic configuration or can have
varying properties, the Prototype Pattern enables the
creation of instances with a shared base structure. Each
instance can then be customized by modifying or adding
properties without affecting the prototype.

Managing object states


Prototypes are handy when dealing with objects that have
different states. You can create prototypes representing
different states, and then clone them to transition between
states. This helps in managing complex state-based
behaviour.

Role-based permissions
When dealing with user roles in an application, each role
may have a common set of permissions. The Prototype
Pattern can be used to create role objects with default
permissions, and new roles can be created by cloning these
prototypes.

Pros and cons


Here are some of the advantages of using Prototype
Patterns in your code:
• We can clone objects without having to rely on or being
dependent on the concrete classes.
• Possibly, the biggest advantage of using Prototype
Pattern is reusability. It completely eradicates the need
to write initialization code repeatedly.
• Complex objects can be created more conveniently.
One significant challenge associated with the Prototype
Pattern is the complexity introduced when cloning objects
with potential circular dependencies, making the cloning
process intricate and error-prone.

Conclusion
In conclusion, this chapter has provided a comprehensive
exploration of Creational Design Patterns in JavaScript,
delving into the fundamental concepts of OOP and drawing
distinctions between classical OOP and the unique
implementation of OOP in JavaScript. Through our journey,
we have gained valuable insights into the significance of
Creational Design Patterns and their role in facilitating
object creation processes.
By thoroughly examining various Creational Design
Patterns, including Singleton, Factory, Abstract Factory,
Builder, and Prototype, we have equipped ourselves with a
diverse set of tools to address specific object creation
challenges. Each pattern offers a distinct solution, enabling
developers to design flexible, maintainable, and scalable
systems.
As we embrace these patterns, it becomes evident that they
not only enhance code organization and structure but also
contribute to the overall efficiency of our applications. The
careful selection and implementation of Creational Design
Patterns empower developers to create objects in a manner
that aligns with specific requirements, promotes code reuse,
and fosters the principles of encapsulation. In the next
chapter, we will be understanding another category of
Design Patterns in JavaScript that addresses communication
between objects.

Points to remember
• JavaScript as a language also follows OOP policies.
Every object on initialization gets several pre-defined
properties and they are accessible from an object
prototype.
• Creational Design Patterns specifically deal with the
correct creation of objects. Often understated, incorrect
object creation has the potential to crash an application.
• Singleton Pattern states that only one instance should
exist for a class and it should be accessed globally.
• Factory Method Pattern offers a super class that can
serve as a reference for subclasses to create objects.
Subclasses can then override the methods provided in
the superclass to create their own versions of products.
• Abstract Factory Design Pattern is an extension of the
Factory Method Pattern where it is useful in creating
related or dependent objects without specifying their
concrete classes. This pattern is particularly useful when
you need to ensure that the created objects are
compatible and should be used together.
• Builder Design Pattern is perfect for scenarios where
you need to create complex objects that require step-by-
step process of construction, and that have several
parameters that could be nested to deeper levels.
• Prototype Pattern helps in building clone of complex
objects by separating the entire construction process
from the implementation.

Exercises
1. Which of the following principles do not belong to
the OOP methodology?
a. A class should not be allowed to share its properties or
let other classes to extends its data or methods.
b. Sensitive information belonging to a class should not
be accessible outside the class.
c. It is prudent to conceal the implementation specifics of
a class from the user.
d. An object can exhibit different behaviours while
accessing the same interface.
2. Which type of Creational Design Pattern deals with
incremental and stepwise building of a complex
object?
a. Prototype
b. Factory Method
c. Builder
d. Abstract Factory
3. Which of the following descriptions fit the
description of an Abstract Factory Pattern?
a. This pattern deals with cloning objects successfully by
separating the cloning process from the
implementation.
b. This pattern believes in creating related or dependent
objects without specifying their concrete classes.
c. This pattern strongly believes that only one instance of
the class should exist.
d. This pattern helps build complex objects gradually in a
step-by-step manner.

Answers
1. a
2. c
3. b
CHAPTER 3
Structural Design Patterns

Introduction
Structural Design Patterns are a crucial part of software
development process that emphasizes on organizing and
composing classes and objects to build larger, flexible
structures. Picture these patterns akin to LEGO blocks –
each block representing an individual class or object. When
arranged adeptly, these blocks enable the creation of not
just visually stunning but also inherently sturdy structures.
Much like assembling LEGO pieces, Structural Design
Patterns offer solutions to common design dilemmas,
fostering code reusability and scalability within intricate
systems. Skilful arrangement and organization of these
classes and objects empower us to craft a resilient system
that champions the separation of concerns, enhancing code
readability in the process.

Structure
This chapter will cover the following topics:
• Introduction to Structural Design Patterns
• Adapter Pattern
• Bridge Pattern
• Composite Pattern
• Decorator Pattern
• Façade Pattern
• Flyweight Pattern
• Proxy Pattern
• Pattern selection tips

Objectives
Upon completing this chapter, you will acquire a mastery of
Structural Design Patterns, unlocking the secrets to
architecting applications with finesse and flexibility. From
demystifying common coding challenges to understanding
the usage of these patterns, you will be able to craft code
that is modular, scalable, and lucid. Structural Design
Patterns will help you to sculpt code that can withstand the
test of time.

Introduction to Structural Design Patterns


In our last chapter, we discussed the significance of classes
and objects in JavaScript, unravelling their crucial roles in
constructing scalable and resilient applications.
Recognizing that Design Patterns in JavaScript hinge on
the manipulation of objects and classes to address
longstanding challenges, we gained insights into strategies
for creating them. This chapter marks the continuation of
our journey, shifting focus to another pivotal aspect in
alleviating developer concerns – the art of organizing and
structuring objects and classes.
Before delving into the intricacies of Structural Design
Patterns, it is crucial to grasp the real-world problems that
developers routinely encounter, and which can be
effectively addressed through the application of these
patterns. So, before immersing ourselves in the definition
of Structural Design Patterns, let us take a moment to
identify some challenges that developers commonly face in
their day-to-day work, challenges that can find solutions
through the introduction of Structural Design Patterns. Our
exploration of solutions for these identified problem
statements will unfold later in this chapter, after
comprehensively understanding various types of Structural
Design Patterns. Stay tuned as we navigate through these
patterns and ultimately address the challenges presented.

Problem statement #1
Let us look at this code example showcasing a nested
object one might commonly encounter while developing an
application:
1. const person = {
2. name: 'John',
3. address: {
4. street: '123 Main St',
5. city: 'Seattle',
6. country: 'USA'
7. }
8. };
9.
10. // Accessing the country property
11. const country = [Link];

Code issue
The code tightly couples the person and the address
object, making it challenging to modify or manage their
relationships. For instance, if you wish to change the
structure of the address object, it will involve modifying
every object and the code in multiple places. This makes
code less flexible and harder to maintain.

Problem statement #2
For our second scenario, let us examine a situation
involving two classes that exhibit a notable degree of
similarity, yet harbour distinct differences:
1. class BasicCar {
2. constructor() {
3. [Link] = function() {
4. [Link]('Basic car started.');
5. };
6. }
7. }
8.
9. class LuxuryCar {
10. constructor() {
11. [Link] = function() {
12. [Link]('Luxury car started.');
13. };
14.
15. [Link] = function() {
16. [Link]('Performing luxury action.');
17. };
18. }
19. }
20.
21. // Usage
22. const car = new LuxuryCar();
23. [Link]();
24. [Link](); // Throws an error if used
with BasicCar

Code issue
Since we looked at the Creational Design Patterns in the
preceding chapter, we might have noticed the glaring
mistake of not having a link between the two classes
BasicCar and LuxuryCar. Lack of a common interface
makes it challenging to use different car types
interchangeably.

Problem statement #3
In our final example, we will be looking at an example
related to inheritance. Let us have a closer look at the
problem that might arise due to a lack of organization:
1. class Animal {
2. constructor(name) {
3. [Link] = name;
4. }
5.
6. eat() {
7. [Link](`${[Link]} is eating.`);
8. }
9. }
10.
11. class Bird extends Animal {
12. fly() {
13. [Link](`${[Link]} is flying.`);
14. }
15. }
16.
17. // Usage
18. const sparrow = new Bird(‘Sparrow’);
19. [Link]();

Code issue
The provided code snippet seems harmless at first glance.
It introduces a class Bird that extends the Animal class.
Through this extension, Bird inherits both the name
property and the eat method from Animal, subsequently
invoking the eat function. However, a potential pitfall lies
in the fact that the eat function exclusively belongs to the
Animal class.
In the dynamic landscape of a growing application,
Structural modifications to the parent class Animal are
conceivable. This could involve drastic changes, including
the removal of the eat function in a large-scale application.
If such alterations occur, all classes inheriting the eat
property would encounter failures, potentially leading to
application crashes that prove challenging to debug.
Furthermore, as the hierarchy expands, it exacerbates code
readability issues. The inherent tight coupling of classes
also hinders their reuse, adding another layer to the
challenge.

Structural Design Patterns


In a nutshell, Structural Design Patterns help solve
problems highlighted in the earlier sections by enhancing
code organization. These patterns revolve around the
concept of organizing and composing classes and objects to
create robust and flexible software architectures. They
provide solutions to problems commonly faced by
developers and at the same time, these patterns promote
reusability of code, maintainability and scalability. Let us
briefly explore some key attributes of Structural Design
Patterns:

Organization of classes
Structural Design Patterns help in organizing classes and
objects in a way that enhances code clarity and separation
of concerns. This aids in managing the complexity of large
codebases by providing a structured and coherent
architecture.

Object composition
These patterns emphasize the composition of objects to
create larger, more complex structures. By composing
objects instead of relying on class inheritance, we can
create a flexible and dynamic relationship between
components.

Code reusability
One of the biggest advantage and goal of the Structural
Design Pattern is code reusability. Structural Design
Patterns like Adapter, Composite, and Decorator, lean on
creating reusable components that can be seamlessly
integrated into different parts of the application.

Flexibility and adaptability


One of the major advantages of using the Structural Design
Pattern is that it provides mechanisms to alter or extend
the structure of existing objects and classes without
modifying the source code. This makes the code adaptable
to changing requirements.

Common design problems


Structural Design Patterns address common design
problems such as managing object relationships, handling
interface mismatches, and organizing code in a way that is
conducive to both current and future requirements.
In summary, true to their name, Structural Design Patterns
play a pivotal role in imparting structure and organization
to code, ultimately fortifying the system and enabling it to
adapt seamlessly to the evolving requirements of the
application. In the forthcoming sections, we will delve into
various types of Structural Design Patterns, each offering
unique solutions to diverse problems.

Adapter Design Pattern


Let us kickstart our journey to understand Structural
Design Patterns by exploring the very first pattern from
this category – Adapter Pattern. In simple words, Adapter
Pattern allows objects with incompatible interfaces to
collaborate.

Problem scenario
Let us understand the problems solved by the Adapter
Pattern with the following two examples. Our first example
has no reference to any coding application.

Scenario #1
In the context of Figure 3.1, which illustrates various
electronic devices used in India, it is apparent that the
chargers for devices such as the MacBook, Android phones,
and smartwatches differ significantly. For instance, the
MacBook charger typically utilizes a USB-C connector,
while Android chargers may have micro USB connectors.
The smartwatch charger, on the other hand, is specifically
designed for USB type-B ports.
Figure 3.1: Different types of ports and sockets used by different countries
When traveling to countries like the UK or various
European nations, a challenge arises due to the distinct
power outlets. The power outlets in the UK and Europe
differ from those in India, and this incompatibility may
render the chargers brought from home unusable. In such
instances, a practical solution is required – a device
commonly known as a travel adapter or plug converter.
This device should be capable of accepting Indian-style
power outlets and converting them to the formats required
in the UK or Europe.

Scenario #2
In the second scenario, let us explore the realm of a
Content Management System (CMS) application
tailored for managing articles and blog posts. For this
illustration, let’s assume our application exclusively
supports the storage and retrieval of data in the relational
database format, exemplified by SQL tables.
The challenge arises when a decision is made to enhance
our application by incorporating a full-text search engine.
After exhaustive research, a third-party search engine
library emerges as the ideal candidate due to its efficiency
and seamless compatibility with our requirements.
However, a significant hurdle surfaces – this library
exclusively operates on document-based storage, such as
MongoDB, and necessitates content in JSON format.
Now confronted with a critical dilemma, the question
looms: should we undergo substantial modifications to our
CMS code to align with this third-party library, or should
we opt for a costly and less efficient alternative that fails to
fully meet our needs?
In both of the scenarios outlined above, a common need
emerges for a device or tool capable of seamlessly
converting or facilitating the adaptation between disparate
devices or codebases. Put simply, what’s required is an
adapter. The Adapter Structural Design Pattern is precisely
crafted to fulfil this purpose.

Implementation
The implementation of the Adapter Design Pattern can be
broken down into five distinct steps, culminating in clear
instructions on its utilization within an actual client code:

Step 1: Define the existing class


In this step, you define the existing class that has a specific
interface. This class represents some functionality that you
want to use in your application but does not conform to the
desired interface.
Step 2: Create the target interface
Here, you define the target interface that your client code
expects. This interface represents the set of methods that
your client code will use. It is what you want your existing
class to conform to.

Step 3: Implement the Adapter


Next, we craft the adapter that functions as a bridge
connecting the target interface and the existing class. In
this step, the adapter class is formed by extending the
target interface and accepting an instance of the existing
class as a parameter. Subsequently, a new method is
introduced within the adapter class to execute the
pertinent method of the existing class.

Step 4: Client code


In this step, we define a function that utilizes the method
that you have defined in the adapter implementation in step
3. This function will receive an instance of the adapter as a
parameter and implement the new method that we have
defined inside it.

Step 5: Usage
In the concluding step, we bring the implementation to
fruition by connecting the existing class with the target
interface using the adapter. This involves instantiating the
existing class, creating an adapter, passing the instance of
the existing class to it, and ultimately utilizing the method
we defined in the previous step to establish the connection
between the two.

Code example
We have broken down the implementation of the Adapter
Pattern into five distinct steps in the previous section. Now,
let us utilize the example of an authentication system to
realise the steps that we have seen in the previous section.
In this example, we have a legacy authentication system
that accepts a username and custom token. However,
modern architecture calls for a username and a password
to authenticate a user. let us see how we can implement
the Adapter Pattern to solve this issue:

Step 1: Define the existing class


The very first thing that we need to do is to define the
existing class. From the preceding section, we know that
the term existing class stands for the code that is not
compatible with the change and that it would require the
assistance of an adapter to achieve that. Let’s go ahead and
define our existing class:
1. class LegacyAuthenticator {
2. authenticate(username, customToken) {
3. // Legacy authentication logic
4. return “Legacy Authentication Successful”;
5. }
6. }
In the above example, we have defined the class
LegacyAuthenticator. This class possesses a single
method authenticate that accepts two parameters – a
username and a customToken. On execution, it returns
the statement “Legacy Authentication Successful”.

Step 2: Create the target interface


Now, let us consider that we have decided to adopt a
modern form of authentication, like OAuth. A modern
authentication would probably require a username and a
password, so we create an interface like this:
1. class ModernAuthenticator {
2. login(username, password) {
3. // The client expects this method
4. }
5. }
In the above code example, we have defined a class –
ModernAuthenticator. In this class, we have a method
login that accepts username and password. This is the
desired interface that follows the modern authentication
method.

Step 3: Implement the Adapter


Up to this point, we have outlined the original
class/interface adhering to the proprietary method of
authentication and a modern interface specifying a newer
authentication method. Now, the pivotal step is to forge the
bridge that seamlessly connects the two. Let us proceed
with the creation of the adapter:
1. class LegacyToModernAuthAdapter extends
ModernAuthenticator {
2. constructor(legacyAuthenticator) {
3. super();
4. [Link] = legacyAuthenticator;
5. }
6.
7. login(username, password) {
8. // Implement the login method using the legacy
authenticator
9. return
[Link](username,
password);
10. }
11. }
In the above code example, we have created an adapter,
aptly titled LegacyToModernAuthAdapter. It is crucial to
ensure that this class serves as a seamless bridge between
the two interfaces. The new code will align with
contemporary standards while internally invoking the
legacy system for authentication. For a clearer
understanding, let us refer to Figure 3.2, based on the
example from Scenario 1 in the Problem scenario segment
of the Adapter Design Pattern.

Figure 3.2: An adapter acts as a bridge connecting two dissimilar items of the
same type
In Figure 3.2, on the left, we have a charger with a
connector tailored for American users. On the extreme
right, there is an outlet port in a Parisian hotel in Europe.
To address this discrepancy, an adapter, depicted in the
middle, is necessary. This adapter takes the American
connector as input and provides its own connector,
adaptable to European ports, as output. Upon connecting
the device through this adapter, it seamlessly
accommodates both the original and newer formats,
ensuring the correct functioning of the device.
Likewise, we can draw a parallel between the
LegacyAuthenticator and the European outlet port, while
the Indian charger corresponds to the
ModernAuthenticator. To enable the Indian charger to
seamlessly operate on European ports, we require the
adapter LegacyToModernAuthAdapter. This adapter
must be capable of interfacing with the Indian charger,
accepting its connector, and delivering the charge to the
European outlet.
In a similar vein, the LegacyToModernAuthAdapter must
extend the ModernAuthenticator to gain access to its
login method. Simultaneously, it needs to connect to the
European outlet port in its original format, necessitating an
internal connection or execution of the
LegacyAuthenticator’s authenticate method. This
intricate dance ensures the smooth compatibility and
operation of the ModernAuthenticator with the legacy
LegacyAuthenticator.

Step 4: Client code


Now, let us create a function that will in essence call the
method of the adapter. Let us first look at the code example
before understanding what transpires in it:
1. function handleAuthentication(authenticator) {
2. [Link]([Link]("user123",
"password123"));
3. }
In the above code example, we have created a function
handleAuthentication that accepts an authenticator as
a parameter. In essence, on the invocation of this function,
it expects an instance of the adapter. Hence, as you can see
on the next line, we will be calling the login method of the
authenticator, i.e., the adapter’s login method. This
method will be useful in completing the bond required for
transitioning from the target interface to the existing class.

Step 5: Usage
In the ultimate step, we consolidate all the information
gathered thus far to bring the adapter method to fruition:
1. // Instantiate the existing class (legacy authenticator)
2. const oldAuthenticator = new LegacyAuthenticator();
3.
4. // Create an adapter and pass the existing class
instance to it
5. const adaptedAuthenticator = new
LegacyToModernAuthAdapter(oldAuthenticator);
6.
7. // Use the client code to handle authentication with the
adapted authenticator
8. handleAuthentication(adaptedAuthenticator);
As we can see in the above code example, we have divided
the final implementation into three parts:
1. We first create an instance of the old authentication
system and store it in the oldAuthenticator variable.
2. Next, we create an instance of the adapter. As
explained in step 3, it requires an instance of the
existing class as a parameter. Therefore, in our case we
pass the oldAuthenticator variable to the constructor
of the adapter by passing it in as an argument. We,
then, store the instance of the adapter in the variable
adapterAuthenticator.
3. In the conclusive step, we invoke the method
delineated in step 4 to log into the system as a modern
authenticator. As observed in the preceding code, this
operation executes the original authenticator within its
context, and we consequently receive the resulting
outcome.
With this completion, the adapter method has been
successfully implemented. This method allows a new code
format to seamlessly interact with an existing code
structure without necessitating any alterations to the
original codebase. The adapter serves as a vital
intermediary, facilitating cohesion between the modern and
legacy components, ultimately enabling them to work
together harmoniously.

Applications
From the preceding sections, we have learnt that the
Adapter Pattern is extremely useful for scenarios where
you want to bring about new changes to your code-base
without changing the original code written. This benefit
naturally finds a lot of applications in modern JavaScript
programming. let us look at some of those applications:

Integration legacy code


As we have already seen in the Code example section, the
Adapter Pattern works like a charm for handling legacy
code that has a different interface to modern JavaScript
applications.

Third-party API integration


More often than not, we need to rely on third-party APIs to
solve a minor chunk of our applications. We may often
encounter a situation where we need to integrate several
third-party APIs or libraries to create a unified interface for
our application. This can be handled by the Adapter Pattern
without affecting the original code base.
Microservices integration
A microservices architecture is often the sound solution for
an application that is implemented on a scale. In essence,
the various major chunks of the application’s backend and
frontend services are divided into individual services which
are then seamlessly connected to each other behind the
scenes to display a unified UI to the user. In such a
scenario, Adapter Pattern can help adapt communication
between services with different protocols or message
formats.

Version upgrades
When upgrading a library or framework to a new version
that has breaking changes in its interface, and we wish to
minimize the impact on our existing code, the Adapter
Pattern can help minimize the damage.

Internationalization
For applications that are built on a global scale,
internationalization or the support for multiple
international languages and/or currency is a must-have
feature. Adapter Pattern can help adapt our application to
support multiple languages and support the need to change
the behaviour of certain components based on the selected
language without modifying their source code.

Pros and cons


Here are some of the advantages of using Adapter Patterns
in your code:
• Single Responsibility Principle (SRP): The Adapter
Pattern resolves the only problem of separating the
interface or the logic for mapping legacy code to new
code from the original source code.
• Open/Close Principle: We can add new adapters to
the code without breaking or modifying the original
source code.
However, the introduction of Adapter Pattern also signifies
adding several new interfaces and classes that might
increase the overall complexity of the code. It is useful to
check if the changes required are minor or major with an
eye on posterity before implementing this pattern.

Bridge Design Pattern


The Bridge Design Pattern is a Structural Design Pattern
that is separates the abstraction part of the object from its
implementation, allowing them to vary independently. It
allows splitting large classes or closely related classes into
two parts – abstraction and its implementation, allowing
them to be developed independently of each other.

Problem scenario
The Bridge Design Pattern proves instrumental in resolving
issues where the abstraction of a class needs separation
from its implementation, allowing them to function
independently. Let’s delve into two scenarios that mirror
problematic situations, demonstrating how the bridge
Design Pattern can effectively address and solve these
challenges:

Scenario #1
Let’s explore a scenario using the example of geometric
shapes and their colours. Initially, we have a class Shape
with two subclasses – Square and Circle. Now, envision
the need to expand these classes to encompass colours –
Blue and Red. This expansion results in a total of four
classes – BlueSquare, BlueCircle, RedSquare, and
RedCircle. However, the complexity intensifies when the
requirement arises to introduce two additional shapes and
two more colours. This leads to a proliferation of
subclasses, resulting in a more intricate and less robust
code structure.

Scenario #2
For our second scenario, let us consider the example of a
UI toolkit. A UI toolkit is a library that provides common UI
elements like buttons, windows, text fields, and so on, to
allow developers to create UI for applications. Now, the
challenge lies in supporting multiple operating systems like
MacOS, Windows, and Linux. Each operating system has its
own native windowing system and UI conventions.
Application developers want to create cross-platform
applications without worrying about the intricacies of each
operating system’s UI implementation. Once again, the
developers should not require modifying the application
code.
The common thread between the above two scenarios is
that in both the cases, there is a need for the abstraction
(colour and UI toolkit) to be independent of the specific
details of various platforms (shape and operating system).
As new elements or structures are introduced, there should
be a way to seamlessly integrate and support them without
modifying the existing codebase.

Implementation
Let’s deconstruct the implementation of the Bridge Design
Pattern into discrete steps. While these steps may initially
appear complex or overwhelming, in the following section,
we will explore the same steps with a relevant example to
enhance our understanding of the implementation of this
Design Pattern.
Step 1: Define the implementor interface
In this step, the focus is on defining the interface or an
abstract class for the implementor. When we refer to the
implementor, we are addressing the underlying system
with its various implementations. During this phase, the
objective is to outline the functions that will be
implemented by the concrete implementors, which we will
be creating in the subsequent step.

Step 2: Create concrete implementors


Now, the next step involves creating several concrete
classes, each dedicated to implementing the operations
declared in the implementor interface. Each of these
concrete implementors serves to represent a distinct and
specific implementation of the functions outlined in the
implementor interface.

Step 3: Define the abstraction


Create an abstraction class that contains a reference to the
implementor. The abstraction class declares higher-level
operations that use the operations declared in the
implementor interface. This separation allows the
abstraction to be independent of the specific details of the
implementations.

Step 4: Create refined abstractions


Upon preparing the concrete implementors and the
abstraction, the final piece of the puzzle comes together
with the creation of refined abstractions. These refined
abstractions are classes designed to implement the
abstraction for specific use cases, utilizing the operations
defined in the implementor interface to execute their tasks.
Additionally, these refined abstractions have the flexibility
to incorporate additional properties and methods beyond
those inherited from the implementor interface.

Step 5: Client code


In the client code, we will create instances of the concrete
implementors and pass them to the abstraction instances.
The client interacts with the abstraction, and the
abstraction delegates the implementation-specific details to
the concrete implementor at runtime. This allows the client
to work with the abstraction without being concerned
about the specific implementation details, promoting
flexibility and maintainability.
Now, let us realise this by utilising a relevant example to
implement the above steps.

Code example
Without further ado, let us deep dive into understanding
the implementation of the Bridge Design Pattern with the
help of a code example. Let’s consider the example of a
messaging system that needs to send messages via
different channels (e.g. SMS, email, Slack) while keeping
the message content and sending logic separate.

Step 1: Define the implementor interface


In accordance with the steps outlined in the preceding
segment, the initial step involves establishing the
foundation, namely, the implementor interface. In our
example, the implementor, or the core essence, is a
message sender. Subsequently, the variations of this class’s
implementation will be employed for sending messages
across different channels.
1. // Implementor interface
2. class MessageSender {
3. sendMessage(message, to) {
4. // Implemented by concrete implementors
5. }
6. }
Here, we have created an abstract class MessageSender
with a singular method, sendMessage that accepts two
parameters message and to. In the subsequent steps, this
method will play a central role in guiding the
implementation of the Bridge Design Pattern.

Step 2: Create concrete implementors


With the establishment of the designated abstract class
defining our implementor, or the foundational structure
upon which an array of implementors (message senders)
will be built, let us proceed to create the three concrete
implementors. These concrete classes will extend the
implementor formulated in step 1.
1. // Concrete Implementor A
2. class EmailSender extends MessageSender {
3. sendMessage(message, to) {
4. [Link](`Sending email: "${message}" to
${to}`);
5. }
6. }
7.
8. // Concrete Implementor B
9. class SMSSender extends MessageSender {
10. sendMessage(message, to) {
11. [Link](`Sending SMS: "${message}" to ${to}`);
12. }
13. }
14.
15. // Concrete Implementor C
16. class SlackSender extends MessageSender {
17. sendMessage(message, to) {
18. [Link](`Sending Slack message: "${message}"
to ${to}`);
19. }
20. }
As we see in the above code example, we have now created
three versions of our message senders by extending our
original implementor, MessageSender. These three
versions or the concrete implementors have extended and
used the original method sendMessage defined in
MessageSender to create their unique versions of sending
messages.

Step 3: Define the abstraction


Drawing reference from the example of Shape used in
Scenario #1 from the Problem scenarios section of the
Bridge Design Pattern, we have successfully tackled the
implementation aspect—the shapes, represented by the
circle and square. Now, our focus shifts to the abstraction
aspect—the colours—of the problem scenario. This implies
that having created the message senders, our next task is
to construct the actual messages. To achieve this, we
initiate the process by creating an abstract class for the
variations of messages.
1. // Abstraction
2. class Message {
3. constructor(sender) {
4. [Link] = sender;
5. }
6.
7. send(message, to) {
8. // Delegated to the concrete implementor
9. [Link](message, to);
10. }
11. }
In the provided code example, we have established the
abstract class for our abstraction—Message. As evident in
the code, we have defined a constructor to accept a
sender, which is subsequently assigned to the sender
property of the class. This parameter corresponds to the
specific variation of the sender needed for its message
equivalent (EmailSender to its equivalent email message).
Additionally, we’ve outlined the send method, which takes
two arguments—message and to. These parameters align
with the requirements of the sender concrete implementors
we defined in the previous example. The logical next step
involves executing the sendMessage method of the
sender received while creating a new instance of this
class. We have done that inside the send method defined
for this abstract class.

Step 4: Create refined abstractions


Our next logical step is to create the individual variants of
our abstraction. In other words, we need to generate
distinct versions of messages that align with the concrete
implementors of senders crafted in the preceding steps. Let
us proceed to create the three refined abstractions of our
abstraction, Message.
1. // Refined Abstraction A
2. class EmailMessage extends Message {
3. constructor(sender) {
4. super(sender);
5. }
6. }
7.
8. // Refined Abstraction B
9. class SMSMessage extends Message {
10. constructor(sender) {
11. super(sender);
12. }
13. }
14.
15. // Refined Abstraction C (You can add more refined
abstractions as needed)
16. class SlackMessage extends Message {
17. constructor(sender) {
18. super(sender);
19. }
20. }
With the illustrated code example above, we now possess
three message senders and their corresponding messages.
It is noteworthy that all three refined abstractions—
EmailMessage, SMSMessage, and SlackMessage—
receive a sender in their constructor. In the final step of
this implementation, we’ll explore how to appropriately
utilize this sender to implement the Bridge Design Pattern.

Step 5: Client code


In the ultimate step of our implementation, we seamlessly
integrate both the implementors and the abstractions to
work in harmony, ultimately achieving the end goal of
delivering successful messages on the appropriate
platforms.
1. // Example usage
2. const emailSender = new EmailSender();
3. const smsSender = new SMSSender();
4. const slackSender = new SlackSender();
5.
6. const emailMessage = new
EmailMessage(emailSender);
7. const smsMessage = new SMSMessage(smsSender);
8. const slackMessage = new
SlackMessage(slackSender);
9.
10. [Link]("Hello via email!",
"john@[Link]");
11. [Link]("Hello via SMS!", "+123456789");
12. [Link]("Hello via Slack!", "John Doe");
In the final implementation, we initiated the process by
creating instances of the concrete implementors, storing
them in their respective variables. Following that, instances
of the refined abstractions were crafted, with the
corresponding senders passed as arguments. Finally, the
send method from each refined abstraction was utilized to
dispatch the message to the respective platforms.
This successful implementation demonstrates the effective
separation of the implementation part from the abstraction
part, allowing both entities to function independently of
each other. Leveraging the Bridge Design Pattern, the
addition of various platforms and the modification of their
message styles can be executed individually without
necessitating any alterations to the original code base.

Applications
The Bridge Pattern proves highly advantageous in
scenarios where the abstraction and the implementation of
your code must operate independently without mutually
impacting each other. Here are some instances where this
pattern excels:
UI framework for cross-platform applications
When creating a user interface framework for cross-
platform applications (e.g., web, mobile, desktop), the
Bridge Pattern can be employed to separate the UI
components from the underlying platform-specific
implementations.

Database abstraction layer


When building a database abstraction layer, especially in
cases where your application supports multiple databases
(e.g., MySQL, PostgreSQL, MongoDB), the Bridge Pattern
can be used to separate the database operations from the
specific database implementations.

Messaging system for communication channels


As we have seen from the example used in the code
example, if you have a messaging system that sends
messages through different channels (e.g., email, SMS,
push notifications), the Bridge Pattern can help separate
the message abstraction from the specific implementations
for each messaging channel.

Network communication framework


When designing a network communication framework that
needs to support different protocols (e.g., HTTP,
WebSocket, TCP), the Bridge Pattern can be applied to
separate the abstraction of network communication from
the specific protocol implementations.

Pros and cons


Let us take a look at some of the advantages of using the
Bridge Pattern:
• One of the biggest advantage of using the Bridge
Pattern is that it allows us to build cross-platform and
platform-independent classes and applications.
• The client code works with the high-level abstractions.
At no point, it is exposed to the implementation details
of the code.
• It follows the SRP by solving only one crucial problem
of separating the abstractions and implementations
from each other.
• It also follows the Open/Closed Principle by allowing
addition of several new elements without impacting the
original code.
As it is with the Design Patterns that we have seen so far, a
potential disadvantage of using the Bridge Pattern is that it
can increase the complexity of the code if not used
correctly.

Composite Design Pattern


The Composite Design Pattern is a Structural pattern that
allows you to deal with hierarchical structures such as tree-
like data structures or graphical user interfaces. It lets you
compose objects into tree structures to represent part-
whole hierarchies. It allows clients to treat individual
objects and compositions of objects uniformly.

Problem scenario
The Composite Design Pattern proves invaluable in
scenarios such as managing a file system in an operating
system, where there are two distinct objects: files and
folders. A folder can contain multiple files as well as other
folders, creating a hierarchical structure.
Now, imagine we are tasked with calculating the total size
of a folder, which includes the sizes of all nested files and
folders within it.
JavaScript developers grappling with this task encounter
several hurdles. Firstly, dealing with different types of
items becomes a concern, as the Folder class must manage
both files and other folders. Designing the composite
structure to accommodate diverse item types while
upholding a consistent interface demands thoughtful
consideration. Additionally, the complexity is heightened by
the fact that the Folder class also handles other folders,
introducing the challenge of circular references.
Implementing checks to prevent infinite loops during
operations becomes a crucial aspect of the solution.
This overview only scratches the surface, and further
exploration into the implementation of this coding problem
reveals additional intricacies.

Implementation
The implementation of the Composite Design Pattern can
be broken down into four distinct steps:

Step 1: Define the component interface or the base


class
In a tree structure, a leaf node is the node that does not
have any child nodes. To think of it in the context that we
have seen above, a leaf node could be a file which cannot
have any child element inside it, unlike a folder which can
possess another folder. However, you have to keep in mind
that an empty folder, i.e., a folder that has no more child
elements inside it can also be termed as a leaf node. In the
very step to implement the Composite Design Pattern, we
create the interface or a base class that declares common
operations for both leaf and composite components.

Step 2: Implement leaf class (Individual components)


In this step, we will create a class or interface for the leaf
classes, i.e., individual components that do not have any
child nodes. This class will be extending the component
interface that we have created in the first step.

Step 3: Implement composite class


Now, we create a class for the composite containers
(containers of other components). This class will also
extend the component interface that we had created in the
first step. Additionally, this class should contain a collection
to store child components.

Step 4: Client code


In the final step of the implementation, we use the created
class to compose and work with the hierarchical structure.
Now, let us solidify our understanding of the
implementation steps for the Composite Design Pattern
with a concrete code example to reinforce the topic.

Code example
let us put the steps for implementing the Composite
Pattern, as discussed in the previous section, into action.
We will use the same example of a file system that we have
discussed in the Problem scenario segment for Composite
Pattern.

Step 1: Define the component interface or the base


class
In the very first step, we need to define the root component
interface which will be extended by both the leaf nodes and
the composite nodes. In our case, for the sake of simplicity,
let’s consider only files to be the leaf nodes while the
folders can be considered as a composite nodes. Therefore,
in our scenario, this component interface will be extended
by both the leaf node (file) and the composite node
(folder).Let us look at the code to implement that:
1. class Component {
2. constructor(name) {
3. [Link] = name;
4. }
5.
6. // Declare common operations
7. operation() {
8. throw new Error("Operation must be implemented by
subclasses");
9. }
10. }
In the provided code example, we have established the
class Component. Its constructor accepts a name
argument, which is then assigned to the class property
name. This class acts as the foundational element, and to
reinforce its role, we have included the operation method,
signalling that it must be implemented by the subclasses.

Step 2: Implement leaf Class (Individual components)


As previously mentioned, we will designate only the File
class as our leaf node, implying that the File class will not
have any child elements. In this step, let us create the class
for a leaf node.
1. class File extends Component {
2. constructor(name, size) {
3. super(name);
4. [Link] = size;
5. }
6.
7. operation() {
8. [Link](`File: ${[Link]}, Size: ${[Link]}
KB`);
9. }
10. }
In the given example, we have introduced the File class,
extending the Component class. Leveraging the super
method, we inherit the name property from the
Component class. Furthermore, an additional property
named size is introduced, accepted as an argument in the
constructor. To tailor the behaviour for the File class, we
override the operation method defined in the Component
class.

Step 3: Implement composite class


Following the leaf node implementation, our next step
involves creating an interface or a class for our composite
elements. In this case, we will designate folders, which can
contain other folders or files, as our composite elements.
Let us proceed by defining the class for it.
1. class Folder extends Component {
2. constructor(name) {
3. super(name);
4. [Link] = [];
5. }
6.
7. // Add a child component
8. add(child) {
9. [Link](child);
10. }
11.
12. // Remove a child component
13. remove(child) {
14. const index = [Link](child);
15. if (index !== -1) {
16. [Link](index, 1);
17. }
18. }
19.
20. // Implement operation to traverse and perform
actions on children
21. operation() {
22. [Link](`Folder: ${[Link]}`);
23. for (const child of [Link]) {
24. [Link]();
25. }
26. }
27. }
In this segment, we have defined the Folder class for our
composite elements, extending the Component class.
Similar to the leaf node interface, we inherit the name
property using the super method. Since folders can contain
other folders or files, a new property called children is
introduced, initialized with an empty array.
Given that the Folder class can now contain children, we
formulate methods to manage these children. First, we
create two dedicated methods, add and remove, to
facilitate the addition and removal of children in the
children array. Additionally, we override the operation
method to print the name of the folder and then traverse
through each of its children, invoking their operation
method to print their names and sizes, as defined in the
File class.

Step 4: Client code


In the final step of the implementation, let us first examine
the code example before delving into how to execute the
last piece of the puzzle:
1. const rootDirectory = new Folder("Root");
2.
3. const file1 = new File("[Link]", 200);
4. const file2 = new File("[Link]", 500);
5.
6. const subDirectory = new Folder("SubDirectory");
7. const file3 = new File("[Link]", 300);
8.
9. [Link](file3);
10.
11. [Link](file1);
12. [Link](file2);
13. [Link](subDirectory);
14.
15. // Perform operations on the composite structure
16. [Link]();
For the final execution, we begin by creating a variable
rootDirectory and store within it an instance of the Folder
class with the argument "Root". This instance serves as
our root directory. Subsequently, we create two more
variables, storing instances of two files—file1 and file2.
Following that, another folder is created, stored in
subDirectory, and a final file, file3, is created.
Next, we add file3 to the subDirectory and add both file1
and file2 to the rootDirectory. As the concluding step, we
add the subDirectory to our rootDirectory and invoke its
operation method. When you run this code, the following
values will be displayed on your console:
1. Directory: Root
2. File: [Link], Size: 200 KB
3. File: [Link], Size: 500 KB
4. Directory: SubDirectory
5. File: [Link], Size: 300 KB
Indeed, by invoking the operation method of the
rootDirectory, we successfully obtain the entire folder
structure, including all the nested files and other folders,
printed in our console. This exemplifies the Composite
Design Pattern in action, allowing us to seamlessly work
with both individual leaf nodes (files) and composite
elements (folders) in a unified manner.

Applications
The Composite Design Pattern is particular useful in
JavaScript for managing hierarchical structures where
individual objects and compositions of objects need to be
treated uniformly. Here are some scenarios where the
Composite Pattern is highly effective:

GUI components
Graphical user interfaces often involve a hierarchy of
components like panels, buttons, and text fields. The
Composite Pattern enables uniform treatment of individual
GUI elements and complex layouts, simplifying their
creation and manipulation..

Tree structures
This pattern is ideal for scenarios involving tree-like
structures such as organizational hierarchies, family trees,
or nested categories. It provides a consistent way to
traverse and manipulate these structures while reducing
complexity.

Document Object Model manipulation


The DOM in web development can be represented as a
composite structure. Using the Composite Pattern allows
developers to perform operations on individual HTML
elements or complex nested document structures in a
consistent manner.

File system operations


In file systems, directories can contain both files and
subdirectories. The Composite Pattern enables uniform
operations on the entire structure, such as calculating
directory sizes or applying bulk actions.

Menu systems
Nested menus, such as those in navigation systems, benefit
from this pattern. Each menu item can act as a leaf
(individual menu option) or a composite (submenu with
options), allowing for seamless management of the
hierarchy.

E-commerce shopping cart


In e-commerce applications, shopping carts often contain
individual products and product bundles (composed of
multiple products). The Composite Pattern simplifies
operations like calculating total prices or applying
discounts uniformly across all items.

Pros and cons


Let us look at some of the benefits of using Composite
Pattern in a JavaScript code:
• It is easier to work with complex tree structures, as we
have seen in the implementation examples above.
• Additionally, it follows the Open/Closed Principle,
where new elements can be added to the system
without breaking the original code.
Unfortunately, the biggest con of using this pattern is its
difficulty in providing a common interface for classes
whose functionality differs too much. Overgeneralizing the
central component could lead to more complications than
required.

Decorator Design Pattern


The Decorator Design Pattern is a Structural Design
Pattern that allows behaviours to be added to an object,
either statically or dynamically, without affecting the
behaviour of the other objects from the same class.

Problem scenario
Upon reviewing the description above, one might initially
consider solving this problem using one of the Design
Patterns encountered so far, or even resorting to simple
inheritance. However, the intricacies of the problem reveal
a layer of complexity that cannot be effectively addressed
by the patterns we have explored thus far. To comprehend
the problem scenario that can be effectively tackled by a
Decorator Design Pattern, let’s explore a real-life analogy.
Consider a car manufacturing company that produces
standard cars equipped with a set of basic features,
including standard seats, a dashboard, and an air-
conditioning system. However, after a customer purchases
a car, they often desire to customize it according to their
preferences. Some may opt for leather seats to enhance
comfort, while others might want to install a sunroof or
upgrade to a superior sound system.
The challenge lies in enabling customers to customize their
cars with optional features such as leather seats, a sunroof,
and an advanced sound system without resorting to
creating an exhaustive set of subclasses for every
conceivable combination of features. For example, some
customers may desire only leather seats, while others
might prefer a combination of leather seats and an
advanced sound system. This leads to numerous
permutations and combinations, and as more services and
additional features are introduced, the system becomes
more error-prone and increasingly complex. We want a
flexible solution that allows customers to pick and choose
additional features dynamically.

Implementation
Let us break down the implementation of the Decorator
Design Pattern into distinct steps:

Step 1: Define the component interface


Let us start with the atomic task of building the interface of
the base class, i.e., the class that will be extended by all the
concrete components to add their respective versions or set
of features.

Step 2: Create concrete component


Now, we implement a concrete component that extends our
base component interface. This will be the basic object that
we want to decorate.

Step 3: Create the decorator class


To incorporate decorators or specific sets of features into
our base component, we initially need to establish an
interface that facilitates this extension. Consequently, we
create the decorator class that extends the base component
interface. However, this class holds a reference to the
concrete component object, enabling it to wrap and
augment the behaviour of that object.
Step 4: Create concrete decorators
With the interface in place for creating decorators, our next
step involves crafting concrete decorators by extending the
decorator class. These classes have the capability to add or
modify behaviour while ensuring compatibility with the
base component interface.

Step 5: Usage
The ultimate step is to create instances of the concrete
component and decorators. We need to compose them in a
way that builds the desired combination of behaviour.
As done previously, let us concretize these implementation
steps through a code example to enhance our
understanding.

Code example
We have outlined the steps for implementing the Decorator
Design Pattern. Now, let’s put them into practice with a
simple example of making coffee. In this scenario, we have
a base variety of coffee with a certain cost. Users can opt to
add their own decorators to this base coffee, such as milk
and/or sugar, and the price will be adjusted accordingly.
Let’s take a look:

Step 1: Define the component interface


Our very first step is to define the interface or the base
class for our primary component, i.e., the coffee. So, let us
go ahead and do that:
1. class Coffee {
2. cost() {
3. return 5;
4. }
5. }
We have created a straightforward class Coffee with no
properties but a single method, cost, that returns the value
as 5 dollars. As we intend to use this cost method to
enhance or update the cost of a coffee, we have opted to
keep it as a number.

Step 2: Create concrete component


Next, we proceed to create a concrete component that will
construct the base version of our component. Let us
proceed with this step:
1. class SimpleCoffee extends Coffee {
2. // You can override the methods if needed
3. cost() {
4. return [Link]();
5. }
6. }
In this step, we have introduced a class SimpleCoffee that
extends our base class, Coffee. Since decorators will be
adding their respective variations to this simple coffee, we
utilize the inherited method cost from the Coffee class.
Inside this method, we return the base price of the coffee
using the [Link]() method.

Step 3: Create the decorator class


Our next logical step is to define the interface or the base
class for our multitude of decorators that we can create:
1. class CoffeeDecorator extends Coffee {
2. constructor(coffee) {
3. super();
4. this._coffee = coffee;
5. }
6.
7. cost() {
8. return this._coffee.cost();
9. }
10. }
In the provided code example, we have introduced a
CoffeeDecorator class that extends our original base
component. This extension is necessary because the
decorators essentially add their own flavour to the actual
base component. Therefore, it is crucial to ensure that they
have access to the original base component. In this code
snippet, we accept an instance of coffee in the constructor,
storing it as the local property _coffee. Simultaneously, we
inherit the properties of the Coffee class using super().
The cost method from the Coffee class is overridden, and
it is used to return the cost of the coffee received in the
constructor’s argument while creating a new instance of
the decorator.

Step 4: Create concrete decorators


In this step, we leverage the decorator base class created
in the previous step to craft our decorators of the base
component, Coffee. Let’s first examine the code example
before delving into a detailed understanding:
1. class MilkDecorator extends CoffeeDecorator {
2. cost() {
3. return [Link]() + 2;
4. }
5. }
6.
7. class SugarDecorator extends CoffeeDecorator {
8. cost() {
9. return [Link]() + 1;
10. }
11. }
In the provided code example, two decorators,
MilkDecorator and SugarDecorator, are created to
impart their specific flavours to our base component,
Coffee. These decorators extend the CoffeeDecorator
interface, effectively isolating the entire decorator
instances from the original code. This ensures that the
original code remains unaffected and unmodified.
Within each respective decorator, the cost method is
overridden to include the specific cost of the decorator in
addition to the original coffee cost.

Step 5: Usage
For our final step in using the Decorator Pattern, we will
create instances of the concrete components and
decorators, structuring them in the desired way. Let’s
proceed with this step:
1. // Usage
2. const simpleCoffee = new SimpleCoffee();
3. [Link]("Cost of simple coffee:",
[Link]());
4.
5. const milkCoffee = new MilkDecorator(simpleCoffee);
6. [Link]("Cost of coffee with milk:",
[Link]());
7.
8. const sugarMilkCoffee = new
SugarDecorator(milkCoffee);
9. [Link]("Cost of coffee with sugar and milk:",
[Link]());
In the above code example, we have first created the
instance of the simple coffee. Next, we create the instance
of the milk coffee and add the simple coffee to its decorator
instance. Finally, we make a coffee with both sugar and
milk, and add the milk coffee instance to the
SugarDecorator class to build this version.
On running the above code, you will receive the following
output on your console:
1. Cost of simple coffee: 5
2. Cost of coffee with milk: 7
3. Cost of coffee with sugar and milk: 8
This demonstrates the substantial advantage of using the
Decorator Pattern to address this crucial problem.
Throughout the final usage, there was no direct access or
reference to the original code. It remained well
encapsulated behind the scenes, safeguarding it while still
influencing the overall behaviour. Furthermore, users only
need to define several decorators in the initial steps. They
can then employ these decorators as desired to create their
personalized versions of coffee. This makes the process
highly user-friendly, concealing the implementation details
to a significant extent.

Applications
The Decorator Design Pattern in JavaScript can be useful in
various scenarios where you want to add or modify
functionality of objects dynamically and flexibly. Here are a
few examples:

User interface components


Decorator Patterns can prove highly beneficial in
constructing UI components. Consider a base UI
component class, such as a button, where users can
dynamically add features like tooltips, borders, or
additional styling without altering the existing code. This
flexibility allows for the creation of customizable and
feature-rich UI elements, enhancing the user interface
without complicating the underlying structure.

Data validation
Decorator Patterns can be useful when we have data
validation requirements for our code. For instances where
we want to add validation rules to objects without
modifying their original validation logic, these patterns can
come to our assistance. For example, decorating a form
validation object with additional validation rules like
checking for password complexity or email format.

Authentication
We can use the Decorator Patterns for instances where we
want to add authentication checks to certain functions or
methods without modifying their core functionality. For
instance, decorating a resource-fetching function with
authentication checks before making the actual request.

Dynamic configuration
We can use these patterns to dynamically configure objects
with different settings without altering their original
structure.

Event handling
We can also use Decorator Patterns to dynamically attach
or detach event handlers to objects without modifying their
existing event handling code. Decorating a DOM element
with additional event listeners based on user interactions.
This could allow us to work independently of the element’s
implementation and not affect it.

Pros and cons


Let us look at the advantages of implementing the
Decorator Pattern in our JavaScript code:
• We can extend an object’s behaviour without making a
new subclass specifically for its demand.
• We can add or remove responsibilities from the object
at runtime.
• Thanks to Decorator Patterns, we can combine several
behaviours on to an object by using multiple decorators.
• The Decorator Pattern adheres to the Single
Responsibility Principle by addressing the challenge of
extending an object’s behaviour without necessitating
the creation of multiple new classes for various
permutations and combinations.
However, like every Design Pattern, the Decorator Pattern
also has its fair share of disadvantages listed down below:
• Ensuring that a decorator’s behaviour remains
independent of the order in the decorators stack can be
challenging during implementation.
• The initial configuration code of the various layers can
tend to look ugly and complex.

Façade Design Pattern


The Façade Design Pattern is a Structural Design Pattern
that provides a simple interface to a set of interfaces in a
subsystem. It involves creating a higher-level interface that
makes it easier to use a complex system or set of interfaces
by providing a unified interface. In JavaScript, the Façade
Pattern is often used to hide the complexities of interacting
with multiple components or APIs behind a single,
simplified interface.

Problem scenario
To understand the efficacy of the façade Design Pattern, let
us consider the example of a web application that needs to
fetch and display the real-time weather information for a
given location. To achieve this, we would require a
probable integration of multiple weather APIs to gather
comprehensive data like current temperature, humidity,
wind speed, and forecasts.
However, this introduces a series of challenges. For
instance, each weather API might have its own unique set
of endpoints, request formats, and authentication
mechanisms. Additionally, some APIs may necessitate
additional processing or data transformation before it can
be consistently presented in your application.
However, the challenges persist. The codebase might
become cluttered with API-specific logic, leading to
difficulties in maintenance and extension. If the third-party
API undergoes changes in its implementation, such as
adding or removing endpoints or modifying the
authentication mechanism, it would necessitate widespread
modifications throughout the application. Managing API
keys, authentication tokens, and error handling for each
API becomes a complex and error-prone task.
We would require a solution that makes it easier to manage
the complexities arising with such scenarios.

Implementation
Let us break down the implementation of the Façade
Design Pattern into distinct steps. The initial step, even
before diving into the actual implementation logic, is to
understand and identify the complex subsystem or set of
interfaces that need simplification. Once the subsystem
requiring modification is identified, the implementation
process begins:

Step 1: Create the subsystem classes


The very first thing we need to do is to create the classes
representing the subsystem components. These classes will
handle the interactions with the respective subsystems.

Step 2: Create the façade classes


Next, we create a façade class that provides a simplified
interface for the client code. This class encapsulates the
interactions with the subsystem components. Its primary
purpose is to simplify the interactions between the client
code and the complex subsystem.

Step 3: Encapsulate the subsystem logic into the


façade
Now, we need to encapsulate the logic and interactions
with the subsystem components. The client code should
interact with the facade and not need to know the details of
the individual implementation logics.

Step 4: Expose simplified methods


Expose simplified methods in the Facade class that the
client code can use. These methods should provide a
coherent and easy-to-understand API, hiding the intricate
details of the subsystem components. The Facade should
handle any necessary coordination or translation between
subsystem calls.

Step 5: Client code uses the façade


In the client code, instantiate the Façade class and use its
simplified methods to perform tasks. The client code
interacts solely with the Façade, unaware of the intricacies
of the underlying subsystem components. This separation
enhances code readability, maintainability, and flexibility.
Let us understand this better with a coding example in the
next segment.
Code example
Let’s break down the implementation of the Facade Design
Pattern in JavaScript using the example of an application
that allows sharing to multiple social media platforms,
namely Facebook, Instagram, and Twitter. Our main
objective is to simplify the process of handling multiple API
integrations without impacting our codebase.
The initial step before delving into the implementation of
the Facade pattern is to comprehend the essence of the
problem. In our scenario, the issue arises from the
complexity stemming from different integrations with each
social media platform. Variations in endpoints,
authentication mechanisms, and intricate implementation
logic pose challenges when integrating with these APIs.
Consider a component that needs to use all three APIs
simultaneously; handling all three API integrations within a
single component becomes a daunting task. The code
would be cluttered with complex logic, including
conditionals to manage each API integration. This problem
intensifies when similar integrations are required for other
components, leading to code repetition and the need for
modification in those components. Additionally, introducing
a new social media platform into the system would be a
complex and time-consuming process. Essentially, the
Facade Design Pattern aims to address and simplify these
challenges.

Step 1: Create the subsystem classes


Our very first job would be to create classes that represent
each of the subsystem. In our example, we will create
classes for interacting with Facebook, Twitter, and
Instagram APIs.
1. class FacebookAPI {
2. postToFacebook() {
3. // Implementation specific to posting on Facebook
4. }
5. }
6.
7. class TwitterAPI {
8. tweet() {
9. // Implementation specific to tweeting on Twitter
10. }
11. }
12.
13. class InstagramAPI {
14. shareOnInstagram() {
15. // Implementation specific to sharing on Instagram
16. }
17. }
In the provided code example, three distinct classes,
namely FacebookAPI, TwitterAPI, and InstagramAPI,
have been established for the three subsystems. Each class
is equipped with a unique method for sharing content on its
respective platform. Implementation details, such as public
keys or authentication mechanisms, can be encapsulated
within these functions based on the specific requirements
of each platform.

Step 2: Create the façade classes


Now, as our implementation dictates that we create a
façade class that provides a simplified interface for the
client code. Let’s create that:
1. class SocialMediaFacade {
2. constructor() {
3. [Link] = new FacebookAPI();
4. [Link] = new TwitterAPI();
5. [Link] = new InstagramAPI();
6. }
7.
8. // Methods to be exposed to the client will be
implemented here
9. }
In the provided code snippet, a class named
SocialMediaFacade has been introduced to encapsulate
interactions with the subsystem components. Three
properties (facebookAPI, twitterAPI, and instagramAPI)
have been defined to store instances of the respective
subsystems (FacebookAPI, TwitterAPI, and
InstagramAPI). This section of the logic primarily involves
the instantiation of subsystem instances.

Step 3: Encapsulate the subsystem logic into the


façade
Next, we need to add the subsystem logic into the façade.
Our goal, we should remember, is to give the client code a
single point of access for the integration, so that most of
the implementation logic is encapsulated. Essentially, the
client code should be interacting with the façade and not
the individual social media APIs.
1. class SocialMediaFacade {
2. constructor() {
3. [Link] = new FacebookAPI();
4. [Link] = new TwitterAPI();
5. [Link] = new InstagramAPI();
6. }
7.
8. // Encapsulate the logic for sharing on all platforms
9. shareOnAllPlatforms() {
10. const facebookResult =
[Link]();
11. const twitterResult = [Link]();
12. const instagramResult =
[Link]();
13.
14. return { facebookResult, twitterResult,
instagramResult };
15. }
16. }
In the provided code snippet, a shareOnAllPlatforms
method has been added to the previously created
SocialMediaFacade class. This method contains the
implementation logic for interacting with individual APIs.
For example, it calls the postToFacebook method of the
facebookAPI instance and stores the result in the variable
facebookResult. Similar actions are performed for Twitter
and Instagram. The method returns an object containing
the results, allowing the client code to receive the
outcomes upon invocation.

Step 4: Expose simplified methods


In our example scenario, the previous step has covered
both steps 3 and 4. By creating the method
shareOnAllPlatforms in the previous example, we have
also exposed it to be used by the client code on
instantiation. By using only the shareOnAllPlatforms
method, we make sure that the client is not exposed to the
internal logic for implementing the APIs.

Step 5: Client code uses the façade


In the final step of our implementation, we consolidate the
logic we’ve developed so far. This involves allowing the
client code to utilize the façade for simplified interaction
with multiple social media platforms.
1. const socialMediaFacade = new SocialMediaFacade();
2. const results =
[Link]();
3. [Link](results);
Indeed, by using the SocialMediaFacade class, the client
code benefits from a simplified and unified interface to
interact with multiple social media platforms. We simply
invoke the shareOnAllPlatforms method instead of
handling all the different API integrations. This shields the
client from the complexities of individual APIs and provides
a convenient way to share content across various
platforms.

Applications
The Facade pattern in JavaScript can be beneficial in
various scenarios where you want to simplify and provide a
unified interface to a complex subsystem or set of
functionalities. Here are a few scenarios where the Facade
pattern can be useful:

Working with multiple APIs


As we have seen in the coding example above, the Façade
Design Pattern is perfect for scenarios where we need to
work with multiple external APIs, each with its own set of
endpoints, authentication mechanisms, and response
formats. The Facade can hide the details of API
communication, making it easier for the client code to
interact with diverse services.

Browser compatibility issues


When dealing with cross-browser compatibility issues, the
Facade can abstract away the differences in browser-
specific implementations, providing a consistent interface
for the client code to work with.

Legacy code integration


Integrating new features or modules with existing legacy
code can be challenging. A Facade can be used to create a
modern and simplified interface for the legacy code,
allowing new components to interact with the old system
seamlessly.

Resource management
When dealing with resource management, such as handling
connections to databases, caches, or file systems. The
Facade can encapsulate the initialization, configuration,
and clean-up processes, providing a straightforward
interface for resource management.

Third-party library integration


Integrating third-party libraries that have intricate setups
and configurations. The Facade can encapsulate the
necessary steps to initialize and use the library, exposing a
simplified interface for the client code.
In these scenarios, the Facade pattern helps improve code
organization, reduce dependencies, and enhance
maintainability by providing a clean and simplified
interface for the client code.

Pros and cons


The advantage of using the Façade Design Pattern in our
code is that it can isolate our code from the complexity of
the system.
At the same time, the major disadvantage here is that it can
also end up becoming a God object, thereby introducing
tight coupling in our code and that can prove to be
disastrous to our code.

Flyweight Design Pattern


The Flyweight Design Pattern is a Structural Design
Pattern that is used to reduce the memory footprint or
computational expense of an object by sharing as much as
possible with related objects. This pattern is particularly
useful when a large number of similar objects need to be
created, and the overhead of creating and managing each
individual instance is too high.
In the context of JavaScript, this pattern can be
implemented using a combination of shared and non-shared
data, where the shared data represents the part of an
object that can be shared among multiple instances, while
the non-shared data in unique to every instance.

Problem scenario
Let us examine a scenario that can shed light on the types
of issues that can be addressed by the Flyweight Design
Pattern. Let’s consider the example of game development.
In a video game scenario, such as the Grand Theft Auto
(GTA) series, numerous non-player characters (NPCs)
populate the virtual world. NPCs are characters that exist
in the game environment but are not controlled by the
player. They may include citizens going about their
routines while the main character undertakes missions.
Each NPC in the game possesses common characteristics,
like appearance, behaviour, or animations (considered
intrinsic state), along with unique properties such as
positions and health (considered extrinsic state). Creating a
distinct object for every NPC that encompasses both
intrinsic and extrinsic states could result in significant
memory consumption, especially considering the large
number of NPCs present in a typical game.
We would require a solution that can help us save memory
by grouping and sharing the common properties. This
separation of the common properties and the unique data
of every object can help reduce memory consumption and
improve the efficiency of the application.

Implementation
Let us go through the step-by-step process of implementing
the flyweight Design Pattern in a JavaScript code:

Step 1: Identify the intrinsic and extrinsic states


The first step in implementing the Flyweight Design
Pattern involves clearly identifying and differentiating
between intrinsic and extrinsic properties. Intrinsic
properties are those that remain constant across multiple
instances and can be shared. These properties are stored
within the flyweight object to minimize memory usage.
Extrinsic properties are unique to each instance and
cannot be shared. These are passed as parameters during
method calls to maintain flexibility while keeping the
shared object lightweight. For instance, imagine a text
editor displaying a large document with various characters.
The intrinsic state could include the character’s font type,
style, and size, which are shared across multiple characters
of the same type. The extrinsic state could be the position
(x, y coordinates) of each character on the screen, which is
unique to every character instance.

Step 2: Create the flyweight interface


Next, we need to define an interface or the base class that
will declare the method(s) that will be used by the concrete
flyweights that will extend this class. This interface usually
includes the operation method.

Step 3: Implement concrete flyweight class


Next, we need to create a concrete flyweight class that
implements the flyweight interface. This class represents
the shared (intrinsic) state.

Step 4: Create a flyweight factory


Now, we need to implement a flyweight factory that
manages the creation and retrieval of flyweight instances.
The factory ensures that instances are shared when
possible.

Step 5: Client code


As the final step, we will create instances of the flyweight
factory in the client code and use it to get or create
flyweight instances.
An additional step can be added here where test the
implementations to ensure that shared and unique states
are handled correctly, and refine the design as needed
based on testing and performance considerations.
To gain a better understanding of this implementation, let’s
explore a real-life coding example in the next section.

Code example
Let us apply the example of a music-playing application to
implement the steps we discussed in the previous section
and incorporate the flyweight Design Pattern into our
codebase. Imagine we are building a music streaming
service where users can create playlists with a large
number of songs. Each song has common properties like its
title, artist, and duration (intrinsic state), but users can add
songs to different playlists, each with its own order and
play count (extrinsic state).

Step 1: Identify the intrinsic and extrinsic states


In the first step, we will identify and differentiate the
intrinsic and extrinsic properties or states for our
application. So, let’s go ahead and define that:
• Intrinsic properties: The properties that can be
shared among multiple instances – title, artist, duration.
• Extrinsic properties: The properties that will be
unique to each instance - order in playlist, play count.

Step 2: Create the flyweight interface


Now, we need to define the interface or the base class that
will act as an interface for our flyweight objects.
1. // Flyweight interface
2. class SongFlyweight {
3. constructor() {
4. [Link] = null; // Shared properties
5. }
6.
7. operation(extrinsicState) {
8. // Shared behaviour using intrinsic and extrinsic
states
9. }
10. }
So, we have now created the class SongFlyWeight that
will be implemented to create the flyweight objects. In the
constructor, we have created a property intrinsicState
that will include the shared properties and an operation
method that accepts an argument extrinsicState to
represent shared behaviour.
Step 3: Implement concrete flyweight class
Now that we’ve created the interface or the base class to
derive a concrete class, Let us examine its code
implementation before delving into an understanding of the
code:
1. // Concrete Flyweight
2. class ConcreteSongFlyweight extends SongFlyweight {
3. constructor(title, artist, duration) {
4. super();
5. [Link] = { title, artist, duration };
6. }
7.
8. operation(extrinsicState) {
9. // Implement shared behavior using intrinsic and
extrinsic states
10. }
11. }
In this example, we extended the SongFlyWeight class to
create the concrete class ConcreteSongFlyweight. By
using the super method, we inherited the properties
defined in the SongFlyWeight class. Simultaneously, an
instance of this class must provide the title, the artist, and
the duration of the song, which we have designated as
intrinsic properties—properties shared by the objects. We
will let the operation method be used in the client code.

Step 4: Create a flyweight factory


Next for the sake of separation of concerns, we will create
a flyweight factory that will be responsible for managing
the creation and retrieval of flyweight objects:
1. class SongFlyweightFactory {
2. constructor() {
3. [Link] = {};
4. }
5.
6. getSongFlyweight(title, artist, duration) {
7. const key = `${title}-${artist}-${duration}`;
8. if (![Link][key]) {
9. [Link][key] = new SongFlyweight(title,
artist, duration);
10. }
11. return [Link][key];
12. }
13. }
The getSongFlyweight method now constructs the key by
including the duration along with the title and artist. This
ensures that each unique combination of title, artist, and
duration results in a distinct key, preventing collisions
where songs with the same title and artist but different
durations might incorrectly share the same flyweight
object.
By doing so, the Flyweight Pattern is implemented more
robustly, accurately reflecting the uniqueness of each song
instance.

Step 5: Client code


Now, let us apply the defined code to implement it in the
client code:
1. // Client code
2. const songFlyweightFactory = new
SongFlyweightFactory();
3.
4. const song1 =
[Link]('Song A',
'Artist X', '3:30');
5. const song2 =
[Link]('Song B',
'Artist Y', '4:15');
6.
7. [Link]({ order: 1, playCount: 10 });
8. [Link]({ order: 2, playCount: 5 });
In the client code, we create an instance of the
SongFlyweightFactory. We then use the factory to get or
create SongFlyweight instances (song1 and song2) by
providing unique intrinsic properties. Finally, we call the
operation method on each instance, passing extrinsic
properties like order in the playlist and play count.
By following these steps, you create a system where shared
properties of songs are efficiently managed by flyweights,
reducing memory overhead in a music playlist application.
This allows you to handle a large number of similar objects
with less memory consumption.

Applications
The Flyweight Pattern can be effective in various scenarios
in JavaScript, especially when dealing with a large number
of similar objects where memory usage needs to be
optimized. Here are some scenarios where the Flyweight
Pattern can be beneficial:

Rendering engine in a game


In a game development scenario, if we have a multitude of
similar objects (e.g., trees, rocks) with shared
characteristics (e.g., texture, colour), we can use the
Flyweight Pattern to optimize memory usage by sharing
common properties among instances.

UI components in a web application


In a web application with numerous UI components (e.g.,
buttons, dropdowns) that share common styles, we can use
the Flyweight Pattern to manage the common styling
properties centrally, reducing redundancy and improving
performance.

Document Object Model nodes


When manipulating the DOM in a web application, if we
have a large number of similar elements with shared
properties (e.g., class names, styles), we can use the
Flyweight Pattern to efficiently manage and share these
common properties.

Collaborative editing tools


In a collaborative editing tool, if we have multiple users
working on a document and using similar text formatting
options, we can use the Flyweight Pattern to share the
formatting information to minimize the memory footprint.
In each of these scenarios, the Flyweight Pattern can help
optimize memory usage by sharing common properties
among similar objects, leading to improved performance
and reduced resource consumption.

Pros and cons


The advantage of using the Flyweight Design Pattern in our
code is that it can save lots of RAM, provided the program
has lots of similar objects.
However, there are two major disadvantages of the
Flyweight Design Pattern:
• We might be trading RAM for CPU cycles when
recalculation would happen every time someone calls a
flyweight method.
• The code becomes far too complicated, if not handled
correctly.

Proxy Design Pattern


The Proxy Design Pattern is a Structural Design Pattern
that provides surrogate or a placeholder for another object.
A proxy controls the access to the original object, allowing
us to add extra behaviour or restrictions. This can be useful
for various scenarios like lazy loading, access control, and
so on.

Problem scenario
To understand the problem solved by the proxy Design
Patterns, let’s take a very relevant example of a photo-
sharing website where users can upload and view images.
As users scroll through their feed, the application needs to
load and display images. However, some images might be
very large in size, leading to a slower loading time and a
poor user experience.
In this scenario, loading all the images at once, especially
the larger ones, can result in a very sluggish performance.
Users generally do not like to wait for the images to load,
and they will end up being frustrated and stop using the
application altogether. Additionally, downloading large
images can consume a significant amount of bandwidth,
impacting both server and client resources.
The challenge is to optimize the image loading process,
ensuring that only the images currently in the user’s
viewport are loaded. Loading images only when necessary
can improve the application’s performance and reduce the
strain on network resources.

Implementation
Let us go through the step-by-step process of implementing
the Proxy Design Pattern in our codebase:

Step 1: Identify the subject (real object)


The very first thing that we need to do is to identify the
original object (subject) for which we want to control
access. This is the object that the proxy will act as a
surrogate for.

Step 2: Create the proxy


Next, we will implement a proxy class that will act as a
substitute or placeholder for the real object. The proxy
controls access to the real object and can add additional
behaviour.

Step 3: Define the proxy interface


Now, we need to create a proxy interface, i.e., a common
interface that both the real object and the proxy will
implement. This ensures that the proxy can be used
interchangeably with the real object.

Step 4: Implement the real object


Now, we need to develop the class for the real object,
which contains the actual functionality that the proxy will
control access to.

Step 5: Implement the proxy


Now, we will create the proxy class, which wraps around
the real object. The proxy intercepts requests and can add
additional functionality or control access to the real object.

Step 6: Utilize the proxy in client code


As a final step, we will replace instances in our application
where we use the real object, with the proxy. This allows
the proxy to control access to the real object and apply the
additional behaviour.
While it’s fairly easy to comprehend these steps, let’s have
a look at a coding example in the next section to help us
understand better.

Code example
To understand the implementation of a Proxy Pattern
effectively, let’s explore the concept of lazy-loading in web
applications. We will break down the process into steps for
a clearer understanding and implementation.

Step 1: Identify the subject (real object)


In our example, the subject will be the image loader. So, let
us create a class surrounding it:
1. // Real Object (Subject)
2. class ImageLoader {
3. loadImage(url) {
4. [Link](`ImageLoader: Loading image from
${url}`);
5. // Actual image loading logic
6. }
7. }
In this example, we have defined a class ImageLoader
with a single method loadImage that takes a url as a
parameter. The loadImage method simulates loading an
image by printing a message indicating the URL from
which the image is being loaded. This serves as a basic
representation of a loading logic in a real-world scenario.

Step 2: Create the proxy


In the subsequent step, we aim to create a proxy for our
main subject, the ImageLoader class. This proxy will be
designed to intercept and control access to the loadImage
method. Let’s proceed with the implementation of this
proxy:
1. // Proxy
2. class ImageProxy {
3. constructor() {
4. [Link] = new ImageLoader();
5. }
6.
7. loadImage(url) {
8. // Proxy logic for lazy-loading
9. [Link](`ImageProxy: Request received for
image from ${url}`);
10. // Additional proxy logic for lazy-loading
11. [Link](url);
12. }
13. }
In this example, we have effectively implemented a proxy
class for our ImageLoader. The proxy, represented by the
ImageProxy class, contains an instance of the
ImageLoader (stored in the imageLoader property). We
have replicated the loadImage method from the
ImageLoader and introduced a statement to indicate that
this is the image proxy. Additionally, we’ve incorporated an
extra logic for lazy-loading. The proxy utilizes the
loadImage method of the imageLoader instance to load
the actual image when needed.

Step 3: Define the proxy interface


Now, let’s proceed with the crucial step of defining the
proxy interface that both the real object and the proxy can
implement. Let’s go ahead and create that:
1. // Interface for both Real Object and Proxy
2. class ImageInterface {
3. loadImage(url) {
4. // Common method for image loading
5. }
6. }
In the example above, We have introduced the class
ImageInterface, which features a singular method –
loadImage. This method is shared between both the proxy
and the original subject.

Step 4: Implement the real object


In this step, we need to make the real object implement the
proxy interface:
1. // Real Object (Subject)
2. class ImageLoader extends ImageInterface {
3. loadImage(url) {
4. [Link](`ImageLoader: Loading image from
${url}`);
5. // Actual image loading logic
6. }
7. }
Here, we have modified our original class to implement the
ImageInterface that we created in step 3.

Step 5: Implement the proxy


Now, we will make sure the proxy also implements the
proxy interface:
1. // Proxy
2. class ImageProxy extends ImageInterface {
3. constructor() {
4. super();
5. [Link] = new ImageLoader();
6. }
7.
8. loadImage(url) {
9. // Proxy logic for lazy-loading
10. [Link](`ImageProxy: Request received for
image from ${url}`);
11. // Additional proxy logic for lazy-loading
12. [Link](url);
13. }
14. }
In the code example above, We have ensured that the proxy
class ImageProxy also extends the ImageInterface.
Additionally, We have included a super method in the
constructor to guarantee the inheritance of all properties
from the proxy interface.

Step 6: Utilize the proxy in client code


Now that all components are in place, let us proceed to
implement the proxy code to manage the lazy-loading of
images.
1. // Client code
2. const imageLoader = new ImageProxy();
3. [Link]("[Link]");
For the final step, we have now ensured that we use the
ImageProxy instead of the ImageLoader to load our
images. If we run this code, this is the result that we will
see in the browser console:
1. ImageProxy: Request received for image from
[Link]
2. ImageLoader: Loading image from [Link]
As evident from the above outcome, the image proxy is
consistently invoked first, subsequently initiating the
request for our image loader to load the image.
This behaviour demonstrates the essence of the Proxy
Design Pattern, where the proxy controls access to the real
object and can add additional functionality. The lazy-
loading logic can be implemented in the proxy to optimize
the loading process based on specific requirements.

Applications
The Proxy Pattern in JavaScript can be useful in various
scenarios to provide control, optimization, and additional
functionalities. Here are several instances where the Proxy
Pattern can be beneficial:

Lazy loading
As we have already seen in the example, proxy Design
Pattern can be used to implement lazy loading for
resources such as images, scripts, or data. The proxy can
delay the actual loading of resources until they are
explicitly requested, improving initial page load times.

Access control
We can employ a proxy to control access to sensitive
operations or resources. For example, we can use a
protection proxy to restrict access to certain methods or
properties based on user roles or permissions..

Caching
We can implement a proxy to cache the results of expensive
operations. The proxy can store the results and return them
if the same operation is requested again, reducing the need
for redundant computations.

Validation
We can use a proxy to validate input parameters or data
before it reaches the actual object. This ensures that only
valid data is processed by the real object, preventing
potential issues.

Remote proxy (AJAX requests)


We can use a proxy to manage and optimize remote calls,
such as AJAX requests. The proxy can handle tasks like
authentication, caching, or batching requests before
forwarding them to the actual service.

Pros and cons


There are several advantages of using the Proxy Design
Pattern in JavaScript. A few of them are as follows:
• We can control the service object and manage its
lifecycle without the clients knowing about it and in
scenarios, where the lifecycle doesn’t matter to the
client.
• The best part about having a proxy is that the service
object is not required to be available or ready. The
proxy can take all the requests and wait until the
service object is ready for implementation.
• Finally, it follows the Open/Closed Principle, where we
can add new proxies without changing the service
object or client.
The few disadvantages associated with the Proxy Design
Pattern are as follows:
• Introduction of new classes could potentially
complicate the code, if not handled or envisioned
correctly.
• Moreover, the proxy merely serves as a waiting room
until the actual service object can respond. So, there is
always a chance that the service might get delayed.
Pattern selection tips
In this chapter, we explored seven types of Structural
Design Patterns along with their diverse use-cases and
scenarios. When confronted with a real-world problem,
determining the most suitable pattern might initially seem
perplexing. Therefore, this section will serve as a guide to
help us choose the right pattern for our specific use-case.

Adapter Pattern
As we know, Adapter Pattern allows objects with
incompatible interfaces to collaborate. With that in view,
here are certain scenarios where this Design Pattern will
be beneficial to use:
• When integrating a new component with an existing
system.
• When the interface of an existing class needs to be
compatible with a client’s expectations.
• When working with legacy code that has an
incompatible interface.

Bridge Pattern
Bridge Pattern allows us to split a large class of closely
related classes into two independent hierarchies.
Therefore, Bridge Pattern can work wonders for scenarios
where:
• When we want to separate abstraction from
implementation.
• When changes in the implementation should not
affect the abstraction.
• When there is a need to extend both abstraction
and implementation independently
Composite Pattern
Composite Pattern permits us to compose objects into
discernible tree structures. This Design Pattern can help
us:
• When we want to treat both individual objects and
compositions of objects uniformly.
• When clients should be able to ignore the difference
between compositions of objects and individual objects.
• When we need to represent part-whole hierarchies.

Decorator Pattern
Decorator Design Pattern, as its name suggests, helps us to
attach new behaviours to objects by placing these objects
inside special wrappers. This can prove beneficial in the
following scenarios:
• When we want to add new functionalities to an object
dynamically without altering its structure.
• When there is a need for a flexible and reusable way to
extend behaviour.
• When we want to avoid subclassing to extend
behaviour.

Façade Pattern
Facade pattern provides a simplified interface to a library,
a framework, or any other complex set of classes.
Therefore, it can be used extensively for the following
scenarios:
• When we need to provide a simplified interface to a
complex subsystem.
• When client code needs to interact with a subsystem,
but we want to shield it from the complexities of that
subsystem.
• When we want to decouple the client code from the
implementation details of a subsystem.

Flyweight Pattern
Flyweight Pattern helps us fit more objects into the
available amount of RAM by sharing common parts of the
state between multiple objects. Its utility can be beneficial
in the following cases:
• When there are a large number of similar objects that
can be shared to reduce memory usage.
• When the application uses a large number of objects
that have similar properties.
• When the overhead of creating and maintaining a large
number of similar objects is significant.

Proxy Pattern
Proxy Pattern helps us by providing a substitute or a
placeholder for another object. This can be useful in cases
where:
• When we want to control access to an object.
• When we need to add functionality before or after the
actual processing of requests.
• When we want to implement lazy loading of an
expensive object.

Conclusion
In conclusion, this chapter provided an in-depth exploration
of Structural Design Patterns, which play a crucial role in
organizing code for the development of flexible and
scalable systems. Throughout the chapter, we delved into
seven distinct types of Structural Design Patterns. We
started with the Adapter Design Pattern, beneficial for
integrating newer code with legacy systems. The Bridge
Design Pattern was next, creating a bridge to separate the
abstraction and implementation aspects of complex
problems. Following that, the Composite Design Pattern
emerged as a solution for tree-based structures with both
leaf and composite elements. The Decorator Pattern was
discussed, offering a way to add new functionalities to
existing objects. Façade Pattern simplifies the handling of
various integrations by creating a unified interface.
Flyweight Pattern optimizes memory usage by sharing
common components. Lastly, the Proxy Pattern enables the
creation of a surrogate that takes control before accessing
the original object, allowing for the addition of
functionalities or behaviours.
A common thread among all these patterns is that
Structural Design Patterns provide a systematic approach
to addressing code complexity through effective code
structuring and organization. When applied appropriately,
these patterns serve as powerful tools for constructing
systems that are not only scalable but also reliable. In the
ever-evolving landscape of software development, the
understanding and mastery of Structural Design Patterns
are indispensable. As we move forward, armed with the
insights gained from this chapter, it is imperative for
developers to leverage these patterns judiciously, adapting
them to the unique requirements of each project. By doing
so, we contribute to the creation of robust, flexible, and
sustainable software architectures that stand the test of
time.

Points to remember
• Structural Design Patterns can be likened to
assembling individual objects like LEGO pieces. Just as
arranging and structuring these pieces can result in
masterpieces, appropriately organizing and structuring
objects can lead to the development of robust code that
stands the test of time.
• The Adapter Design Pattern involves creating an
interface capable of accommodating newer methods of
implementation. It provides a suitable adapter or
modification that serves as a bridge between the new
and different method and the traditional legacy
approach of a code.
• The Bridge Design Pattern advocates separating the
implementation part of the code from the abstraction
part. This separation allows encapsulating the
implementation code, facilitating the creation of cross-
platform applications.
• The Composite Design Pattern is well-suited for code
with a hierarchical or tree structure. It effectively
addresses complexities arising from components that
can have children, accommodating both leaf nodes and
composite nodes in a structured manner.
• The Decorator Design Pattern enables the addition of
functionalities or behaviours to code at runtime without
modifying the original code base.
• The Façade Pattern acts as a literal façade, hiding the
complex implementation logic of integrating multiple
components. Instead, it provides a simplified interface
for the client code to interact without being aware of
the underlying complexities.
• The Flyweight Design Pattern is useful in reducing
memory usage by efficiently managing shared and non-
shared components, particularly in scenarios involving a
large number of objects.
• The Proxy Design Pattern creates a surrogate that can
access the actual object and add behaviours or
functionalities to it before the actual object can be
accessed.

Exercises
1. Which Structural Design Pattern can effectively
handle the scenario of integrating multiple APIs
into a common codebase?
a. Adapter
b. Proxy
c. Decorator
d. Façade
2. Which Structural Design Pattern is described by
the following statement – “splitting large classes
or closely related classes into two parts –
abstraction and its implementation, allowing them
to be developed independently of each other”?
a. Composite
b. Bridge
c. Adapter
d. Singleton
3. Which of the following applications define the
correct use-case for a Flyweight Design Pattern?
a. Lazy loading
b. Access control
c. Data validation
d. Integration to legacy code.

Answers
1. d
2. b
3. a
CHAPTER 4
Behavioral Design Patterns

Introduction
Behavioral Design Patterns in software development are
patterns exclusively addressing communication between
objects. They focus on defining the responsibilities of
objects and how communication occurs among them. To
illustrate these patterns with a real-life analogy, let us liken
our code to a bustling city intersection, with objects
representing pedestrians or vehicles navigating through the
city. Now, envision the scenario in a real metropolitan city
such as New York, London, or Mumbai without traffic
signals or traffic police officials — utter chaos would ensue.
One could anticipate vehicles colliding and traffic
congestion reaching unprecedented levels. Much like traffic
signals maintaining order and facilitating proper vehicle
navigation, Behavioral Design Patterns serve a similar role
for objects in our code.

Structure
This chapter will cover the following topics:
• Introduction to Behavioral Design Patterns
• Chain of Responsibility Pattern
• Command Pattern
• Iterator Pattern
• Mediator Pattern
• Memento Pattern
• Observer Pattern
• Strategy Pattern

Objectives
Upon completing this chapter, we will gain a deeper
understanding of how responsibilities are distributed among
objects. We will explore how a particular behavior or
interaction can be accomplished by establishing effective
communication channels between objects. Grasping these
two concepts positions you to write more adaptable code.
Like Creational and Structural Design Patterns, the
integration of these patterns into your code will render it
modular, scalable, and resilient against future
modifications.

Introduction to Behavioral Design Patterns


At the beginning of this chapter, we discussed a real-life
scenario: the chaos that ensues in a metropolitan area
without the presence of traffic signals. This situation could
lead to utter disorder. The presence and effective
functioning of traffic signals, however, can restore order to
the chaos. We drew a parallel between this and Behavioral
Design Patterns.
Before plunging into a detailed understanding of what
Behavioral Design Patterns are (traffic signals), it is more
logical to first comprehend the problems these patterns
address (like traffic and chaos). Let us begin by examining
some commonly occurring issues that JavaScript developers
encounter while coding:

Problem statement #1
Let us look at this example depicting a shopping cart
pricing:
1. // Problematic Code without Strategy Pattern
2.
3. // Shopping Cart class
4. class ShoppingCart {
5. constructor() {
6. [Link] = [];
7. }
8.
9. // Method to calculate total price
10. calculateTotalPrice() {
11. let totalPrice = 0;
12.
13. [Link](item => {
14. // Problem: Switch statement for different pricing
strategies
15. switch ([Link]) {
16. case 'book':
17. totalPrice += [Link] * 0.9; // 10% discount
on books
18. break;
19. case 'mobile':
20. totalPrice += [Link] * 1.2; //20% markup on
mobiles
21. break;
22. // More cases for other types...
23. }
24. });
25.
26. return totalPrice;
27. }
28.
29. // Method to add items to the cart
30. addItem(item) {
31. [Link](item);
32. }
33. }
34.
35. // Example usage
36. const cart = new ShoppingCart();
37. [Link]({ type: 'book', price: 30 });
38. [Link]({ type: 'mobile', price: 50 });
39.
40. [Link](`Total Price:
$${[Link]()}`);

Code issue
Although the code may seem straightforward with a base
class ShoppingCart managing both item addition and
pricing, numerous issues arise. Firstly, the
calculateTotalPrice method employs a switch statement to
implement various pricing strategies based on the item
type. This complexity hinders extending the pricing strategy
without altering the existing code. Furthermore, it tightly
couples the pricing logic to the class, diminishing its
flexibility.

Problem statement #2
For our second problem statement, we will consider the
example of a logging system:
1. // Problematic Code without Chain of Responsibility
Pattern
2.
3. // Logger class
4. class Logger {
5. log(message, level) {
6. // Problem: Conditional statements for different log
levels
7. if (level === 'info') {
8. [Link](`[INFO] ${message}`);
9. } else if (level === 'warning') {
10. [Link](`[WARNING] ${message}`);
11. } else if (level === 'error') {
12. [Link](`[ERROR] ${message}`);
13. }
14. // More conditions for other log levels...
15. }
16. }
17.
18. // Example usage
19. const logger = new Logger();
20. [Link]('System is running smoothly.', 'info');
21. [Link]('Warning: Low disk space.', 'warning');

Code issue
In this code, a Logger class is present, logging messages to
the console based on level logic. However, it relies on
conditional logic, assigning the class the dual responsibility
of both printing and determining the message to be printed.
This setup complicates the addition or modification of log
levels without altering the existing code, resulting in a lack
of flexibility and extensibility.
In the two examples above, we observed two scenarios, or
rather, two types of problems that a developer might
unwittingly introduce into the codebase. Similarly, we can
encounter several other issues in our code, such as:
• Loose coupling: Tightly coupled objects in the code
make it difficult to modify one without affecting the
other.
• Flexibility and extensibility: Modifying or extending
the functionality in a system can be challenging without
introducing breaking changes.
• Dynamic behaviour: Managing various behaviours
through diverse logic can become intricate when dealing
with numerous participants.
• Responsibility chain: Multiple objects may handle a
request, and the appropriate handler needs to be
determined dynamically.

Behavioral Design Patterns


In essence, we have witnessed several instances where
improper communication or delegation of objects can result
in inefficient and inflexible coding, often culminating in
application crashes. This is where Behavioural Design
Patterns come into the picture.
Behavioral patterns address the challenge of defining
effective communication and collaboration between objects
in a software system. These patterns focus on defining how
objects interact, communicate, and collaborate with each
other. The solutions provided by these patterns can help
write flexible code that will have zero to minimal impact on
the original code. For instance, the Strategy Pattern among
Behavioral Design Patterns can address the problem
observed in code example #1, while the Chain of
Responsibility (CoR) can resolve the issues encountered in
code example #2. So, let us proceed to deepen our
understanding and explore the various types of Behavioral
Design Patterns.
Chain of Responsibility Pattern
The CoR Design Pattern, (alternatively also known as Chain
of Command) is a Behavioral Design Pattern where a
request is passed through a chain of handlers, where every
handler decides whether to process the request or pass it on
to the next handler in the chain.

Problem scenario
To help understand the problems that can be solved using
the CoR pattern, let us consider the example of an expense
approval workflow in a company.
Imagine a small and midsize enterprise (SME) company
experiencing steady growth. In such companies, there exists
a process for approving expenses incurred by employees.
These expenses could range from purchasing software
subscriptions and team outing meals to travel costs accrued
during a client presentation. Varied levels of approval are
necessary, depending on the expense amount and type.
Refer to the following figure showcasing a typical overflow
of expense approval:

Figure 4.1: A typical overflow of expense approval


The company might encounter several challenges when
attempting to develop software from scratch to address the
critical issue of expense approval. For example, the
application could evolve into a monolithic architecture with
tight coupling, meaning that one part of the code heavily
depends on another. This dependency makes it challenging
to modify one area without impacting others, reducing
flexibility and maintainability.
All conditional logic for handling various expenses might be
consolidated into a centralized module, requiring
modifications to the original code for any new change or
adjustment. This approach significantly impacts scalability
as the company expands with the addition of new approvals
or categories. Without proper separation of concerns —
an architectural principle that emphasizes dividing a system
into distinct features that overlap as little as possible — it
could further complicate error debugging amid a mix of
organizational hierarchy logic, audit trail, and expense
approval.
Additionally, lacking delegation — a design principle
where one object hands off responsibility for a task to
another — can lead to inefficiencies as responsibilities
remain concentrated in a single module. In summary, the
system may become inefficient, leading to delays in
processing payments, causing dissatisfaction among
employees, and placing a substantial burden on the financial
teams.
By adopting the Chain of Responsibility Pattern, these
potential problems can be mitigated. This pattern promotes
a more modular, scalable, and maintainable design by
ensuring that responsibility is passed through a chain of
handlers, each addressing specific concerns. This allows the
system to efficiently handle changes in the organizational
structure over time while improving its flexibility and
maintainability.
Implementation
The implementation of the CoR Design Pattern can be
divided into five steps:

Step 1: Define a handler interface


For the very first step, you need to create a base handler
class or a base interface with a handler method. This
method could be responsible for handling a request or
passing the request to the next handler in the chain.

Step 2: Create concrete handlers


Implement concrete handler classes that extend the handler
interface that we created in the previous step. Each
concrete handler should have a specific role in the
resolution process.

Step 3: Implement the handle method


Now, in each concrete handler, implement the method to
handle the request. At this stage, the decision must be made
on whether the handler can resolve the request or if it
needs to escalate the ticket to the next level. If the handler
is unable to address the request, it should invoke the handle
method of the next handler in line.

Step 4: Set up the chain


In the client code, instantiate instances of each handler and
establish the chain by linking them together using a method
that could be named setNextHandler. This method should
enable dynamic setting of the next handler in the chain.

Step 5: Client code


In the final step, we start the entire process with the user
submitting the request. We pass the request to the first
handler in the chain. The request will traverse the chain
until it is resolved or escalated to the highest level.

Code example
In this section, we will utilize the example of a help desk
ticket resolution system and implement the five-step process
we saw in the preceding section. In this help desk system,
users can submit tickets for various issues. Each ticket
needs to undergo a series of handlers to ascertain the
appropriate resolution. These handlers may encompass level
1 support, level 2 support, and ultimately, escalation to a
manager.

Step 1: Define a handler interface


The first thing we need to do is create a handler interface.
So, let us create that:
1. // Handler Interface
2. class Handler {
3. constructor() {
4. [Link] = null;
5. }
6.
7. setNextHandler(handler) {
8. [Link] = handler;
9. }
10.
11. handleTicket(ticket) {
12. // Handle the ticket or pass it to the next handler
13. if ([Link]) {
14. [Link](ticket);
15. } else {
16. [Link]("Ticket not resolved by any handler.");
17. }
18. }
19. }
In the above example, we have established a class named
Handler. In the constructor for this class, a property
named nextHandler is created with a default value of null.
Two methods have been added — setNextHandler, which
accepts a handler as an argument and sets it to the
nextHandler property, and handleTicket, which takes a
ticket as a parameter. Inside the handleTicket method, we
implement the handler logic where the ticket is either
processed or passed to the next handler.

Step 2: Create concrete handlers


For our second step, we need to create concrete handlers
classes that extend our Handler interface:
1. // Concrete Handlers
2. class Level1SupportHandler extends Handler {
3. handleTicket(ticket) {
4. }
5. }
6.
7. class Level2SupportHandler extends Handler {
8. handleTicket(ticket) {
9. }
10. }
11.
12. class ManagerHandler extends Handler {
13. handleTicket(ticket) {
14. }
15. }
In this step, we have generated three classes representing
the three levels of request handlers —
Level1SupportHandler, Level2SupportHandler, and a
ManagerHandler. Each of these classes extends the
Handler interface. As evident in the provided code, each
concrete handler inherits and overrides the base interface’s
handleTicket method.

Step 3: Implement the handle method


In this step, we fill the handleTicket method that we have
used in every concrete handler with the required code for
processing the request or passing it ahead:
1. // Concrete Handlers
2. class Level1SupportHandler extends Handler {
3. handleTicket(ticket) {
4. if ([Link] === 1) {
5. [Link]("Level 1 Support handled the ticket.");
6. } else {
7. [Link](ticket);
8. }
9. }
10. }
11.
12. class Level2SupportHandler extends Handler {
13. handleTicket(ticket) {
14. if ([Link] === 2) {
15. [Link]("Level 2 Support handled the ticket.");
16. } else {
17. [Link](ticket);
18. }
19. }
20. }
21.
22. class ManagerHandler extends Handler {
23. handleTicket(ticket) {
24. if ([Link] > 2) {
25. [Link]("Manager handled the escalated
ticket.");
26. } else {
27. [Link](ticket);
28. }
29. }
30. }
In this code, processing logic has been incorporated into
each handleTicket method. The method accepts the ticket
parameter, and within each class, relevant logic is
implemented to handle the ticket. For instance, in the
handleTicket method of the Level1SupportHandler class,
we check if the level property inside the ticket parameter is
1. If the condition is true, we print the statement; otherwise,
we pass it to the base class’s handleTicket method with
the ticket as an argument.

Step 4: Set up the chain


Our next step involves creating instances of each request
handler (concrete handler) and establishing the chain by
configuring the next handler using the setNextHandler
method:
1. // Client Code - Set Up the Chain
2. const level1Handler = new Level1SupportHandler();
3. const level2Handler = new Level2SupportHandler();
4. const managerHandler = new ManagerHandler();
5.
6. [Link](level2Handler);
7. [Link](managerHandler);
With the provided code, we have connected the chain
responsible for handling requests. Instances of each
concrete handler were created, and the setNextHandler
method of level1Handler and level2Handler was invoked
to set the next level of request handler as level2Handler
and managerHandler, respectively. It is crucial to note
that the managerHandler is the final link in the chain, and
there will be no handlers above it.
So, in this case (refer the code above and the code example
shown in step 1), the ticket handling initiates from the
lowest handler, and if the ticket level does not match, it
cascades up the chain to the base class Handler using the
super method. The base class, in turn, checks if the
nextHandler has been set (for instance, we have set the
next handler on the line number 6 in the code above). If set,
the ticket is passed on to that handler; otherwise, the
message in the else block is printed. This cascading
mechanism allows for a systematic and hierarchical
approach to ticket resolution.

Step 5: Client code


In the final step, we commence the process by assigning a
ticket to the lowest handler and specifying a level in the
ticket. Consider the level as a category of expense in real
life. For instance, a basic transportation expense may be
approved by the manager (a level above). However, a
substantial expense made by an employee that necessitates
approval from the Chief Executive Officer (CXO) team of
the company represents the topmost level:
1. // Client Code - Submit Ticket
2. const userTicket = { level: 2 };
3. [Link](userTicket);
In this code, a ticket object is created with the level set to
2. As mentioned earlier, we assign this ticket to the lowest
handler, level1Handler, by invoking its handleTicket
method. Now, level1Handler receives the ticket and, inside
its handleTicket method, checks if the level is 1 (refer to
the code example in Step 3). Since the value of the level
does not match, it is forwarded to the else block where the
ticket is then passed to the handleTicket method of the
base Handler class using the super method.
Within the base Handler class’s handleTicket method, the
code first checks if the level1Handler has set the
nextHandler property value. In our case, it has been set in
the code shown on line number 6 in Step 4 as
level2Handler. Consequently, it moves inside the if
condition and invokes the handleTicket method of
level2Handler. Inside the handleTicket method of
level2Handler, it checks the ticket’s level value, which
matches, resulting in the corresponding output being
printed on the console:
1. Level 2 Support handled the ticket.
In this way, the CoR pattern provided a clean and
maintainable solution for the help desk ticket resolution
system by structuring the code in a way that allows easy
extension, configuration, and adaptation to changes in the
support hierarchy. With this code, we created a modular
code where every handler is responsible for a specific level
of support. The separation of concerns ensures that each
handler focuses on its specific task, making the codebase
more readable and maintainable. We dynamically
configured the chain of handlers in the client code. This
flexibility enables easy modification of the resolution
workflow without altering the client code. The pattern
helped eliminate complex conditional statements that would
have been necessary in a non-pattern approach. Each
handler decides whether it can handle the ticket or passes it
to the next handler, promoting a more straightforward and
cleaner code structure. This makes our code scalable,
adaptable, and it massively helps in the audit trail of the
ticket logging process.

Applications
We have just observed the substantial benefits that the CoR
Design Pattern provides over traditional conditional logic.
Now, let us explore a list of scenarios where this pattern
proves advantageous:

Middleware processing in web servers


In web development, CoR can be used for processing
middleware functions in web servers. Each middleware
component can handle specific tasks, such as
authentication, logging, or error handling, and pass control
to the next middleware in the chain.

Form validation
In form validation scenarios, where different validation
rules need to be applied to form inputs, CoR can be used.
Each validation rule can be a handler in the chain, checking
a specific aspect of the input data.

Request handling in [Link] middleware


In [Link] development using [Link], CoR can be
applied to handle HTTP requests through middleware
functions. Each middleware component can handle specific
tasks, and the order in which they are added to the chain
defines the request processing flow.

Chatbot response processing


In chatbot development, CoR can be used to process user
queries and generate responses. Each handler in the chain
may handle specific types of queries or contexts, providing a
modular and extensible approach to chatbot interaction.
Workflow engines
CoR can be employed in workflow engines where a series of
processing steps need to be executed. Each step in the
workflow is represented by a handler, and the chain ensures
a sequential and customizable execution of the workflow.

Pros and cons


Here are some of the advantages of using CoR pattern:
• You can control the order of request handling.
• It follows the SRP, where it separates the classes that
invoke operations from classes that perform operations.
• It also follows the Open/Closed Principle, where you
can introduce new handlers to the code without
breaking the existing client code.
One major disadvantage is that some requests may end up
being unhandled if incorrect parameters are provided and
the code is not designed to handle edge cases.

Command Pattern
The Command Design Pattern is a Behavioral Design
Pattern that turns a request into a standalone object that
contains all the information about the request. Often used in
decoupling the sender and receiver objects, this
transformation can pass the request around, store it, and
delay its execution for a later time.

Problem scenario
Let us delve into a real-life example of a home automation
system to grasp the type of issues a command Design
Pattern can address. In this scenario, imagine we have
recently installed a home automation system that manages
various devices such as the thermostat, air-conditioner,
lights, and smart speakers with a simple click of a button.
Now, we aim to enhance the system to handle intricate
routines. For instance, uttering the words Good Morning
to the system should trigger a sequence of complex events
like turning on specific lights, powering down the air-
conditioner, and playing our favourite morning playlist.
Initially, our basic remote control features a straightforward
interface and control logic where each button corresponds
to a specific device. However, to integrate our complex
Good Morning logic, we would need to hardcode the
sequence of commands in the remote control. This approach
makes it inflexible and challenging to modify or extend,
especially when adding more complex commands to the
remote control in the future.
By introducing the command Design Pattern to the
aforementioned problem, we can address it by decoupling
the sender (remote control) from the receivers (smart home
devices). In the following sections, we will delve into a
detailed exploration of implementing this pattern in our
code and examine the various applications where it can be
advantageous.

Implementation
As mentioned earlier, we will be decoupling the senders
from the receivers as a part of the command Design Pattern
implementation. We can divide this implementation into the
following steps:

Step 1: Define the command interface


We commence by creating a command interface. This
interface, or base class, declares a method ideally named
execute, which will be implemented by concrete command
classes. This initial step establishes a common ground for all
commands, ensuring they have an execute method.
Step 2: Implement concrete command classes
Here, we create specific commands that extend the
command interface. Each concrete command class
represents a specific operation or action.

Step 3: Implement receiver classes


In this step, we create receiver classes that represent the
devices being controlled. Receivers are responsible for
performing the actual actions.

Step 4: Implement invoker classes


In this step, we define the invoker class. This class is
responsible for managing and executing commands. It has
methods to add commands and methods to execute them.

Step 5: Client code


Here, we create instances of receivers, concrete commands,
and the invoker. Commands are added to the invoker, and
when the invoker executes the commands, the
corresponding actions are performed.

Code example
We will use the example of the smart home system
discussed earlier in the Problem scenario section for the
Command Design Pattern and attempt to implement it in the
code below, following the implementation steps outlined in
the preceding section. To recap the problem statement, we
aim to efficiently control smart home devices. The goal is to
design it in a way that newer and more complex functions
can be added to the remote control used for controlling
these devices without impacting the original code.

Step 1: Define the Command interface


The first step involves creating a Command interface
housing a method for executing a command. This interface
will be implemented by concrete command classes. Let us
see it in action:
1. // Command interface
2. class Command {
3. execute() {}
4. }
In the provided code example, a base class Command has
been created with a singular empty method, execute. This
class will function as the foundation for implementing
concrete commands that the remote control can execute.

Step 2: Implement concrete command classes


In this step, we create the specific commands that extend
the Command interface. Each of these commands or
operations will be represented by individual concrete
classes:
1. // ConcreteCommand for Lights
2. class LightsOnCommand extends Command {
3. constructor(light) {
4. super();
5. [Link] = light;
6. }
7.
8. execute() {
9. [Link]();
10. }
11. }
12.
13. // ConcreteCommand for Thermostat
14. class SetTemperatureCommand extends Command {
15. constructor(thermostat, temperature) {
16. super();
17. [Link] = thermostat;
18. [Link] = temperature;
19. }
20.
21. execute() {
22. [Link]([Link]);
23. }
24. }
In the provided code example, two concrete classes,
LightsOnCommand and SetTemperatureCommand,
have been created to represent two actions — turning on
lights and setting the temperature. As observed in the code
above, both classes extend the base class Command.
However, they take a different approach to handling their
commands. For instance, in the LightsOnCommand class,
the constructor accepts light as a parameter, while in the
SetTemperatureCommand class, it takes thermostat and
temperature as parameters. Both constructors also invoke
the super method to fetch and inherit properties from the
base class.
Similarly, the execute method of the LightsOnCommand
class invokes the turnOn method of the light property
obtained from the received parameters, while the
SetTemperatureCommand class invokes the
setTemperature method of the received thermostat
property and passes the received temperature value to it.

Step 3: Implement receiver classes


In this step, we create the receiver classes that represent
the devices that we need to control. These receivers are
responsible for performing the actual actions. Let us go
ahead and define these classes:
1. // Receiver for Lights
2. class Light {
3. turnOn() {
4. [Link]("Lights are on.");
5. }
6. }
7.
8. // Receiver for Thermostat
9. class Thermostat {
10. setTemperature(temperature) {
11. [Link](`Thermostat temperature set to
${temperature} degrees.`);
12. }
13. }
We have already established that our remote control will
have two commands to execute — toggling the lights and
setting the thermostat temperature. In the provided code,
we have created the devices on which these operations will
be performed. Now, there is a Light class featuring a
singular function we have encountered in step 2 — turnOn.
For the sake of this example, we have added a command to
print the message Lights are on upon the execution of this
method.
Next, we define the Thermostat class with the
setTemperature function, which accepts a temperature
parameter. Upon invocation, it prints a message indicating
that the thermostat is set to the provided temperature.
These messages are, of course, merely illustrative and will
be replaced by the logic for performing the respective
operations.

Step 4: Implement invoker classes


So far, we have defined and created the commands and the
devices which will be impacted by those commands. Now,
let us create the final piece of the puzzle and build the
remote control:
1. // Invoker
2. class RemoteControl {
3. constructor() {
4. [Link] = [];
5. }
6.
7. addCommand(command) {
8. [Link](command);
9. }
10.
11. executeCommands() {
12. [Link](command =>
[Link]());
13. }
14. }
In the provided code, we have created the class
RemoteControl, which will act as the invoker. In the
constructor, an empty array of commands property is
created to contain all the commands. Additionally, the
addCommand method is defined, which accepts a
command and adds it to the commands property. Finally, a
method, executeCommands, is also added. This method
traverses through each command and invokes their execute
method.

Step 5: Client code


In the final step of our implementation, we create instances
of everything we have defined so far and trigger the invoker
to execute the commands:
1. // client code
2. const livingRoomLights = new Light();
3. const livingRoomThermostat = new Thermostat();
4.
5. const lightsCommand = new LightsOnCommand(livingR
oomLights);
6. const thermostatCommand = new SetTemperatureCom
mand(livingRoomThermostat, 22);
7.
8. const remoteControl = new RemoteControl();
9. [Link](lightsCommand);
10. [Link](thermostatCommand);
11.
12. // execute the routine
13. [Link]();
In the provided code, instances of the Light class and
the Thermostat class have been created. These
instances are then passed to the concrete command
classes, LightsOnCommand and
SetTemperatureCommand, to create the commands
lightsCommand and thermostatCommand. Following
this, an instance of the remote control is created, and
the respective commands are added. The final step
involves calling the executeCommands method to
execute every command from the remote control. The
output is:
1. Lights are on.
2. Thermostat temperature set to 22 degrees.

Applications
We have noticed that the Command Design Pattern works
wonders in scenarios where we want to decouple the sender
of a request from the receiver, and we need flexibility,
extensibility, and support for features like undo/redo. Here
are some scenarios in JavaScript where the Command
Design Pattern would be beneficial:

GUI applications
In GUI applications, we can use the Command Pattern to
handle user interactions. For example, a button click can be
represented as a command, allowing us to encapsulate and
execute different actions.

Smart home automation


As mentioned earlier, in a smart home system, the
Command Pattern can be used to control different devices
like lights, thermostats, and speakers. This provides a
modular and extensible approach to handle various
commands and routines.

Undo/redo functionality
When you need to implement undo and redo functionality,
the Command Pattern shines. Each executed command can
be stored, and undoing or redoing involves reversing or re-
executing those commands.

Logging and auditing


Commands can be logged for auditing purposes. This is
useful for tracking user actions or system operations over
time.

Workflow management
When dealing with complex workflows or business
processes, the Command Pattern allows you to encapsulate
each step of the workflow as a command, making it easier to
manage and modify the workflow.
In these scenarios, the Command Pattern promotes
flexibility, maintainability, and separation of concerns,
making your codebase more scalable and adaptable to
changing requirements.

Pros and cons


Here are some of the benefits of implementing the
Command Pattern in the code:
• The Command Pattern adheres to the SRP, enabling the
decoupling of classes that invoke operations from the
classes that perform these operations.
• It follows the Open/Closed Principle. It allows us to
introduce new commands into the system without
breaking existing code.
• We can implement the unique feature of undoing or
redoing a command, as it allows storage of commands.
• It allows us to defer the execution of commands and
acts as a perfect middleware.
• It allows implementing complex set of commands by
disintegrating it into smaller and simpler commands.
A major disadvantage of implementing Command Design
Pattern is that since it acts as a middle layer, it might end
up complicating the code.

Iterator Pattern
The Iterator Design Pattern is a Behavioral Design Pattern
that provides a way of accessing elements of a collection
sequentially without exposing the underlying representation
of the collection. In JavaScript, this pattern is often used to
iterate over elements in an object or array.

Problem scenario
Let us understand the problems that can be solved by an
Iterator Pattern with the example of navigating a tree
structure.
Let us contemplate a scenario involving a tree data
structure representing a file system, where we aim to
execute some operation on every file or folder. It is crucial
to note that each folder may contain nested folders, which
can further include additional folders or files. Navigating
the tree using recursive or nested code would make the
entire codebase intricate and less modular.
1. const fileSystem = {
2. name: 'Root',
3. type: 'directory',
4. children: [
5. {
6. name: 'Folder1',
7. type: 'directory',
8. children: [
9. { name: '[Link]', type: 'file' },
10. { name: '[Link]', type: 'file' },
11. ],
12. },
13. {
14. name: 'Folder2',
15. type: 'directory',
16. children: [
17. { name: '[Link]', type: 'file' },
18. { name: 'Folder3', type: 'directory', children: [{
name: '[Link]', type: 'file' }] },
19. ],
20. },
21. ],
22. };
23.
24. // Without Iterator
25. function processFileSystem(node) {
26. if ([Link] === 'file') {
27. [Link]([Link]);
28. // Perform some operation on the file
29. } else if ([Link] === 'directory') {
30. for (const child of [Link]) {
31. processFileSystem(child);
32. }
33. }
34. }
35.
36. processFileSystem(fileSystem);
As we can see from the code example above, the code
becomes complex and harder to understand, making
maintenance and modification challenging. If the structure
of the tree changes, the traversal logic needs to be updated,
leading to potential errors.
What we require is a solution that is modular that separates
the iteration logic and the ability to traverse complex data
structure with ease. This is a scenario where Iterator Design
Pattern can come to the rescue.

Implementation
The iterator Design Pattern proves useful for segregating
the iteration logic from the logic required to traverse
complex data structures. Let us delineate its
implementation into the four steps outlined below:

Step 1: Define the iterator interface


The crux of our logic lies in the iterator. Hence, let s
commence with this aspect. In the initial step, we will create
an interface for the iterator. This interface should
encompass methods essential for the iterator. Typically,
there are two methods, usually named next() and
hasNext().

Step 2: Implement a concrete iterator


Now that we have established a base class for our iterator,
let us proceed to create a concrete iterator that extends our
base class iterator. This iterator will be tailored to the type
of collection we want to traverse. For instance, we could
develop separate iterators for arrays, maps, sets, strings, or
custom objects.

Step 3: Create an aggregate object


Next, we create an object that holds that collection and
provides a method to get an iterator for that collection.

Step 4: Use the iterator in client code


Now, we can use the aggregate object and its iterator to
traverse the collection without exposing its internal
structure.

Code example
We will employ a real-life analogy, such as a shopping list,
to elucidate the steps outlined in the previous step for
implementing the Iterator Pattern in JavaScript. The
concept is straightforward: we aim to traverse a shopping
list and determine whether there are more items left to buy.

Step 1: Define the iterator interface


As we have seen before, the very first step is to create the
base class for our iterator interface:
1. class ShoppingListTemplate {
2. nextItem() {}
3. hasMoreItems() {}
4. }
In this example, we have crafted a class named
ShoppingListTemplate to serve as the base class for
iterators. Additionally, two empty methods, nextItem and
hasMoreItems, have been incorporated into this class.
These methods will be inherited and overridden by our
concrete iterators.

Step 2: Implement a concrete iterator


Let us now generate a specific list that necessitates
traversal. In alignment with our shopping example, we will
create a groceries list, which will be of the array type:
1. class GroceryListIterator extends
ShoppingListTemplate {
2. constructor(items) {
3. super();
4. [Link] = items;
5. [Link] = 0;
6. }
7.
8. nextItem() {
9. if ([Link]()) {
10. return [Link][[Link]++];
11. }
12. return null;
13. }
14.
15. hasMoreItems() {
16. return [Link] < [Link];
17. }
18. }
In this example, we have constructed a class named
GroceryListIterator that extends our base class,
ShoppingListTemplate. In the constructor, we accept a
parameter, items, which is then added to the local property
items. Additionally, we have introduced another property,
currentIndex, which aids in navigating through our list.
This property is appropriately initialized with a value of 0,
representing the lowest position in
an array.
Furthermore, we have inherited the two methods from our
base class by invoking the super method within our
constructor. Within the hasMoreItems method, we
ascertain whether the current index is less than the total
length of the items array received during instance creation.
This check determines if there are more items to traverse or
if we have reached the end of the list.
Subsequently, the hasMoreItems method is utilized within
the nextItem method to verify the presence of additional
items and subsequently return the next item from the list. In
the event we have reached the end of the list, the method
will return null.

Step 3: Create an aggregate object


Our subsequent implementation step involves creating an
object to hold the list we need to traverse. For our shopping
example, we will establish the ShoppingCart class to
encapsulate that list:
1. class ShoppingCart {
2. constructor(lists) {
3. [Link] = lists;
4. }
5.
6. getListIterator(listIndex) {
7. const list = [Link][listIndex];
8. return new GroceryListIterator(list);
9. }
10. }
In this example, we have crafted a class named
ShoppingCart. Its constructor accepts a parameter, lists,
which represents the list of items we need to buy.
Additionally, we have included a method called
getListIterator that takes a parameter, listIndex,
denoting the index of a specific item in the list. We store
this value in the variable list and then pass it to an instance
of GroceryListIterator. With this step, we have fulfilled all
the prerequisites necessary for implementing the Iterator
Pattern in our JavaScript code.

Step 4: Use the iterator in client code


For the final step, we will implement the Iterator Design
Pattern in the client code. Let us go ahead and try this:
1. const shoppingCart = new ShoppingCart([
2. ['Apples', 'Bananas', 'Oranges'],
3. ['Milk', 'Eggs', 'Bread', 'Butter'],
4. ]);
5.
6. const listIterator = [Link](1); //
Accessing the second list
7.
8. while ([Link]()) {
9. const item = [Link]();
10. [Link](item);
11. // Purchase or perform some action on the item
12. }
In the real-life scenario, the shopper utilizes the shopping
list (iterator) to traverse through the items to buy without
knowing the exact contents of each list. We have attempted
to emulate this in the above example by initially creating a
nested array containing multiple lists of items we need to
buy. Subsequently, we establish a variable named
listIterator, where we invoke the getListIterator method
of the ShoppingCart instance and pass the index 1,
corresponding to the second list in our nested array.
For the final piece of our code, we employ a while loop that
invokes the hasMoreItems method, which is available to
the ShoppingCart class due to the instance created of the
GroceryListIterator within the function. This method
assists us in determining if there are any items left in the
list and prints them on the console, as demonstrated in the
code on line number 10 in the above example.
On running this code, we finally get the output:
1. Milk
2. Eggs
3. Bread
4. Butter
This analogy helps to understand the steps of implementing
the Iterator Pattern by relating it to the experience of
managing and navigating through shopping lists while
shopping.

Applications
The Iterator Design Pattern can be beneficial in various
scenarios in JavaScript, especially when dealing with
collections or sequences of elements. Here are some
scenarios where the Iterator Pattern can be particularly
useful:

Iterating over arrays


When we need to traverse the elements of an array without
exposing its internal representation or knowing its exact
length.

Traversing object properties


When we want to iterate over the properties of an object
without directly accessing its keys or values.

Navigating database results


When fetching data from a database and we want to iterate
over the result set one row at a time, the Iterator Pattern
can help us achieve that.

Parsing and processing streams


The Iterator Pattern is extremely useful for scenarios where
we are reading from streams of data (e.g., reading lines
from a file, parsing JSON streams), and we want to process
each element as it becomes available.
In all these scenarios, the Iterator Pattern helps to decouple
the iteration logic from the underlying data structure or
source, promoting code reusability, maintainability, and
abstraction. It also provides a consistent interface for
iterating over different types of collections or sequences.

Pros and cons


Here are some of the advantages that the Iterator Pattern
offers to coding in JavaScript:
• It follows the SRP. We can refine the client code by
extracting and separating the iteration logic into
separate classes.
• It follows the Open/Closed Principle. The Iterator
Pattern stresses on separating the iteration logic into
separate classes to make it generic in nature so that it
can traverse any kind of collection. It means we can
implement new types of collection and pass their logic to
the separate classes without impacting the original code.
• We can iterate over the same collection in parallel,
because each iterator object contains its own state.
• Thanks to the above step, we can delay an iteration and
continue it when required.
However, there are two major constraints or disadvantages
to this pattern:
• The Iterator Pattern proves immensely beneficial in
scenarios involving numerous or intricate iterations or
complex collections in your code. However, it might be
considered overkill for handling only simple collections.
• Utilizing an iterator may be less efficient compared to
directly iterating through elements of certain specialized
collections.

Mediator Pattern
The Mediator Design Pattern is a Behavioral Design Pattern
that literally lives up to its name. This pattern stresses on
promoting loose coupling by wrapping object interactions in
a mediator. This mediator serves as a middleman, ensuring
participating objects can communicate without directly
referencing each other. It is like a party host who ensures
everyone’s mingling without needing personal
introductions.

Problem scenario
To grasp the problem-solving prowess of the Mediator
Design Pattern, let us delve into the intricate operations of
an airport.
At an airport, several entities must collaborate seamlessly
for safe and efficient operations. These entities include the
outgoing and the incoming airplanes, the ground staff, the
maintenance crew and the air traffic control (ATC) tower.
Each entity has specific responsibilities, like managing
runways and handling emergencies. Failure to perform
these duties could lead to delays, bottlenecks, or even
collisions.
For example, imagine an airport without an ATC tower.
Without one, airplanes would need to coordinate directly for
take-offs and landing. Whether it is a small airport or a
major hub like those in Singapore, New York, or Mumbai
(that see at least 4 landings or take-offs in a minute),
managing communication would be a challenge. Factor in
unpredictable weather and other complications, and the risk
of collisions increases, potentially leading to tragic
outcomes.
In this scenario, the role of an ATC tower is paramount. It
serves as a crucial intermediary, facilitating interactions
between airplanes while providing guidance to ensure safe
operations, avoid collisions, and manage emergencies.
Airplanes communicate with the tower to request clearance
for take-offs and landings, report their positions, and
receive instructions for taxiing on the runway. Ground
vehicles coordinate with the tower to navigate safely around
the airport, and maintenance crews communicate with the
tower to schedule repairs and inspections without
disrupting ongoing flights.
Likewise, the Mediator Pattern streamlines communication
by introducing a middleman to prevent congestion among
interacting objects.

Implementation
The Mediator Design Pattern proves highly advantageous in
scenarios where we seek to simplify communication
between objects through the introduction of a mediator. Let
us explore how we can implement this logic using the
following steps:
Step 1: Define the mediator object
To kick off the implementation of the Mediator Pattern, our
first step is to create the mediator object itself. This
mediator will serve as the intermediary for our
communicating objects, keeping track of registered entities
and managing the distribution of messages among them.

Step 2: Create entity objects


Next, we proceed to create the entity objects —
representing the various entities that require interaction
with each other. These entities typically come equipped with
methods for sending and receiving messages via the
mediator.

Step 3: Register entities with the mediator


With our two participants identified, we will instantiate
entity objects and register them with the mediator.
Registration ensures that the mediator can keep tabs on all
entities involved in the communication process.

Step 4: Communication through the mediator


Now, the entities can communicate with each other through
the mediator without requiring direct references to each
other. The mediator takes charge of distributing messages
based on the entities’ registration, ensuring that messages
reach their intended recipients accurately.
By centralizing communication through the mediator,
entities become decoupled from each other, meaning they
do not need to know the specifics of other entities or
maintain direct references to them. This decoupling
enhances flexibility and scalability, as entities can be added,
removed, or modified without impacting the overall system
architecture.
Code example
We will utilize the example of the ATC and its operations, as
discussed in the Problem scenario section earlier for the
Mediator Design Pattern. Let us dive into the
implementation with a coding example:

Step 1: Define the mediator object


To begin our implementation, let us create the mediator
object. In our example of airport operations, this will be the
ATC tower. Let us proceed with its creation:
1. class ATCTower {
2. constructor() {
3. [Link] = [];
4. }
5.
6. addAirplane(airplane) {
7. [Link](airplane);
8. }
9.
10. sendMessage(message, sender) {
11. [Link](airplane => {
12. if (airplane !== sender) {
13. [Link](message);
14. }
15. });
16. }
17. }
The mediator object serves as the central controller, akin to
a real-life ATC Tower. In the defined class ATCTower, we
first initialize the property airplanes with an empty array in
the constructor. This property will be used to add entities
to the base mediator class, which, in our case, represents
airplanes. Next, we introduce two methods: addAirplane
and sendMessage. The addAirplane method is
straightforward — it registers airplanes with this mediator
class. Meanwhile, sendMessage acts as the communication
channel for entities (airplanes) to converse. Within
sendMessage, we iterate through each plane and, based on
the received parameters (message and sender), we locate
all airplanes that are not the senders and relay the message
to them through their native receiveMessage method,
which we will define in the next step.

Step 2: Create entity objects


Now, let us create the entities that require interaction with
each other. Staying true to our example, these will be the
airplanes. Let us proceed to create the Airplane class
before delving into its functionality:
1. class Airplane {
2. constructor(id, atcTower) {
3. [Link] = id;
4. [Link] = atcTower;
5. }
6.
7. sendMessage(message) {
8. [Link](message, this);
9. }
10.
11. receiveMessage(message) {
12. [Link](`Airplane ${[Link]} received:
${message}`);
13. }
14. }
In the provided example, we define our Airplane class to
create objects capable of interacting with each other. Within
the constructor, we accept two parameters upon
instantiation: id and atcTower, which are stored as local
properties with the same names. The id property uniquely
identifies each airplane, while atcTower holds a reference
to the ATCTower mediator object.
The class includes two essential methods: sendMessage
and receiveMessage. sendMessage facilitates sending a
message to the ATC Tower via the mediator, with the
message parameter representing the content to be sent. On
the other hand, receiveMessage enables the object to
accept messages from other airplanes via the mediator and
subsequently logs them to the console.

Step 3: Register entities with the mediator


Now, we connect our entities to the mediator. We will
instantiate our airplanes and the mediator, then register the
airplanes with the mediator:
1. // Create an instance of the ATC tower
2. const atcTower = new ATCTower();
3.
4. // Create airplane instances
5. const airplane1 = new Airplane('ABC123', atcTower);
6. const airplane2 = new Airplane('XYZ789', atcTower);
7. const airplane3 = new Airplane('DEF456', atcTower);
8.
9. // Register airplanes with the ATC tower
10. [Link](airplane1);
11. [Link](airplane2);
12. [Link](airplane3);
In this third step, we begin assembling the components.
First, we instantiate the ATC tower on the second line.
Then, we create three instances of airplanes, assigning each
a unique ID and passing them the atcTower instance along
with their respective IDs. Finally, we register all three
airplanes by invoking the addAirplane method of the ATC
Tower mediator class.

Step 4: Communication through the mediator


In the final step, we enable communication between the
airplanes through the ATC tower:
1. [Link]('Requesting permission to
land.');
2. [Link]('Roger, cleared for landing.');
3. [Link]('Reporting turbulence ahead,
advise caution.');
In the provided code example, each airplane is now sending
a message, invoking the sendMessage method of the base
mediator class through their individual entity objects. The
base mediator class ensures that all entities except the
sender receive the messages. Let us examine what will be
printed in the console once this code has been executed:
1. Airplane ABC123 received: Roger, cleared for landing.
2. Airplane XYZ789 received: Requesting permission to
land.
3. Airplane DEF456 received: Requesting permission to
land.
4. Airplane XYZ789 received: Reporting turbulence ahead,
advise caution.
5. Airplane DEF456 received: Roger, cleared for landing.
6. Airplane ABC123 received: Reporting turbulence ahead,
advise caution.
In this example, we have managed to use the mediator as a
middleman to pass across messages between objects
without them referencing each other and making the code
complicated.
Applications
The Mediator Design Pattern proves invaluable in scenarios
where objects require continuous interaction. Introducing a
mediator not only resolves interaction issues but also
enhances code organization. With this in mind, let us
explore several scenarios where the application of the
Mediator Design Pattern shines:

Chat applications
As demonstrated in the previous example with airplanes, the
Mediator Design Pattern thrives in scenarios requiring
interaction and communication between objects. Few
examples illustrate this as effectively as chat applications.
Here, the Mediator Pattern can adeptly manage
communication between users across various chat rooms or
channels, guaranteeing messages reach their intended
recipients.

Game development
In multiplayer online games, the Mediator Pattern can
handle communication between players, NPCs, and game
objects, coordinating actions and events in the game world.

Microservices communication
In a microservices architecture, various services (such as
cart, payment, and catalogue in an e-commerce platform)
reside within different containers, requiring seamless
connection to establish a stable environment. Here, the
Mediator Pattern proves invaluable, facilitating
communication between microservices. Acting as a central
hub, the Mediator efficiently routes messages and
coordinates interactions among services, contributing to the
system’s cohesion and reliability.
Real-time collaborative editing
In collaborative editing applications, such as Google Docs,
the Mediator Pattern can manage concurrent edits from
multiple users, ensuring consistency and synchronization of
document changes.
In these scenarios, the Mediator Pattern helps to decouple
components, manage complex interactions, and promote a
more modular and maintainable architecture.

Pros and cons


Here are a few advantages that the Mediator Pattern brings
to JavaScript coding:
• It adheres to the SRP, effectively separating
communication between different components. This not
only enhances maintainability but also aids
comprehension of the codebase.
• It adheres to the Open/Closed Principle, enabling the
addition of new mediators to the system without
necessitating modifications to the original codebase.
• It helps reduce tight coupling between different
components, allowing them to operate independently of
specific rules. This flexibility makes it easier to adapt
components without requiring extensive modifications
whenever changes are needed.
A major disadvantage of using the Mediator Pattern is that
if the mediator object is left unchecked, it can evolve into a
god object. A god object is an anti-pattern where a single
object becomes overly large, centralizing too much
functionality and taking on responsibilities that should be
distributed across the system. This centralization tightly
couples multiple components, reducing modularity and
making the system harder to maintain, understand, and test.
For example, if a mediator begins handling an excessive
number of interactions between components, it can become
a bottleneck, hindering scalability and introducing complex
dependencies. This undermines the pattern’s original
purpose of improving communication and decoupling
components.
To avoid this, the mediator can be split into smaller, more
specific mediators, each responsible for a distinct set of
interactions or domain logic. For instance, instead of a
single Mediator coordinating all operations, you can create
separate mediators for different modules, such as one for
user management and another for notifications.
Additionally, adhering to the SRP ensures that each
mediator has a well-defined and limited scope. Properly
documenting the responsibilities of each mediator can also
help maintain the pattern’s integrity and prevent it from
becoming a god object.

Memento Pattern
The Memento Design Pattern is a Behavioral pattern that
elegantly handles capturing and restoring an object’s
internal state while keeping its implementation details
hidden. In JavaScript, we wield this pattern with finesse,
employing objects and closures to securely stash and
retrieve an object’s state.

Problem scenario
Let us kick off our exploration of this pattern by delving into
a common problem scenario that resonates with many.
To understand the practical applications of the Memento
Design Pattern, let us take a peek into the world of a text
editor, like Microsoft Word. In such an app, we often hit the
save button to preserve a draft of our current text. This not
only secures our progress but also provides a safety net,
much like creating checkpoints in a game for potential
future returns.
Now, imagine a scenario where we have made numerous
edits to our document, and we realise that we need to
rewind to an earlier version. Without the Memento Pattern,
we would be stuck at manually tracking each change and its
corresponding state—a tedious and error-prone task, to say
the least.
Implementation
We can break down the step-by-step implementation of the
Memento Design Pattern into four stages, with three key
characters playing vital roles in this narrative:

Step 1: Create the originator class


Our exploration of implementing the Memento Design
Pattern begins with the creation of a class tasked with
instantiating an object to be saved and restored. This class
features methods for setting its state, saving its current
state to a memento, and restoring its state from a memento.

Step 2: Create the memento class


Next, we will craft the memento class. This class will store
the state of the originator and offer a method to retrieve the
saved state.

Step 3: Create the caretaker class


The caretaker class is responsible for keeping track of the
mementos. It provides methods to add mementos to a list
and retrieve mementos from the list.

Step 4: Client code


In the final step, we instantiate objects of the originator and
the caretaker classes. We utilize the originator to set its
state, save the state to a memento, and employ the
caretaker to restore the state from a memento.
By introducing these three key characters, we can craft an
efficient code that effectively mirrors the task of saving a
state and executing undo or redo actions.

Code example
Let us leverage the example of a music player application to
implement the Memento Design Pattern in JavaScript,
following the steps outlined in the preceding section. We
will utilise the Memento Design Pattern in creating a music
player application that can store playlists of songs.

Step 1: Create the originator class


We begin our journey with creating the originator class. In
our example scenario, this will be the Playlist itself where
we need to save songs. We need to create a class for playlist
that has methods to set state, save the current state, and
restore the state:
1. class Playlist {
2. constructor(tracks) {
3. [Link] = tracks;
4. }
5.
6. setTracks(tracks) {
7. [Link] = tracks;
8. }
9.
10. saveToMemento() {
11. return new PlaylistMemento([Link]);
12. }
13.
14. restoreFromMemento(memento) {
15. [Link] = [Link]();
16. }
17. }
In the provided code example, we have established the
Playlist class, which accepts tracks (songs) upon
instantiation and stores them in the local property tracks.
Furthermore, we have defined three methods:
• setTracks, employed for setting tracks.
• saveMemento, responsible for creating a new instance
of the soon-to-be-created memento class.
• restoreFromMemento, which utilizes the parameter
memento representing the memento’s instance to
invoke its getTracks method and retrieve the tracks.
These tracks are then saved to the property tracks of
the Playlist class.

Step 2: Create the memento class


In this step, we will forge the memento class, tasked with
storing the playlist’s tracks and furnishing a method for
retrieval:
1. class PlaylistMemento {
2. constructor(tracks) {
3. [Link] = tracks;
4. }
5.
6. getTracks() {
7. return [Link];
8. }
9. }
As evident from the provided code, we have crafted a
PlaylistMemento class designed to accept tracks upon
instantiation and store them in the local property tracks.
Additionally, we have implemented a method named
getTracks, responsible for returning the tracks saved
within the memento.

Step 3: Create the caretaker class


It is time to introduce the third character of our narrative —
the caretaker class. Its job is to keep a track of the playlist
mementos. It should be able to add mementos to a list and
retrieve a specific memento from the list:
1. class PlaylistCaretaker {
2. constructor() {
3. [Link] = [];
4. }
5.
6. addMemento(memento) {
7. [Link](memento);
8. }
9.
10. getMemento(index) {
11. return [Link][index];
12. }
13. }
In the code snippet above, we have introduced the
PlaylistCaretaker class, initializing a property named
mementos in its constructor to store a list of mementos,
initialized by default as an empty array. Additionally, we
have implemented two methods:
• addMemento: This method accepts a memento as
input and appends it to the list stored in the mementos
property.
• getMemento: Here, an index is accepted as input, and
the method returns the memento stored at that index in
the list.
Step 4: Client code
In the final step, we will instantiate objects of the originator
and the caretaker to breathe life into the example:
1. // Example usage
2. const playlist = new Playlist(["Song 1", "Song 2", "Song
3"]);
3. const playlistCaretaker = new PlaylistCaretaker();
4.
5. [Link]([Link]
()); // Save initial playlist
6.
7. [Link](["Song 4", "Song 5"]); // Modify
playlist
8.
9. [Link]([Link]
()); // Save modified playlist
10.
11. [Link]([Link]
mento(0)); // Restore initial playlist
12.
13. [Link]([Link]); // Output: ["Song 1", "Song
2", "Song 3"]
In this final code example, we begin by creating an instance
of the originator Playlist and populate it with three songs in
the form of an array. Following this, we instantiate a
PlaylistCaretaker.
We then call the addMemento method of the playlist
caretaker to save the initial playlist. This is achieved by
invoking the saveToMemento method of the playlist,
which, as seen in Step 1, creates a new instance of
PlaylistMemento within the Playlist class to store the
tracks. The returned memento instance is then passed to
the addMemento method of the playlist caretaker, adding
this playlist to the list of mementos.
Subsequently, we modify the playlist by invoking the
setTracks method of the Playlist instance. Once again, we
use the addMemento method of the playlist caretaker to
save this updated playlist.
Finally, we utilize the Playlist class to invoke its
restoreFromMemento method to retrieve the first playlist.
This is achieved by passing the value obtained from the
invocation of the getMemento method of the
PlaylistCaretaker. This action retrieves the first value from
the list and sets it as the current playlist.
Consequently, upon printing the playlist’s current set of
tracks, the initial playlist is displayed on the console.
The Memento Pattern encapsulates playlist state in Playlist
and PlaylistMemento, managed by PlaylistCaretaker. It
enables encapsulation and retrieval of state, preserving
playlist integrity. The caretaker organizes mementos,
facilitating state history management. With this pattern, the
music player gains undo/redo functionality by saving and
restoring playlist states. Users can revert to previous
playlist configurations, enhancing application usability and
robustness.

Applications
The Memento Design Pattern proves advantageous in
scenarios where frequent saving and retrieval of an object’s
state are necessary. Let us explore some examples where
this pattern can shine:

Form state management


When building forms in web applications, the Memento
Pattern can be used to save the state of form fields, allowing
users to revert to previous inputs or undo changes.
Text editors or code editors
Text or code editors can utilize the Memento Pattern to save
and restore the state of the document, enabling users to
undo/redo changes or revert to previous versions.

Drawing applications
Applications that involve drawing or painting functionalities
can benefit from the Memento Pattern to save and restore
the state of the canvas, allowing users to undo/redo actions
or revert to previous drawings.

Game state management


In browser-based games or web applications with game
elements, the Memento Pattern can be used to save and
restore the state of the game, enabling players to undo
moves or revert to previous game states.
In each of these scenarios, the Memento Pattern provides a
systematic way to manage and revert to previous states or
versions of data, enhancing user experience and application
functionality.

Pros and cons


Here are a few advantages that the Memento Pattern brings
to JavaScript coding:
• By employing the Memento Design Pattern, we can
capture snapshots of an object’s current state without
compromising its encapsulation.
• We can simplify the originator’s code by allowing the
caretaker to take care of the entire history of the
originator’s state.
However, the Memento Design Pattern also comes with its
fair share of disadvantages. Let us explore a few of them:
• If clients create numerous mementos, it may consume
significant RAM, potentially resulting in slower
application performance and dissatisfied users.
• Crucially, caretakers must monitor the originator’s
lifecycle to effectively manage the removal of obsolete
mementos.
• There is always a risk that the state might not remain
intact.

Observer Pattern
The Observer Design Pattern, aptly named, is a Behavioral
Design Pattern utilized to establish a subscription
mechanism. This mechanism allows multiple objects, known
as observers, to stay informed about any events or changes
occurring within an object they are observing. It serves as a
robust solution for building components or models that
require synchronization with the state of another
component or model.

Problem scenario
Let us explore the example of a weather application to shed
light on certain type of challenges developers face, which
can be effectively mitigated by implementing the Observer
Design Pattern.
For the sake of this example, let us envision our weather
application sending notifications to users about changes in
the weather for the cities they have subscribed to. Weather,
being dynamic, can change frequently, and users expect
accurate updates for all their subscribed locations. With
users spread across the globe, each interested in real-time
updates for multiple locations, the system must efficiently
notify subscribed users of weather changes without
excessive resource consumption. Scalability for future
enhancements is essential, given the diverse locations users
may subscribe to. Therefore, the solution should minimize
unnecessary overhead, such as constant polling for updates,
which could degrade user experience and consume
excessive memory, delaying application processes.
The Observer Pattern offers a solution by implementing a
subscription-based mechanism. Let us delve into it in the
following sections:

Implementation
Let us outline the implementation of the Observer Pattern
into the following steps:

Step 1: Define the subject class


In the first step, we create a class that represents the
subject being observed. This class will maintain a list of
observers and provide methods for managing them.

Step 2: Define the observer classes


Next, we create one or more classes that represent the
observers. These classes should have a method that the
subject can call to notify them of changes.

Step 3: Implement subscription mechanism


In this step, we implement methods in the subject class to
allow subscribers to subscribe or unsubscribe from
receiving updates.

Step 4: Implement notification mechanism


Now, we create a method in the subject class to notify all
the subscribed observers when a change occurs.

Step 5: Create, subscribe and trigger


In the final step, we will first instantiate the subject and
observer classes. Then, we should register the observers
with the subject to receive updates. When relevant changes
occur in the subject, we call the method to notify all
observers.
In this manner, the Observer Pattern facilitates the creation
of a subscription mechanism, effectively addressing the
challenge of real-time updates. Moving forward, we will
delve into a code example to provide a clearer
understanding of its implementation.

Code example
Let us elucidate the aforementioned implementation steps
using an example from a social media application, such as
Instagram or Twitter. In these platforms, users can follow
each other virtually to receive updates on each other’s
activities:

Step 1: Define the subject class


In the first step, we will create the subject class that
represents the subject being observed. In the case of our
example, we will create a class representing a user on the
social media platform. This class will maintain a list of
followers, which ideally represent our observers, and
provide methods for managing them:
1. class User {
2. constructor(username) {
3. [Link] = username;
4. [Link] = [];
5. }
6.
7. follow(follower) {
8. }
9.
10. unfollow(follower) {
11. }
12.
13. postUpdate(update) {
14. }
15.
16. notifyFollowers(update) {
17. }
18. }
In the provided code example, we have implemented a class
User. Upon instantiation, this class accepts a username for
the social media account, which is stored in the local
property username. Additionally, we have added another
property, followers, initialized with an empty array to store
the list of followers for this user account.
Furthermore, we have defined four empty methods that will
be used for adding the subscription-notification logic in the
upcoming steps: follow, unfollow, postUpdate, and
notifyFollowers.

Step 2: Define the observer classes


Next, we create the observers. In our context, these will be
the followers who wish to follow our user account that we
created in the previous step. Let us represent this in code:
1. class Follower {
2. constructor(name) {
3. [Link] = name;
4. }
5.
6. receiveUpdate(username, update) {
7. [Link](`${[Link]} received update from
${username}: ${update}`);
8. }
9. }
In the provided code example, we have created the observer
class Follower. Upon instantiation, it accepts a parameter,
name, which is added to the local property name.
Additionally, we have implemented the receiveUpdate
method, which we created in the previous example with the
sole purpose to enable the user account to notify its
followers of updates. We observe that this method accepts
two parameters, username and update, which are then
used to print on the console.

Step 3: Implement subscription mechanism


Now that we have created the two entities of our narrative,
let us commence the journey of connecting the two with the
subscription logic, enabling us to write efficient code. The
first part involves adding the subscription mechanism,
which will allow the observers (followers) to connect or
subscribe to the subject (user):
1. class User {
2. constructor(username) {
3. [Link] = username;
4. [Link] = [];
5. }
6.
7. follow(follower) {
8. [Link](follower);
9. }
10.
11. unfollow(follower) {
12. [Link] = [Link](f => f !==
follower);
13. }
14.
15. postUpdate(update) {
16. }
17.
18. notifyFollowers(update) {
19. }
20. }
In this step, we have added the logic to two of the methods
that we had previously left empty in the first step. As a part
of the subscription logic that we intend to implement, the
follow method accepts a follower as a parameter and adds
that follower to the list of followers stored in the local
property followers. As for the unfollow method, a follower
who wishes to unfollow our user account is received as a
parameter. We utilize JavaScript’s filter method to remove
this follower from the followers list.

Step 4: Implement notification mechanism


We have initiated the first part of our logic to connect our
subject (User) to its observers (followers), enabling the
observers to receive any modifications to the code. Now, let
us proceed to complete the second part of our logic, which
involves enabling the observers to receive notifications
about the updates released by the subject class:
1. class User {
2. constructor(username) {
3. [Link] = username;
4. [Link] = [];
5. }
6.
7. follow(follower) {
8. [Link](follower);
9. }
10.
11. unfollow(follower) {
12. [Link] = [Link](f => f !==
follower);
13. }
14.
15. postUpdate(update) {
16. [Link](`${[Link]} posted update:
${update}`);
17. [Link](update);
18. }
19.
20. notifyFollowers(update) {
21. [Link](follower =>
[Link]([Link], update));
22. }
23. }
With this step, we have now added both the subscription
and the notification logic that should complete the
connection between our subject and observers. In this step,
we have added logic to the two methods created in the first
step and left empty since then. We have updated the
postUpdate and the notifyFollowers method with the
following logic:
• postUpdate: Upon invocation of this method, which
signifies the subject (i.e., the User) posting an update
such as a photo or a general post, it will first print on the
console that the user has posted the following update.
Additionally, its primary responsibility now is to ensure
that everyone who has subscribed to this class will
receive a notification of this update. Therefore, it
triggers or invokes the notifyFollowers method, where
we will implement the notification logic to reach the
observers.
• notifyFollowers: In this method, we accept the update
received from the postUpdate method. This update now
needs to be diligently communicated to all the
subscribers. Therefore, we traverse the followers array,
iterating through each follower and triggering the
receiveUpdate method that we created inside the
observer class in Step 2. During this invocation, we pass
the username and the update. The username is
passed because the subject can be more than one user,
as per our example, and simultaneously, multiple
followers can follow multiple users. Thus, this ensures
that the correct user is sent across when providing a
notification to its subscriber.

Step 5: Create, subscribe and trigger


For the final part, all we need to do is create the instances,
connect the two entities of our narrative, and trigger the
code logic. Let us proceed with that:
1. // Create users
2. const user1 = new User("Alice");
3. const user2 = new User("Bob");
4.
5. // Create followers
6. const follower1 = new Follower("Charlie");
7. const follower2 = new Follower("Diana");
8.
9. // Subscribe followers
10. [Link](follower1);
11. [Link](follower2);
12. [Link](follower2);
13.
14. // Trigger updates
15. [Link]("Hello, world!");
16. [Link]("Goodbye, world!");
In this final step, we first created two instances of users —
user1 with the name Alice and user2 with the name Bob,
achieved by instantiating the User class. Next, we created
two instances of followers — Charlie and Diana, by
instantiating the Follower class. Then, we implemented the
subscription logic. The follower1 subscribed to user1 by
invoking the follow method of the user1 instance, and
follower2 subscribed to both user1 and user2 by invoking
the follow method of both user instances. Finally, we
simulated posting updates where user1 posted a message
Hello, world! and user2 posted Goodbye, world! These
messages will now be notified to the followers.
By invoking the postUpdate method of the respective User
instances, it will internally print the update on the console
and then trigger the notifyFollowers method. The followers
that have been added to the users will now be individually
traversed, and their receiveUpdate method will be invoked,
resulting in a console log from the followers. This will result
in the following output on the console:
1. Alice posted update: Hello, world!
2. Charlie received update from Alice: Hello, world!
3. Diana received update from Alice: Hello, world!
4. Bob posted update: Goodbye, world!
5. Diana received update from Bob: Goodbye, world!
As we can see from the output above, we have implemented
each step of the Observer Pattern sequentially, from
defining the subject and observer classes to creating
instances, subscribing observers, and triggering updates.
With this approach, we have created a logic that will be
future-proof and efficient.

Applications
The Observer Pattern is beneficial in JavaScript in various
scenarios where you have one-to-many relationships
between objects, and you need to notify multiple objects
about changes or events. Here are some scenarios where
the Observer Pattern can be beneficial:

UI components and state management


In web applications, we often have UI components that need
to react to changes in application state. By using the
Observer Pattern, we can decouple the UI components from
the state management logic, making it easier to maintain
and update the application.

Event handling
JavaScript is event-driven, and many libraries and
frameworks rely on events to trigger actions. The Observer
Pattern can be used to implement event handling systems
where multiple listeners (observers) can react to events
emitted by an event emitter (subject).

Model-View-Controller architecture
In the Model-View-Controller (MVC) architecture, the
Observer Pattern is commonly used to implement the
relationship between the model (subject) and the views
(observers). Changes in the model can trigger updates in
multiple views, ensuring that the views stay synchronized
with the model.

Cross-component communication
In large applications with multiple components, you may
need a way for components to communicate with each other
without creating tight coupling between them. The Observer
Pattern provides a flexible mechanism for cross-component
communication, allowing components to subscribe to and
receive updates from each other.
Overall, the Observer Pattern is beneficial in JavaScript
whenever you need to establish loosely coupled
relationships between objects and enable communication
and synchronization between them.

Pros and cons


Here are several advantages of implementing the Observer
Pattern in a JavaScript codebase:
• It adheres to the Open/Closed Principle. With this
pattern, new subscribers can be introduced without
modifying the subject or publisher’s code. Likewise, it
can be applied conversely, allowing the addition of
multiple publishers without altering the subscribers.
• It enables the establishment of relationships between
objects at runtime.
One major disadvantage is that the observers will be
notified in random fashion. Additional logic needs to be
implemented inside the subject to handle this disadvantage.

Strategy Pattern
The Strategy Design Pattern belonging to the Behavioral
Design Pattern allows us to define a family of algorithms.
These algorithms will be encapsulated by the Design
Pattern and make them interchangeable. It enables clients
to choose an algorithm from the available options at runtime
without modifying the client code.

Problem scenario
To consider the types of problems that the Strategy Pattern
can solve, let us examine the example of a shipping
application.
In this application, shipping prices must be calculated for
various types of packages based on factors such as weight,
distance, shipping method (e.g., airways, ships, trucks), and
promotions or discounts.
The potential issues we might encounter while coding such
an application include the need for numerous conditional
statements to handle different combinations of shipping
criteria. For instance, we may resort to nested if-else
statements to determine shipping costs based on factors like
weight, distance, and shipping method. As the application
scales, maintaining and extending this code becomes
increasingly complex and error prone.
Challenges escalate if we must introduce new shipping
methods or modify existing nested conditions. These
changes could necessitate significant modifications across
multiple parts of the codebase, making it susceptible to
errors. Moreover, unanticipated or inadequately tested
modifications may introduce bugs or result in incorrect
calculations, leading to losses for both the service provider
and the customers.
To address such challenges, it is imperative to separate the
shipping calculation algorithm from the rest of the
application. This separation enables the easy addition of
new strategies, modification of existing ones, and switching
between strategies at runtime without impacting other
components of the codebase.

Implementation
Let us outline the implementation of the Strategy Design
Pattern in JavaScript into the following three steps:

Step 1: Define the strategies


The very first thing that we need to do is to create the
multiple strategies that we will be using for our code. We
will create separate objects, each representing a specific
algorithm or strategy.
Step 2: Create the context class
The context class is responsible for interacting with the
strategies. It typically holds a reference to a strategy object
and delegates the execution of the algorithm to that object.

Step 3: Use the context class with different strategies


In the final step, we instantiate the context class with a
specific strategy and use its methods to execute some
function. We can then dynamically change the strategy on
the go and then recalculate by executing a different
strategy.
Overall, the Strategy Design Pattern allows us to
encapsulate variations in algorithm implementations,
promote code reuse, and enable dynamic selection of
algorithms at runtime without modifying the client code.

Code example
Let us utilize the shipping application example from the
Problem scenario section of the Strategy Design Pattern to
implement the steps outlined in the Implementation section
above.

Step 1: Define the strategies


As we discussed earlier, the very first step involves creating
the various strategies that will be used by the context class.
In our example, we will be creating the various shipping
strategies required for our shipping application:
1. // Strategy 1: Regular shipping strategy
2. const regularShippingStrategy = {
3. calculateCost: (weight, distance) => {
4. return weight * 0.5 + distance * 0.1;
5. }
6. };
7.
8. // Strategy 2: Express shipping strategy
9. const expressShippingStrategy = {
10. calculateCost: (weight, distance) => {
11. return weight * 0.8 + distance * 0.2;
12. }
13. };
14.
15. // Strategy 3: Free shipping strategy
16. const freeShippingStrategy = {
17. calculateCost: (weight, distance) => {
18. return 0; // Free shipping
19. }
20. };
In the provided code example, three shipping strategies —
regular, express, and free — are implemented. These
strategies are represented by three objects:
regularShippingStrategy, expressShippingStrategy,
and freeShippingStrategy. Each strategy object contains a
calculateCost property, which holds a function. This
function accepts two parameters — weight and distance —
representing the package’s weight and the distance it needs
to travel. The function calculates and returns the final
shipping cost based on these parameters for each respective
strategy.

Step 2: Create the context class


In the second step, we will create a context class that is
responsible for interacting with the strategies. It will take a
strategy object during instantiation and provides methods to
set and use different strategies for calculating shipping
costs:
1. class ShippingCalculator {
2. constructor(strategy) {
3. [Link] = strategy;
4. }
5.
6. setStrategy(strategy) {
7. [Link] = strategy;
8. }
9.
10. calculateShippingCost(weight, distance) {
11. return [Link](weight, distance);
12. }
13. }
In the provided code snippet, a ShippingCalculator class
is introduced to handle interactions with the shipping
strategies and perform cost calculations. The class
constructor accepts a strategy parameter and assigns it
to the local property strategy. Two methods are defined
within this class: setStrategy for dynamically setting the
strategy, and calculateShippingCost for calculating the
shipping cost based on the provided weight and distance
parameters. Inside the calculateShippingCost method, the
calculateCost method of the assigned strategy is invoked,
triggering the respective strategy’s calculation logic and
returning the resulting cost.

Step 3: Use the context class with different strategies


In the final step, we will instantiate the context class with a
specific strategy and use its methods to perform some
calculations. With the help of the context class, we should
be able to dynamically change the different strategies:
1. // Create a shipping calculator instance with regular
shipping strategy
2. const calculator = new
ShippingCalculator(regularShippingStrategy);
3.
4. // Calculate shipping cost for a package
5. const weight = 10; // in kg
6. const distance = 500; // in km
7.
8. const regularShippingCost =
[Link](weight, distance);
9. [Link]("Regular shipping cost:",
regularShippingCost);
10.
11. // Change the strategy to express shipping
12. [Link](expressShippingStrategy);
13.
14. const expressShippingCost =
[Link](weight, distance);
15. [Link]("Express shipping cost:",
expressShippingCost);
16.
17. // Change the strategy to free shipping
18. [Link](freeShippingStrategy);
19.
20. const freeShippingCost =
[Link](weight, distance);
21. [Link]("Free shipping cost:", freeShippingCost);
In the provided code snippet, we instantiate the
ShippingCalculator class with a regular shipping strategy
(regularShippingStrategy) and store it in the variable
calculator. We then define the weight and distance
variables required for calculating the shipping price.
Next, we invoke the calculateShippingCost method of the
calculator instance, passing it the weight and distance
parameters. The calculated shipping cost is then printed to
the console. This method will internally invoke the
strategy’s calculateCost method to return the price.
Following that, we change the shipping strategy by using
the setStrategy method of the calculator instance, and
once again invoke the calculateShippingCost method.
This time, the shipping cost is calculated based on the new
strategy and printed to the console.
Finally, we change the strategy again using the setStrategy
method and invoke the calculateShippingCost method
once more to calculate and print the shipping cost based on
the updated strategy.
The output on executing the code will look like:
1. Regular shipping cost: 55
2. Express shipping cost: 80
3. Free shipping cost: 0
With the help of this example, we have achieved a
separation of the strategies from the rest of the code. This
will help us provide a clean and maintainable solution by
encapsulating each shipping calculation algorithm as a
separate strategy, allowing for easier maintenance,
extensibility, and scalability of the shipping application.

Applications
From the code implementation above, it is evident that the
Strategy Pattern offers significant benefits in scenarios
where there are multiple options or strategies, and the code
needs to dynamically switch between them at runtime. This
pattern provides flexibility and modularity, allowing for easy
addition of new strategies without impacting existing code.
In JavaScript, the Strategy Pattern finds applications in
various scenarios, including:

Sorting algorithms
Implementing different sorting algorithms (e.g., bubble sort,
quick sort, merge sort) as separate strategies allows for
easy switching between algorithms based on factors like
input size or data characteristics.

Form validation
Implementing different validation strategies for form inputs
(e.g., required fields, email format, password strength)
enables flexible and customizable validation rules based on
specific form requirements.

Authentication strategies
Implementing different authentication strategies (e.g.,
username/password, OAuth, JWT) enables flexible
authentication mechanisms tailored to different security
requirements or integration scenarios.

Search algorithms
Implementing different search algorithms (e.g., linear
search, binary search, hash table) as separate strategies
allows for efficient searching based on factors like data size
or search key characteristics.

Payment processing
Implementing different payment processing strategies (e.g.,
credit card, PayPal, bank transfer) enables flexible handling
of payment transactions based on factors like user
preferences or regional payment methods.

Routing strategies
Implementing different routing strategies in web
applications (e.g., client-side routing, server-side routing,
hybrid routing) allows for optimizing navigation based on
factors like page load performance or SEO requirements.
As seen above, the Strategy Design Pattern promotes
encapsulation and interchangeability of algorithms, allowing
for greater flexibility, maintainability, and scalability in
software design. It is particularly useful in scenarios where
multiple algorithms or behaviours need to be dynamically
selected or switched at runtime based on different factors
or requirements.

Pros and cons


Here are several advantages that the Strategy Design
Pattern offer to a JavaScript code:
• It allows us to swap algorithms at runtime, which can
be advantageous.
• We can isolate the implementation details of the
algorithm from the rest of the code which allows us to
add more strategies without impacting the code.
• We can replace inheritance with composition.
• It follows the Open/Closed Principle. We can add new
strategies without having to change the context.
A few disadvantages of this algorithm are as follows:
• If we only have a few algorithms that rarely change,
adding the overhead of new classes and interfaces may
not be necessary.
• Clients need to understand the differences between
strategies to select the appropriate one. This adds a
level of complexity and dependency on the client side.
• In modern programming languages with functional
support, we can implement different algorithm versions
using anonymous functions. This approach allows us to
use functions similar to strategy objects without
introducing extra class and interface overhead.

Conclusion
Behavioral Design Patterns, such as Chain of Responsibility,
Command, Iterator, Mediator, Memento, Observer, and
Strategy, are essential for addressing challenges in software
development by managing object interactions and
behaviors. They encapsulate behavior within objects,
promoting modular and maintainable designs while enabling
flexibility and code reuse.
These patterns allow behaviors to be parameterized,
configured, and exchanged dynamically at runtime,
facilitating adaptability to changing requirements. By
separating concerns and promoting loose coupling between
objects, Behavioral patterns enhance system maintainability
and scalability.
Unlike Creational and Structural patterns, Behavioral
patterns focus on defining patterns of communication and
collaboration between objects at runtime, rather than on
object creation or structure. By fostering communication
and collaboration among objects, these patterns lead to
better coordination and cooperation, resulting in robust and
scalable systems capable of accommodating evolving
requirements.
In summary, Behavioral Design Patterns provide powerful
tools for managing complexity, promoting flexibility, and
enhancing the maintainability and scalability of software
systems.

Points to remember
• Behavioral Design Patterns aim at facilitating the
interactions and communication between objects in a
software system.
• The Chain of Responsibility Pattern enables the
decoupling of senders and receivers of requests,
allowing multiple objects to handle a request without
explicitly specifying the receiver.
• The Command Pattern encapsulates a request as an
object, allowing for parameterization of clients with
requests, queuing of requests, and logging of command
history.
• The Iterator Pattern provides a way to access
elements of an aggregate object sequentially without
exposing its underlying representation, promoting
flexibility and reusability.
• The Mediator Pattern facilitates communication
between objects by centralizing control and reducing
direct dependencies, resulting in a more maintainable
and loosely coupled system.
• The Memento Pattern captures and externalizes an
object’s internal state, allowing it to be restored to a
previous state, thereby supporting undo and redo
operations.
• The Observer Pattern defines a one-to-many
dependency between objects, ensuring that when one
object changes state, all its dependents are notified and
updated automatically.
• The Strategy Pattern defines a family of algorithms,
encapsulates each one, and makes them
interchangeable, providing a flexible way to select and
switch algorithms at runtime.

Exercises
1. Which behavioural Design Pattern is useful for
scenarios where the participating objects
constantly need to communicate with each other?
a. Memento
b. Iterator
c. Observer
d. Mediator
2. Which behavioural Design Pattern is useful for
scenarios where the history of an object’s state
needs to be maintained so that it can allow the
user to effectively go back to previously taken
decisions?
a. Command
b. Memento
c. Chain of Responsibility
d. Strategy
3. Which behavioural Design Pattern is excellent for
the use-case of a smart home automation system?
a. Chain of Responsibility
b. Iterator
c. Command
d. Strategy

Answers
1. d
2. b
3. c
CHAPTER 5
Asynchronous
Performance Design
Patterns

Introduction
In today’s internet era, it is crucial to have web applications
that are dynamic and responsive. To leverage the full
potential of the internet, we incorporate asynchronous
JavaScript in our code. However, asynchronous operations
such as fetching data from servers, handling user
interactions, or executing time consuming tasks can
introduce complexities that may hinder performance and
scalability if not managed skillfully.
In this chapter, we will delve into the intricate landscape of
asynchronous performance in JavaScript. We will explore
the various challenges faced by developers and the solutions
available to them. Additionally, we will understand how
Design Patterns can aid in crafting efficient and robust
asynchronous code. To begin our journey, we will delve into
or refresh our understanding of asynchronous JavaScript
programming. We will cover its fundamentals and address
the notorious callback hell problem. Moreover, we will
explore event handling in JavaScript and examine the event
pipeline, potential issues that may arise, and the solutions
provided by Design Patterns. Following this, we will
examine several Design Patterns that assist in managing
asynchronous tasks, ensuring smoother execution, and
enhancing the responsiveness of JavaScript applications.

Structure
This chapter will cover the following topics:
• Asynchronous JavaScript
• Event handling in JavaScript
• Throttling and debouncing
• Parallelism and concurrency
• Batch processing
• Promise optimization

Objectives
Upon completing this chapter, we will have a greater
understanding of how Design Patterns can aid in efficiently
handling asynchronous events in JavaScript. In addition to
refreshing our knowledge of Promises and callbacks, we will
explore five new Design Patterns/concepts that will assist
us in writing efficient and robust code for asynchronous
JavaScript applications. These Design Patterns will be
effective for managing both smaller code segments and
enterprise-level code.

Asynchronous JavaScript
In the introductory segment of this chapter, we will
understand the concept of asynchronous coding in
JavaScript. We will rewind the clock to understand the
challenges encountered by the developers’ community when
dealing with asynchronous problems, the rationale behind
introducing asynchronous programming, and the solutions
that were implemented. We will scrutinize the solutions
employed, the drawbacks introduced by their
implementation, and the introduction of new concepts
aimed at addressing those issues.
To gain a comprehensive understanding of how Design
Patterns can assist with asynchronous operations, it is
essential to grasp asynchronous JavaScript in its entirety
and comprehend the problems it resolved. This section will
delve into the history of asynchronous JavaScript and the
various solutions implemented over time to address these
challenges. Feel free to skip this section if you already
possess a solid understanding of asynchronous JavaScript.

Synchronous JavaScript
To start, let us examine an example that can illustrate the
challenge developers faced when they needed to integrate
the raw power of the Internet into building JavaScript
applications:
1. // Synchronous code to fetch data and update UI
2. function fetchDataAndRender() {
3. // Assume fetchData() is a synchronous function that
fetches data from a server
4. const data = fetchData(); // This function might take
some time to complete
5.
6. // Assume renderUI() is a synchronous function that
updates the UI with the fetched data
7. renderUI(data); // This function will not execute until
fetchData() completes
8.
9. [Link]('UI updated with fetched data');
10. }
11.
12. fetchDataAndRender();
13. [Link]('Fetching data and rendering UI...');
While the above code example may not fully encapsulate all
the challenges developers faced when incorporating the
strength of the Internet in the early days, it provides a
glimpse into the issues that affected both user and
developer experiences.
Even before delving into the description of the code
example above, it is important to remember that JavaScript,
by nature, is synchronous and blocking. This means that if
the browser encounters a JavaScript file while parsing the
HTML to create the DOM tree, it will halt further parsing of
the tree until the entire JavaScript file has been read and
executed. With this in mind, when examining the depicted
example, we can observe that the very first line to be
executed is line number 12, where the
fetchDataAndRender method is invoked.
Within the fetchDataAndRender method, the initial line of
code executed is the invocation of the fetchData method,
storing its response in the variable data. Although we have
not delved into the specifics of the fetchData method, it is
reasonable to assume that in the early days of integrating
network fetching in JavaScript, this function’s
implementation would entail connecting to servers,
requesting data, and awaiting the correct data response. In
the forthcoming segments, we will explore the earliest
examples of asynchronous JavaScript with the assistance of
XMLHttpRequest.
Until the entire execution of the fetchData method is
completed, the subsequent line of code responsible for
rendering the UI will not be reached or executed due to the
blocking and synchronous nature of JavaScript. Once again,
the rendering of the page will take several milliseconds,
depending on the size of the data received and the
complexity of the design.
If you have had the experience of opening web pages in the
90s or the early 2000s, waiting several seconds for your
website to load would have been quite common. Users
would often encounter a page similar to the example below:

Figure 5.1: In the early 2000s, it would often take several seconds for web
pages to load, and it was common to observe incomplete web page displays
As depicted in Figure 5.1, it would frequently take several
seconds to fetch and render data. Additionally, improper
styling references could further slowdown the construction
of DOM trees and potentially lead to circular dependencies,
resulting in users seeing an incomplete display of a web
page.
Fast forward to 2024, slower rendering of web applications
could lead to a significant loss of customers. Users are more
likely to switch to a platform that loads quickly and does not
leave them waiting indefinitely to use the application.

Challenges with synchronous JavaScript


Based on the example above, let us examine the list of
issues associated with the synchronous nature of JavaScript
during the rise of web based applications, which ultimately
prompted the introduction of asynchronous JavaScript:

Blocking nature of JavaScript


As observed in the example above, JavaScript initially
operated synchronously by default. It means long running
tasks could block the main execution thread. This resulted
in unresponsive user interfaces, especially when performing
tasks like fetching data from servers or processing large
datasets.

Network operations
Web applications often need to interact with servers to fetch
data or perform actions. In synchronous JavaScript, making
a network request would halt the execution of subsequent
code until the request is completed. This led to poor user
experiences, as the browser would appear frozen until the
request finished.

User experience
Users expect web applications to be fast and responsive.
However, synchronous JavaScript could cause delays in
responding to user interactions, leading to frustration and
dissatisfaction.

Concurrency and stability


As web applications became more complex, with multiple
concurrent tasks and interactions, managing asynchronous
operations in a scalable and maintainable way became
increasingly challenging.

Browser compatibility
Ensuring consistent behavior across different browsers and
versions was a significant concern. Different browsers had
different implementations of JavaScript and its APIs, which
could lead to unexpected behavior, especially in
asynchronous scenarios.
In response to these challenges, asynchronous
programming constructs such as Promises, async/await, and
event driven architectures were introduced to JavaScript.
These features enable developers to write non-blocking
code that can handle asynchronous operations more
efficiently, resulting in faster, more responsive, and more
maintainable web applications.

Callback functions
In the preceding segment, we observed the numerous
drawbacks associated with the synchronous nature of
JavaScript programming. It became imperative to introduce
an asynchronous approach for handling network requests
and other critical scenarios in JavaScript. This necessity
gave rise to the introduction of callback functions, or simply
callbacks. The concept was straightforward: functions
would accept a callback, or a block of code, as an argument.
This block of code would only be executed when an
asynchronous operation was completed. Let us examine a
code snippet showcasing a callback function:
1. // Asynchronous function with a callback
2. function fetchData(callback) {
3. [Link]("Fetching data...");
4. setTimeout(function() {
5. [Link]("Data fetched!");
6. const data = { id: 1, name: "Example Data" };
7. callback(data);
8. }, 2000); // Simulating a delay of 2 seconds
9. }
10.
11. // Callback function to handle the fetched data
12. function processData(data) {
13. [Link]("Processing data:", data);
14. }
15.
16. // Calling the fetchData function with a callback
17. fetchData(processData);
18.
19. [Link]("Program continues to execute...");
As evident from the above example, we have two functions:
fetchData and processData. It is logical to conclude that
data processing can only occur once the data has been
fetched. Therefore, it is crucial that the processData
function is invoked only after the fetchData function has
successfully completed its task. However, it is equally
essential that invoking this function does not block the
execution of the remaining code. When we run this code,
you will get the following output:
1. Fetching data...
2. Program continues to execute...
3. Data fetched!
4. Processing data: { id: 1, name: 'Example Data' }
As evident from the above output, invoking fetchData
triggers the execution of the first line inside the fetchData
method, printing the statement Fetching data... to the
console. However, immediately afterward, JavaScript
encounters an asynchronous function, setTimeout.
Traditionally, this would entail pausing the code for 2
seconds (the period specified in the setTimeout function)
before proceeding with the rest of the fetchData method.
Consequently, the line that prints Program continues to
execute on the line number 19 would not have been printed
until the entire fetchData function was executed (including
the 2 seconds wait) was complete.
Indeed, with the introduction of asynchronous JavaScript
and callback functions, JavaScript no longer blocks
execution for 2 seconds. Instead, it proceeds to the next line
of code after the fetchData invocation and prints Program
continues to execute.... When the two seconds elapse, it
resumes execution from the point where it was paused due
to its asynchronous nature.
However, there are certain issues with this approach, and
we will delve into them in the next segment: Callback hell.

Callback hell
In the previous segment, we were introduced to one of the
very early solutions to implement the asynchronous
behavior in JavaScript. While callbacks turned out to be a
game-changer, there was one major drawback that soon
came into picture.
Also known as the Pyramid of Doom due to the distinctive
shape taken up by the code, callback hell refers to the
situation in JavaScript where multiple nested callbacks are
used to handle asynchronous operations. In the coding
example from the previous section, we observed the
processData method being dependent on the fetchData
method. Now, consider a scenario where several methods
are sequentially dependent on previous methods for their
invocation.
Let us consider the example of a social media application. In
such an application, it is crucial to first fetch user
information. Only after retrieving the user’s information
can, we proceed to fetch the user’s posts. Subsequently,
only when the user’s posts have been successfully fetched
can we consider fetching comments on a particular post. Let
us visualize this in the form of code:
1. function fetchUserData(userId, callback) {
2. // Simulated API call to fetch user data
3. setTimeout(function() {
4. const userData = { id: userId, name: "John Doe" };
5. [Link]("User data fetched:", userData);
6. callback(userData);
7. }, 1000);
8. }
9.
10. function fetchUserPosts(user, callback) {
11. // Simulated API call to fetch user posts
12. setTimeout(function() {
13. const posts = ["Post 1", "Post 2", "Post 3"];
14. [Link]("User posts fetched:", posts);
15. callback(posts);
16. }, 1500);
17. }
18.
19. function fetchPostComments(post, callback) {
20. // Simulated API call to fetch comments on a post
21. setTimeout(function() {
22. const comments = ["Comment 1", "Comment 2"];
23. [Link]("Comments for post", post, ":",
comments);
24. callback(comments);
25. }, 2000);
26. }
27.
28. // Callback hell - nested callbacks
29. fetchUserData(1, function(user) {
30. fetchUserPosts(user, function(posts) {
31. [Link](function(post) {
32. fetchPostComments(post, function(comments) {
33. // Do something with comments
34. });
35. });
36. });
37. });
As evident from the code above, we have created three
functions to handle the three network requests required.
The fetchUserData method retrieves the user’s
information, the fetchUserPosts method retrieves the
user’s posts, and the fetchPostComments method
retrieves comments on a specific post of the user. We can
observe that these methods have been structured in a
callback fashion, where each method accepts a callback in
its argument, which is then triggered after the function’s
execution has been completed.
While the initial part appears straightforward, the
complexity emerges from line 29 onwards. As evident, due
to the chain of dependencies, each function is called within
the execution of another function. This nesting of callbacks
complicates the code, making it harder to read, understand,
and maintain. The structure resembles a pyramid or
staircase, with each level representing a new asynchronous
operation or error handling logic. While in our example, we
only defined three functions, a more realistic scenario might
involve additional methods for tasks such as login and
authentication before fetching user information.
Additionally, there could be further methods for retrieving
reactions or replies to comments after fetching the
comments. This potential loop of possibilities could
significantly complicate the code further.
Callback hells are notoriously difficult to handle, debug, or
read, as we can see from the example above. There could be
several reasons for a callback hell to arise:
• Sequential asynchronous operation: Like in the
above example, there could arise several situations
where we need to perform one asynchronous task after
another. If we do not pay close attention or if we follow
this above approach, we might end up nesting callbacks
to ensure the sequential execution.
• Error handling: Handling errors in asynchronous
operations adds another layer of nesting, especially if
different error scenarios need to be handled differently.
• Complex control flow: Asynchronous code with
complex control flow, such as loops or conditional
branching, can lead to deeply nested callbacks.
• Anonymous functions: In many cases, callback
functions are defined inline as anonymous functions,
further cluttering the code, and making it harder to
follow.
As evident from the above description, callbacks proved to
be more complex to use for realistic scenarios due to the
challenges associated with nesting related sequential
asynchronous requests.

Promises
The ES6 era in 2015 introduced a new solution to tackle the
issue of asynchronous JavaScript. The challenges observed
with callbacks posed a significant obstacle for JavaScript
users, especially with the increasing prevalence of web
applications powered by JavaScript. The ES6 era brought
forth a robust solution in the form of Promises.
Promises offer a superior method for managing
asynchronous operations. They not only address the issues
encountered in callback hells but also represent the
completion or failure of an asynchronous operation.
Promises provide a cleaner and more structured approach
to handling asynchronous code.
To better understand Promises, let us consider the example
of the popular music band Coldplay. Given their immense
popularity, it is apparent that fans of the band would
inundate them with requests for information about
upcoming concerts. Realistically, it is not feasible for the
band to respond to every fan, potentially numbering in the
millions worldwide. A more streamlined solution would be to
encourage fans to follow their social media accounts or a
blog. By doing so, the band can update all concert-related
information on these platforms, ensuring that every follower
receives up-to-date information about the band and its
concerts. This analogy aligns perfectly with the concept of
Promises.
Figure 5.2: A pictorial description of how Promises work in a real-life scenario
of a music band
In this example, the music band acts as the producer, and
the fans serve as the consumers. A Promise represents the
fans’ request for information about a concert. Consumers
must subscribe to the producers to request this information.
Subsequently, when the request is fulfilled or if it fails, the
subscribed users will be notified accordingly.
Based on what we have read earlier, a Promise works in the
following fashion:
A Promise, essentially, represents a value that may be
available now, or in the future, or never. At any point in
time, it has three states:
• Pending: In the initial state of the Promise, it is
considered pending. At this stage, the Promise is neither
fulfilled nor rejected. Analogously, think of the pending
state as fans’ requests for information about an
upcoming concert. During this period, the music band
has neither confirmed the dates for their next concert
nor denied the possibility of a concert.
• Fulfilled: When the operation is completed
successfully, the Promise is considered fulfilled, and it
holds a resolved value. In our analogy, this would
symbolize the music band confirming the dates of their
upcoming concert, and the subscribed users receiving a
notification about it.
• Rejected: This indicates that the operation has failed,
and the Promise holds a reason for the failure. In our
analogy, it would mean that the music band has
confirmed on their website that they will not be
performing or scheduling any concerts for the
foreseeable future, and the users are duly notified about
this.
Additionally, every Promise had two methods to handle the
above states:
• then(): This method is used to handle the fulfilment of a
Promise. It takes two optional callback functions — one
for the fulfilment (onFulfilled) and one for the rejection
(onRejected).
• catch(): This method is used to handle Promise
rejections (errors). It is similar to the second parameter
of then(), but only catches rejections instead of both
fulfilment and rejection.
To gain a more contextual understanding of Promises, let us
consider the example of a social media application
previously used to illustrate callback hell. In this scenario,
we will implement Promises to handle the task of fetching
comments for a particular post belonging to a specific user.
By the end of this example, we will explore how Promises
alleviate the complexities associated with callback hell:
1. function fetchUserData(userId) {
2. return new Promise(function(resolve, reject) {
3. // Simulated API call to fetch user data
4. setTimeout(function() {
5. const userData = { id: userId, name: "John Doe" };
6. [Link]("User data fetched:", userData);
7. resolve(userData);
8. }, 1000);
9. });
10. }
11.
12. function fetchUserPosts(user) {
13. return new Promise(function(resolve, reject) {
14. // Simulated API call to fetch user posts
15. setTimeout(function() {
16. const posts = ["Post 1", "Post 2", "Post 3"];
17. [Link]("User posts fetched:", posts);
18. resolve(posts);
19. }, 1500);
20. });
21. }
22.
23. function fetchPostComments(post) {
24. return new Promise(function(resolve, reject) {
25. // Simulated API call to fetch comments on a post
26. setTimeout(function() {
27. const comments = ["Comment 1", "Comment 2"];
28. [Link]("Comments for post", post, ":",
comments);
29. resolve(comments);
30. }, 2000);
31. });
32. }
33.
34. // Using Promises to chain asynchronous operations
without nesting
35. fetchUserData(1)
36. .then(function(user) {
37. return fetchUserPosts(user);
38. })
39. .then(function(posts) {
40. return [Link]([Link](fetchPostComments));
41. })
42. .then(function(commentsArray) {
43. // Do something with commentsArray
44. })
45. .catch(function(error) {
46. [Link]("Error:", error);
47. });
In the above example, we have utilized the same functions
previously used to illustrate callback hell. As seen, the
fetchUserData method retrieves the user’s information,
the fetchUserPosts method retrieves the user’s posts, and
the fetchPostComments method retrieves comments on a
specific post of the user. However, a notable difference
between the two examples is the absence of the daunting
pyramid-like structure that signified callback hell. Instead,
we have a much cleaner and a viable option for handling the
chain or sequence of network requests. Each function
returns a Promise, and we use .then() to chain them
together sequentially. This approach avoids the nesting of
callbacks, making the code much more readable and
maintainable. Additionally, error handling is simplified with
the use of .catch() at the end of the chain.

Async/await
Two years after JavaScript was revolutionized with the
introduction of the game-changing ES6, the W3C (the
international standards organization responsible for
standardizing the ECMAScript format) introduced an
advanced approach to handling asynchronous JavaScript
with ES8 in 2017. This new approach, known as
async/await, offers a more concise and synchronous-looking
syntax for writing asynchronous code compared to
traditional Promise-based approaches.
While Promises solved the critical problem of callback hell,
async/await is more like an advancement on the well-
established concept of Promises. While Promises addressed
the issue of callback hell to some extent, code could still
become complex and hard to follow, especially when dealing
with multiple asynchronous operations or error handling
scenarios. Async/await was introduced to provide a more
intuitive and readable way to work with asynchronous code,
making it easier for developers to write and maintain
asynchronous programs.
Async/await is built on top of Promises and provides
syntactic sugar for working with asynchronous operations.
It consists of two keywords:
• async: The async keyword is used to define an
asynchronous function. An async function returns a
Promise implicitly, allowing us to use the await keyword
within it to pause execution until a Promise is resolved
or rejected.
• await: The await keyword is used to pause the
execution of an async function until a Promise is settled
(resolved or rejected). It can only be used within an
async function.
The primary advantage of using async/await over traditional
Promises is its synchronous-like approach, which simplifies
debugging. It eliminates the need for explicit Promise
chaining or nesting of callbacks, resulting in cleaner and
more readable code. Let us briefly examine how async/await
is implemented in code. We will use the same example of
fetching comments on a user’s particular post to illustrate
the difference in coding styles:
1. function fetchUserData(userId) {
2. return new Promise(function(resolve, reject) {
3. // Simulated API call to fetch user data
4. setTimeout(function() {
5. const userData = { id: userId, name: "John Doe" };
6. [Link]("User data fetched:", userData);
7. resolve(userData);
8. }, 1000);
9. });
10. }
11.
12. function fetchUserPosts(user) {
13. return new Promise(function(resolve, reject) {
14. // Simulated API call to fetch user posts
15. setTimeout(function() {
16. const posts = ["Post 1", "Post 2", "Post 3"];
17. [Link]("User posts fetched:", posts);
18. resolve(posts);
19. }, 1500);
20. });
21. }
22.
23. function fetchPostComments(post) {
24. return new Promise(function(resolve, reject) {
25. // Simulated API call to fetch comments on a post
26. setTimeout(function() {
27. const comments = ["Comment 1", "Comment 2"];
28. [Link]("Comments for post", post, ":",
comments);
29. resolve(comments);
30. }, 2000);
31. });
32. }
33.
34. async function fetchData() {
35. try {
36. const user = await fetchUserData(1);
37. const posts = await fetchUserPosts(user);
38. const commentsArray = await
[Link]([Link](fetchPostComments));
39. // Do something with commentsArray
40. } catch (error) {
41. [Link]("Error:", error);
42. }
43. }
44.
45. // Call the fetchData function
46. fetchData();
To comprehend the difference between the approach taken
by Promises and the approach taken by async/await, let us
review the code written for Promises before examining this
code. Indeed, the primary difference between the two
approaches lies in the final execution, where we
sequentially call the network requests. With Promises, we
had to rely on chaining, which was a significant
improvement from traditional callback approaches.
However, it still required writing a considerable amount of
code, which could be somewhat challenging to read. In
contrast, the async/await approach simplifies the entire
process to the extent that it resembles a synchronous
pattern.
As observed in the code above, instead of chaining
sequential network request calls, we utilize three variables.
We are introducing a new function called fetchData to
encapsulate the entire process of our sequential network
requests. Inside this function, we wrap our code inside a
try/catch block. As required, since we utilize await in our
network requests, we prepend the function definition with
the async keyword. This indicates to the browser and
JavaScript that this function implements the async/await
functionality for asynchronous calls. The variable user
awaits the completion of the fetchUserData method,
signified by the await keyword. This ensures that the code
remains asynchronous and waits for execution to be
completed. The variable user is then passed to the
fetchUserPosts invocation on the subsequent line. Once
again, we utilize the await keyword to indicate that this will
be an asynchronous call, and the variable posts will need to
wait for the function execution to be completed before it can
obtain the data. Finally, on the next line, we utilize the
posts variable along with the await keyword to populate the
commentsArray. Any failure occurring at any point in the
code will be caught by the catch block we specifically
added.
We have not only achieved a significant reduction in the
number of lines of code required, but also vastly improved
the readability of the code by adopting a synchronous
coding style.

Generators and yield


In 2015, ES6 introduced another key feature called
generators, powered by the keyword yield. However,
generators were not as intuitive as Promises and
async/await, which were introduced two years later.
Nonetheless, let us explore what generators had to offer.
Generators were not introduced specifically for
asynchronous programming. However, they provided a
powerful mechanism for defining iterator-like objects and
writing asynchronous code that resembles synchronous
code. Generators with the yield keyword offer a more
structured way to manage asynchronous tasks, especially
when dealing with sequences of asynchronous operations or
complex control flow.
Generators provide a powerful way to work with sequences
of data that can be paused and resumed. They are often
used for asynchronous programming, iterating over large
datasets, and implementing custom iteration behavior. The
keyword yield is used in conjunction with the JavaScript
generators to pause and resume the execution of a function.
When yield is encountered in a generator function, it
pauses a function execution and returns the yielded value.
The function can then be resumed from that point later. Let
us have a look at generators and yield in action:
1. function* generatorFunction() {
2. yield 1;
3. yield 2;
4. yield 3;
5. }
6.
7. const generator = generatorFunction();
8. [Link]([Link]()); // { value: 1, done: false }
9. [Link]([Link]()); // { value: 2, done: false }
10. [Link]([Link]()); // { value: 3, done: false
}
11. [Link]([Link]()); // { value: undefined,
done: true }
Here, generatorFunction is a generator function defined
using the function* syntax. Inside the function body, there
are three yield statements. When this function is called, it
returns a generator object. Each time the generator’s
next() method is called, the function’s execution advances
until it encounters a yield statement. At that point, it pauses
and returns an object with the yielded value and a done
property indicating whether the generator has completed its
execution. In this way, the generators allow us to produce a
sequence of values lazily and control the flow of execution.

Web APIs
The web APIs are technically not a language feature and a
few of them have been in existence since the early days of
JavaScript. They are a set of tools and protocols that allow
different software applications to communicate with each
other over the web. They provide a way for web developers
to interact with various web services and functionalities,
such as fetching data from servers, manipulating the DOM,
handling user input, and more. In this segment, we won’t
cover every Web API. Instead, we’ll focus on a selection of
web APIs and describe their roles in JavaScript.
Here are some common examples of Web APIs in JavaScript:
• DOM: This API allows JavaScript to interact with the
HTML and CSS of a web page dynamically. We can use
the methods and properties provided to manipulate the
structure, content, and style of a webpage.
• XMLHttpRequest (XHR): This API allows JavaScript
to make HTTP requests to servers asynchronously. It’s
commonly used to fetch data from a server without
needing to reload the entire webpage. XHR is often used
in Asynchronous JavaScript and XML (AJAX) to
create dynamic and interactive web applications.
• Fetch API: Like XHR, the Fetch API provides a modern,
Promise-based interface for making HTTP requests in
JavaScript. It offers a more streamlined and flexible way
to fetch resources asynchronously from the server.
• Web storage API: This API provides mechanisms for
storing data in the browser, including sessionStorage
and localStorage. Developers can use these storage
options to persist data locally on the user’s device, such
as user preferences, shopping cart items, or
authentication tokens.
• Geolocation API: This API allows web applications to
access the user’s geographical location information. It
enables developers to build location-aware web
experiences, such as mapping services, location-based
reminders, or weather apps.
• Canvas API: The Canvas API provides a way to
dynamically generate and manipulate graphics and
animations on a web page using JavaScript. It is
commonly used for creating interactive visualizations,
games, and multimedia content.
• Web audio API: This API allows developers to
generate, manipulate, and play audio in the browser
using JavaScript. It is useful for building audio-related
applications, such as music players, audio editors, or
sound synthesizers.
These are just a few examples of the web APIs that are
commonly used in daily usage by JavaScript developers.
Each API serves a specific purpose to help create interactive
and intuitive web applications.

Event handling in JavaScript


Thus far, we have delved into the increasing necessity of
managing asynchronous code in JavaScript. In response,
JavaScript introduced several concepts over time. It began
with callbacks, but handling multiple sequential callbacks
proved to be a significant challenge. Consequently,
JavaScript introduced Promises. While Promises are still
widely used, code complexity and readability can often be
comPromised. This led to the introduction of async/await,
providing a more concise and readable alternative.
Additionally, JavaScript introduced generators, which were
instrumental in resolving issues related to sequential
network calls. Furthermore, we briefly explored various
Web APIs that aid in tasks such as fetching data from
servers, manipulating the DOM, handling user input, and
more.
In this segment of the chapter, we will examine another
crucial aspect of JavaScript that frequently relies on
asynchronous behavior: event handling. We will begin by
briefly exploring what events are in JavaScript and how they
are managed. Following that, we will delve deeper into the
asynchronous handling of events in JavaScript by examining
concepts such as the event queue, call stack, and Event
Loop.
Event handling is indeed a fundamental concept that is
typically covered in introductory chapters on HTML and
JavaScript. While we would not spends much time on the
basic introduction of events or event handling in this
segment, we will briefly touch upon the definition of events
and event handling and look at a basic example for
continuity. However, our focus will primarily be on the
asynchronous side of event handling: the event queue, call
stack, and Event Loop. Understanding the basics will be
essential for comprehending the asynchronous aspects of
event handling and maintaining continuity.

Events in JavaScript
In JavaScript, events are essentially actions or occurrences
that occur in the browser, triggered by the user or the
browser itself. They are a by-product of user interactivity.
For example, events include clicking a button, hovering over
an element, pressing a key on the keyboard, resizing a
browser window, and so on.
Event handling involves specifying the code that should be
executed in response to a particular event. For example, if a
user clicks on a button designated for signing in existing
users, the corresponding code should consider the
username and password entered by the user and send it to
the server for user verification. This is commonly achieved
by attaching event listeners to HTML elements. Event
listeners listen for specific events to occur, and when they
do, they execute a callback function.
Let us explore the most basic example of how events are
handled in vanilla JavaScript:
1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <meta name="viewport" content="width=device-
width, initial-scale=1.0">
6. <title>Event Handling Example</title>
7. </head>
8. <body>
9. <button id="myButton">Click Me!</button>
10.
11. <script>
12. // Get a reference to the button element
13. var button =
[Link]('myButton');
14.
15. // Add an event listener to the button
16. [Link]('click', function() {
17. // Code to run when the button is clicked
18. alert('Button clicked!');
19. });
20. </script>
21. </body>
22. </html>
In the provided HTML code, there is a single button
displayed on the UI, assigned with the id myButton. In the
JavaScript section, we utilize the DOM method
getElementById to locate the element with the id
myButton in our HTML DOM. Once we have obtained the
corresponding HTML element, we attach an event listener
to it using another DOM method, addEventListener. This
method requires two arguments: the first argument
specifies the event we intend to target, which in our case is
the click event. The second argument is the callback
function to be executed whenever the button with the id
myButton is clicked.

Event propagation
Event propagation refers to how events are managed as
they propagate or traverse through the DOM elements in
the browser. It involves two phases: the capturing phase
and the bubbling phase. Understanding these phases is
crucial for comprehending the intricate nature of event
handling in a real-life web application. This understanding
serves as the groundwork for comprehending Design
Patterns that aid in dealing with asynchronous
programming in JavaScript. Let us try to understand the
difference between event capturing and event bubbling
using an image:
Figure 5.3: Capturing vs. bubbling

Capturing phase
As depicted in the diagram, the capturing phase of event
handling can be seen as a top to bottom approach. In this
phase, the event is captured from the outermost element
down to the target element. The capturing phase allows you
to intercept events at the ancestor elements before they
reach the target element. However, by default, event
handlers are not executed during this phase unless
explicitly set.

Bubbling phase
Event bubbling is a type of event propagation where the
event starts from the target element and then bubbles up
through its ancestors in the DOM hierarchy, all the way up
to the root of the document (usually the <html> element).
During the bubbling phase, each ancestor element in the
DOM hierarchy can handle the event. Event bubbling is the
default behavior in most modern browsers.

Event Loop
Now that we have a solid understanding of events and how
they are managed in JavaScript, let us explore how
asynchronous events are handled. In JavaScript, the Event
Loop is a vital concept that governs the handling of
asynchronous operations. It serves as the mechanism
ensuring non-blocking I/O operations, enabling JavaScript
to efficiently manage multiple tasks.
To understand Event Loop, we need to understand the two
primary participants in this concept — Call Stack and Event
Queue.
JavaScript follows a single-threaded execution model by
default, meaning it can execute only one task at a time. The
Call Stack is where function calls are queued and executed
in a last-in, first-out (LIFO) manner. When a function is
invoked, it is added to the stack, and when it completes, it is
removed from the stack. Like the Call Stack, the Event
Queue is a queue that holds events/callbacks that are ready
to be processed. The Event Loop, meanwhile, continuously
monitors the Call Stack and the Event Queue. It checks if
the Call Stack is empty and if there are any events in the
Event Queue. If the Call Stack is empty and there are events
in the Event Queue, the Event Loop moves the first
event/callback from the Event Queue to the Call Stack for
execution:
1. [Link]("Start");
2.
3. setTimeout(function() {
4. [Link]("Inside setTimeout");
5. }, 0);
6.
7. [Link]("End");
Let us use the provided code example as a reference to
understand how the Event Loop, the Call Stack, and the
Event Queue work together in harmony:
• Initially, the JavaScript engine starts executing the code
synchronously from the top to the bottom. So, based on
the code example above, "Start" will be logged first on
the console. Then, the setTimeout function will be
called, and finally, "End" will be logged. At this point,
the synchronous execution is completed.
• When the setTimeout function is encountered, it
schedules the callback function to be executed after a
specified delay (even though the delay is 0 milliseconds
in this case). However, the callback function is not
executed immediately; instead, it is pushed into the web
API environment to be executed later.
• Once the callback function is ready to be executed, it is
placed in the Event Queue. The Event Queue holds
events/callbacks that are ready to be processed.
• The Event Loop continuously monitors the Call Stack
and the Event Queue. It checks if the Call Stack is empty
and if there are any events in the Event Queue. If the
Call Stack is empty and there are events in the Event
Queue, the Event Loop moves the first event/callback
from the Event Queue to the Call Stack for execution.
• When the callback function from the setTimeout
operation is moved from the Event Queue to the Call
Stack, it gets executed. So, "Inside setTimeout" will be
logged after "End", even though it is scheduled with a
delay of 0 milliseconds.
Therefore, the final output will be:
1. Start
2. End
3. Inside setTimeout

Throttling and debouncing


With the completion of the previous section, we have gained
a solid understanding of both asynchronous programming in
JavaScript and how events are handled asynchronously. In
this segment, we will build upon the foundations laid out in
the chapter so far and learn about Design Patterns that can
enhance the efficiency of our asynchronous code. To begin,
we will explore throttling and debouncing as two Design
Patterns that can optimize how functions are called,
particularly in scenarios where performance or resource
usage needs to be managed.

Throttling
Throttling is a Design Pattern in JavaScript that helps
control the rate at which a function is executed, ensuring it
is called at the most once within a specified time period.
This pattern is particularly useful in scenarios where event
handlers or other functions are invoked frequently, such as
scrolling or typing events in a web application. Throttling
helps in optimizing performance and managing resources by
limiting the frequency of function calls. In the context of
asynchronous programming, throttling regulates the flow of
asynchronous tasks, preventing them from overwhelming
the system with rapid execution. By spacing out function
calls, throttling can help prevent performance degradation
and ensure smoother operation of the application.

Problem statement
Consider a scenario where we are implementing a search
feature in a web application. As users type into the search
field, an API request is sent to the server to fetch the results
and update the UI accordingly. However, we cannot
accurately guess the speed at which a user can type. For
instance, if we have a user that types very rapidly, it could
lead to a flood of API requests being sent to the server. This
could potentially overwhelm the server by causing
unnecessary load. This will not only impact on the
performance of the application but also put unnecessary
strain on the server.

Implementation
For these Design Patterns, since the implementation steps
are minimal, we will merge the implementation, and the
code example sections into one. To explain throttling, we
will continue with the same example defined in the problem
statement.
Essentially, we need a function that can regulate the flow of
the number of requests being sent to the server, preventing
it from becoming overwhelmed and crashing. In a more
generic sense, we need to implement a process that can
control the number of asynchronous requests.
Step 1: Define the throttle function
In this step, we will define the throttle function that will
regulate the flow of the asynchronous requests. Let us look
at how we can create the throttle function:
1. function throttle(func, delay) {
2. let lastCall = 0; // Initialize a variable to store the
timestamp of the last function call
3.
4. return function(...args) {
5. const now = new Date().getTime(); // Get the current
timestamp
6. if (now - lastCall >= delay) { // Check if enough time
has elapsed since the last call
7. func(...args); // Call the original function with the
provided arguments
8. lastCall = now; // Update the timestamp of the last
call
9. }
10. };
11. }
In the above code example, we have defined the throttle
function. This function accepts two arguments: a function,
which will be the original function requiring throttling, and
a delay, signifying the time in milliseconds during which the
function should not be called again.
In this code, we have utilized the concept of closure. The
variable lastCall, defined on line number 2, will act as a
private variable for the returned function defined from line
4 onwards. Inside the returned function, lastCall keeps
track of the timestamp of the last invocation. The returned
function also accepts any arguments passed to it and checks
whether enough time has elapsed since the last function call
(now - lastCall >= delay). If it has, the original function
(func) is invoked with the provided arguments, and the
timestamp of the last call (lastCall) is updated.
Step 2: Using the throttle function for throttling
In this step, we will use the throttle function that we have
created in the previous step on a function that requires
throttling. In our case, it will be the function that will make
the network request. Let us have a look at the code to
visualize that:
1. // Function to simulate making a network request
2. function makeNetworkRequest(data) {
3. [Link](`Making network request with data:
${data}`);
4. }
5.
6. // Create a throttled version of the network request
function with a delay of 1000 milliseconds (1 second)
7. const throttledNetworkRequest =
throttle(makeNetworkRequest, 1000);
In the above code, we have created a function called
makeNetworkRequest that accepts an argument data.
Although we are only printing a console statement in this
function for demonstration purposes, in a real-life scenario,
this function could contain the network API request that we
intend to make. This function will be triggered every time
the user enters a keyword to search. Therefore, we need to
ensure that this function is throttled.
Hence, we create a new variable called
throttledNetworkRequest that invokes the throttle
function we created in the previous example. We pass the
makeNetworkRequest function and a time of 1 second
(1000 milliseconds) as arguments.
Step 3: Use the throttled function to invoke async
code
For the final step, we will use the throttled function instead
of the original function to make a network request. This
ensures that our network requests have been throttled or
limited to avoid overwhelming the server.
1. [Link]('searchInput').addEventListe
ner('input', function(event) {
2. const query = [Link];
3. throttledNetworkRequest(query);
4. });
In the above code, we have demonstrated the actual
implementation of a user input that triggers the throttled
function. For demonstration purposes, we have assumed our
input element to have the id searchInput. We add an event
listener to the input event of this element, ensuring that
every time the user adds a value to the input box, the
function in the second argument will be triggered. Inside
that callback function, we call the
throttledNetworkRequest method with the value of the
input element.
Throttling is a technique used to control the frequency of
task execution over a specific time period. Instead of
allowing a task to execute, for example, 20 times in a short
interval, throttling reduces the execution frequency to a
manageable number, such as 5 times. This approach helps
prevent excessive strain on the server by spreading out the
workload, ensuring system performance remains stable and
responsive while avoiding potential overloads or crashes
caused by high-frequency requests.
By following these steps, we can implement throttling in our
JavaScript code to control the rate at which network
requests are made, ensuring smoother performance and
avoiding overwhelming the server with excessive requests.
We can adjust the delay parameter as needed to suit our
specific requirements.

Applications
As mentioned before, throttling is excellent for scenarios
where we need to optimize performance and manage
resources. Here are a few of the scenarios where throttling
can be beneficial:

Scrolling events
Throttle the execution of event handlers attached to scroll
events, such as infinite scrolling or parallax effects, to
prevent excessive function calls as the user scrolls.

Resize events
Throttle the execution of event handlers attached to window
resize events to avoid unnecessary recalculations or UI
updates, especially in complex layouts.
Input events
Throttle the execution of event handlers attached to input
events, such as keypresses or mouse movements, to prevent
rapid firing of functions, especially in autocomplete or
search functionality.

Network requests
Throttle the rate of network requests, such as AJAX
requests or API calls, to avoid flooding the server with too
many requests at once and to adhere to rate limits or API
usage policies.

UI updates
Throttle the execution of UI update functions, such as
rendering or DOM manipulation, to optimize performance
and prevent excessive reflows or repaints, especially in
data-intensive applications.

Polling
Throttle the frequency of polling operations, such as
periodic data updates or status checks, to conserve
bandwidth and server resources, especially in long-lived
connections or real-time applications.

Pros and cons


Here are some of the advantages of using throttling pattern:
• It allows us to control the rate at which a function is
invoked, ensuring it is called at a manageable frequency.
This helps prevent performance degradation and
resource exhaustion.
• It can improve the performance of our application and
help us in avoiding potential bottlenecks or slowdowns.
• Most importantly, it helps us reduce the load on servers
in scenarios involving network requests, such as AJAX
calls or API requests.
However, there are also a set of disadvantages associated
with this Design Pattern:
• Throttling introduces a delay between function
invocations, which can lead to a delayed response in
certain scenarios. For tasks that require immediate or
real-time updates, throttling may not be suitable as it
can introduce noticeable latency.
• In some cases, throttling may lead to a loss of real-time
accuracy, especially when dealing with time-sensitive
tasks.
• Implementing throttling logic requires additional code
and complexity compared to simply calling the function
directly. Throttling may involve maintaining state,
tracking timestamps, and managing timers, which can
increase the cognitive load for developers and introduce
potential bugs.
Despite these potential drawbacks, throttling is a valuable
Design Pattern in JavaScript for managing function
invocation rates and optimizing performance, especially in
scenarios involving network requests or user interface
interactions.

Debouncing
Debouncing is a Design Pattern in JavaScript used to ensure
that a function is only executed after a certain period of
inactivity. Unlike throttling, which limits the frequency of
function calls within a specified time interval, debouncing
delays the execution of a function until there has been a
pause or cessation of activity for a predetermined amount of
time. This pattern is particularly useful in scenarios where
rapid or repetitive events, such as user input or event
listeners, may trigger function calls that need to be handled
in a more controlled manner. In the context of asynchronous
programming, debouncing helps manage the flow of
asynchronous tasks by postponing the execution of a
function until a quiet period occurs, thus reducing
unnecessary overhead and improving performance.

Problem statement
Due to the similarities between throttling and debouncing
and to help distinguish between them, we will use the same
example that we have used for throttling. In this scenario,
we are implementing a search feature in a web application.
As users type into the search field, an API request is sent to
the server to fetch the results and update the UI
accordingly. However, we cannot accurately guess the
speed at which a user can type. For instance, if we have a
user that types very rapidly, it could lead to a flood of API
requests being sent to the server. This could potentially
overwhelm the server by causing unnecessary load. This will
not only impact on the performance of the application but
also put unnecessary strain on the server.

Implementation
To explain debouncing, we will continue with the same
example defined in the problem statement. Like the
throttling Design Pattern, the implementation of the
debounce pattern also involves three simple steps.
Essentially, we need a function that can regulate the flow of
the number of requests being sent to the server, preventing
it from becoming overwhelmed and crashing. In a more
generic sense, we need to implement a process that can
control the number of asynchronous requests.
Step 1: Define the debounce function
Much like the first step for the throttling Design Pattern, we
will create a function for implementing the debounce or the
function for handling the wait for invoking asynchronous
functions:
1. function debounce(func, delay) {
2. let timeoutId;
3.
4. return function(...args) {
5. clearTimeout(timeoutId); // Clear any existing timeout
6. timeoutId = setTimeout(() => {
7. func(...args); // Call the original function after the
delay
8. }, delay);
9. };
10. }
The above code shares several similarities with the one we
implemented for throttling. Like the throttling function, the
debounce function also accepts two arguments: an original
function that requires debouncing and a delay, which
represents the time interval (in milliseconds) to wait before
invoking the function after the last call. Similar to the
throttling function, we also have a closure function defined
here. However, unlike the throttling function, the private
variable signifies a timeout ID for handling the setTimeout
method that we will implement for our use case.
Inside the returned function (closure), timeoutId is used to
store the ID of the setTimeout timer. Whenever the
returned function is called, any existing timeout is cleared
using clearTimeout(timeoutId). A new timeout is set
using setTimeout, delaying the execution of func by delay
milliseconds.
Step 2: Using the debounce function for implementing
debounce
In this step, we will use the debounce function that we have
created in the previous step on a function that requires
debouncing. In our case, it will be the function that will
make the network request. Let us have a look at the code to
visualize that:
1. // Function to simulate making a network request
2. function makeNetworkRequest(data) {
3. [Link](`Making network request with data:
${data}`);
4. }
5.
6. // Create a debounced version of the network request
function with a delay of 1000 milliseconds (1 second)
7. const debouncedNetworkRequest =
debounce(makeNetworkRequest, 1000);
In the above code, we have created a function called
makeNetworkRequest that accepts argument data.
Although we are only printing a console statement in this
function for demonstration purposes, in a real-life scenario,
this function could contain the network API request that we
intend to make. This function will be triggered every time
the user enters a keyword to search. Therefore, we need to
ensure that this function is debounced.
Hence, we create a new variable called
debouncedNetworkRequest that invokes the debounce
function we created in the previous example. We pass the
makeNetworkRequest function and a time of 1 second
(1000 milliseconds) as arguments.
Step 3: Use the debounced function to invoke async
code
For the final step, like the throttling pattern, we will use the
debounced function instead of the original function to make
a network request. This ensures that our network requests
have been debounced to avoid overwhelming the server:
1. // Event listener for search input
2. [Link]('searchInput').addEventListe
ner('input', function(event) {
3. const query = [Link];
4. debouncedNetworkRequest(query);
5. });
In the above code, we have demonstrated the actual
implementation of a user input that triggers the debounced
function. Like the old code, for demonstration purposes, we
have assumed our input element to have the id
searchInput. We add an event listener to the input event of
this element, ensuring that every time the user adds a value
to the input box, the function in the second argument will be
triggered. Inside that callback function, we call the
debouncedNetworkRequest method with the value of the
input element.
To help you understand how this will work, let us explore
another way of invoking this function:
1. // Example usage of the debounced function
2. debouncedNetworkRequest(); // This call will be ignored
because no subsequent call is made
3. debouncedNetworkRequest(); // This call will also be
ignored
4. // Wait for 500 milliseconds
5. setTimeout(() => {
6. debouncedNetworkRequest(); // This call will execute
myFunction after the debounce delay has elapsed
7. }, 500);
In the above example, we invoke the
debouncedNetworkRequest multiple times to observe the
debouncing behavior. The first two calls to
debouncedNetworkRequest are made in quick
succession. However, since the debounce delay has not
elapsed, both calls are ignored, and makeNetworkRequest
is not executed. After waiting for the debounce delay (in this
case, 1000 milliseconds), a third call to
debouncedNetworkRequest is made. This time, the
debounce delay has elapsed, so makeNetworkRequest is
executed.
By following these steps, you can implement debouncing in
your JavaScript code to ensure that a function is only
executed after a period of inactivity, thus optimizing
performance and resource utilization. Adjust the delay
parameter as needed to suit your specific requirements.

Applications
Debouncing, like throttling, is excellent for scenarios where
we need to optimize performance and manage resources.
Here few of the scenarios where debouncing can be
extremely useful:
Search autocomplete
In a search autocomplete feature, debouncing can be used
to delay sending AJAX requests for autocomplete
suggestions until the user has paused typing, reducing
unnecessary network traffic and server load.
Window resize or scroll events
Debouncing can be employed to handle window resize or
scroll events in a web application, ensuring that expensive
layout or rendering operations are only performed after the
user has finished resizing or scrolling, rather than on every
intermediate event.
Input field validation
When performing validation on user input fields, such as
email or password validation, debouncing can delay the
validation function until the user has finished typing,
providing a smoother and less intrusive validation
experience.
Button click or form validations
In scenarios where button clicks or form submissions
trigger complex or time-consuming operations, debouncing
can prevent multiple rapid clicks from firing multiple
requests or submissions, ensuring that only the last action is
executed.
Delayed event handling
Debouncing can be used to delay the execution of event
handlers, such as mouseover or mouse-out events, to
prevent rapid flickering or flashing effects caused by rapid
mouse movements.
Delaying API requests
When integrating with external APIs, debouncing can delay
the execution of API requests until the user has completed
their input or interaction, reduced the number of requests
and improving overall efficiency.

Pros and cons


Here are some of the advantages of using debouncing
patterns:
• Debouncing helps reduce the frequency of function
calls, especially in scenarios where rapid or repetitive
events occur. By postponing the execution of a function
until a quiet period occurs, debouncing ensures that the
function is only called once, even if multiple events are
triggered within a short timeframe.
• It can improve the performance of our application and
help us in avoiding potential bottlenecks or slowdowns.
• Debouncing helps prevent overloading servers or APIs
by controlling the rate at which requests are made.
However, there are also a set of disadvantages associated
with this Design Pattern:
• Debouncing introduces a delay between the occurrence
of an event and the execution of the associated function.
While this delay helps optimize performance and prevent
overloading, it can also result in a perceived delay in
responsiveness, especially in scenarios where real-time
updates are expected.
• In some cases, debouncing may lead to a loss of
immediate feedback or visual cues, particularly when
dealing with user interactions that require immediate
responses, such as typing or scrolling. Users may
perceive the delay introduced by debouncing as lag or
unresponsiveness, which can impact usability.
• Debouncing may introduce variability in the timing of
function execution, depending on the duration of the
debounce delay and the frequency of events.
Despite these potential drawbacks, debouncing is a valuable
Design Pattern in JavaScript for managing event-driven
interactions and optimizing performance.

Parallelism and concurrency


The second set of Design Patterns that can help us deal with
asynchronous programming includes parallelism and
concurrency. Parallelism, as the word suggests, enables the
execution of multiple tasks simultaneously, utilizing
techniques like Web Workers and parallel processing
libraries to leverage multi-core CPUs efficiently. This
pattern is particularly useful for CPU-intensive operations,
allowing tasks to run concurrently without blocking the
main thread.
On the other hand, concurrency focuses on handling
multiple tasks that may overlap in time, ensuring smooth
execution without waiting for one task to finish before
starting another. Concurrency Design Patterns, such as
Promises, async/await, and event-driven architecture,
enable developers to orchestrate asynchronous tasks
seamlessly, ensuring non-blocking execution and efficient
handling of I/O operations.
Both parallelism and concurrency are indispensable tools
for building responsive and scalable JavaScript applications
in various environments, from web browsers to server-side
platforms.

Parallelism
As mentioned at the start of this segment, parallelism is a
Design Pattern that deals with executing multiple tasks
simultaneously. This can be achieved either by utilizing
multiple CPU cores or by performing non-blocking
operations concurrently. In JavaScript, parallelism is
particularly useful for tasks that can be broken down into
independent units of work that can execute concurrently,
thereby improving performance and resource utilization.
There are several approaches that we can use for
implementing the parallelism Design Pattern. Among these
approaches, three patterns closely adhere to the JavaScript
way of development:
• Web Workers: Web Workers allow us to run JavaScript
in a background thread separate from the main
execution thread, enabling parallel execution of tasks
without blocking the UI.
• Parallel array methods: The JavaScript array methods
introduced in ES6, such as map, filter, and reduce, can
be parallelized using libraries like [Link]. These
libraries distribute the workload across multiple CPU
cores, enabling parallel execution of array operations.
• Parallel processing libraries: Several libraries and
frameworks are available in JavaScript for parallelizing
tasks and leveraging multiple CPU cores efficiently.
Of these three approaches, we will solely concentrate on
Web Workers in this book.

Web Workers
In the previous segment, we briefly introduced the concept
of parallelism and Web Workers. To gain a better
understanding of what Web Workers do and how parallelism
can help solve critical issues in asynchronous programming,
let us explore a coding example that highlights a potential
problem users might face when dealing with asynchronous
requests:
1. // Function to perform intensive calculations (e.g.,
factorial calculation)
2. function calculateFactorial(n) {
3. let result = 1;
4. for (let i = 1; i <= n; i++) {
5. result *= i;
6. }
7. return result;
8. }
9.
10. // Function to handle button click event
11. [Link]('calculateButton').addEvent
Listener('click', function() {
12. const input =
[Link]('numberInput').value;
13. const result = calculateFactorial(parseInt(input));
14. [Link]('result').innerText =
'Factorial: ' + result;
15. });
In this simple example depicted above, we have attached a
click event listener to a button with the id
calculateButton. Whenever the user clicks on this button,
the value stored inside an input element with the id
numberInput is fetched and stored in the variable input.
This variable is then passed to the calculateFactorial
function to calculate the factorial of the number provided by
the user. The result is then rendered on another element
with the id result.
On the surface, this function appears straightforward,
where the provided code calculates the factorial for the
number entered by the user. However, if the user inputs a
large number or if the factorial calculation becomes
sufficiently complex, this operation can consume a
considerable amount of time. Given JavaScript’s
synchronous nature, this could potentially block the UI
rendering, rendering it unresponsive for the user to interact
with. This issue becomes even more apparent when dealing
with more complex calculations or when attempting to
perform multiple tasks simultaneously.
Such computational problems that could potentially block
the rendering of UI can be resolved by implementing Web
Workers.
Implementation
Traditional JavaScript execution is single-threaded, meaning
tasks are processed sequentially on a single main thread. In
modern web applications, this can lead to performance
bottlenecks, especially when handling heavy computations
or input/output operations. Web Workers provide a solution
by running scripts in separate background threads, enabling
parallelism. This allows developers to offload
computationally intensive tasks, such as data processing,
image manipulation, or network requests, to background
threads, ensuring the main thread remains free for UI
updates. By leveraging Web Workers, developers can
improve performance and maintain a responsive user
interface, enhancing the overall user experience.
Let us utilize the example of calculating a factorial that we
have seen in the previous section to effectively demonstrate
how Web Workers can solve such a critical problem:
Step 1: Create a Web Worker script file
To start, we will create a separate JavaScript file to house
our Web Worker logic. Let us name this file [Link], and
it will contain the code responsible for performing the
intensive calculations:
1. // [Link]
2.
3. // Function to perform intensive calculations (e.g.,
factorial calculation)
4. function calculateFactorial(n) {
5. let result = 1;
6. for (let i = 1; i <= n; i++) {
7. result *= i;
8. }
9. return result;
10. }
11.
12. // Event listener for receiving messages from the main
thread
13. [Link]('message', function(event) {
14. const input = [Link];
15. const result = calculateFactorial(input);
16. // Send the result back to the main thread
17. [Link](result);
18. });
19.
In the provided code, a new file named [Link] has been
introduced. Within this file, the computationally intensive
function calculateFactorial has been moved. Additionally,
the JavaScript keyword self has been utilized to reference
the global scope of the worker, enabling it to interact with
its own context and handle message passing. An event
listener for the message event has been set up using
addEventListener, which triggers the associated callback
function when messages are received. This function
retrieves data from the event argument that will be passed
to it by the element which triggers the message event. It
then invokes the calculateFactorial method to compute
the factorial. Communication between the main thread and
the Web Worker primarily occurs through message passing,
with the Web Worker listening for messages and responding
using the postMessage method.
Step 2: Instantiate a Web Worker in the main thread
For the next step, we will work on the main JavaScript file,
titled [Link] in this example. In this file, we will create a
new Web Worker instance by specifying the path to the
worker script file ([Link]):
1. // [Link]
2.
3. // Create a new Web Worker instance
4. const worker = new Worker('[Link]');
As shown in the code above, we have created an instance of
the Web Worker by specifying the path to the [Link] file.
In this case, it is assumed that both the [Link] and
[Link] files are stored in the same folder.
Step 3: Send data to the Web Worker
In this step, we will continue working on the main thread,
i.e., [Link] file. We will utilize the native postMessage
method provided by the browser to send data from the main
thread to the Web Worker. This data will be received by the
Web Worker through the event listener set up to listen for
messages:
1. // [Link]
2.
3. // Send data to the Web Worker
4. const inputNumber = 10; // Example input
5. [Link](inputNumber);
For simplicity, we have used a constant variable to
represent the value that might have been received from a
user inputting a numerical value in an input element. We
then pass this number to the Web Worker by invoking the
postMessage method on the worker instance.
Step 4: Receive details from the Web Worker
In the final step, we add an event listener in the main thread
to handle messages received from the Web Worker. This
event listener will receive the results of the computation
from the Web Worker and utilize it in the main thread:
1. // [Link]
2.
3. // Receive results from the Web Worker
4. [Link]('message', function(event) {
5. const result = [Link];
6. [Link]('Result received from Web Worker:',
result);
7. // Update the UI or perform further actions with the
result
8. });
In the second step, we invoked [Link] on the
worker after the results were computed. This method sends
the result using postMessage. Now, we need to listen for
the message event in our main thread. Hence, in the code
above, we added an event listener to listen for the message
event triggered from the worker. The data received from the
postMessage call triggered from the worker will be
available in the event variable, which can then be used to
perform further actions on the UI.
At this stage, we have achieved two-way communication
between the worker thread and the main thread. By
delegating computationally expensive tasks to a Web
Worker, we can successfully achieve parallelism and ensure
a smoother execution of the UI. However, there are two
additional steps you can consider, adding to this approach
for improved efficiency and code organization.
Step 5: Terminate the Web Worker
Once the calculations are complete and the Web Worker is
no longer needed, it is crucial to terminate the worker
explicitly to free up resources and prevent memory leaks.
Although Web Workers are garbage-collected when no
longer referenced, relying on this behavior is not a best
practice, especially in applications with heavy resource
usage:
1. // Terminate the Web Worker (optional)
2. [Link]();
Explicit termination ensures efficient resource management
and avoids potential performance issues in long-running
applications.
Step 6: Handling errors (optional)
Error handling is critical for robust Web Worker usage. Any
unhandled exceptions in the Web Worker code can go
unnoticed if not explicitly captured. To catch and handle
these errors effectively, use an event listener for the error
event:
1. // Error handling for the Web Worker
2. [Link]('error', function(event) {
3. [Link]('Error in Web Worker:',
[Link]);
4. });
This ensures that unexpected issues, such as runtime errors
in the worker’s script, can be diagnosed and addressed,
maintaining the stability and reliability of your application.
With these steps, you have implemented Web Workers in
JavaScript for performing intensive calculations
asynchronously. The Web Worker script ([Link]) runs in
the background thread, allowing the main thread to remain
responsive. Communication between the main thread and
the Web Worker is achieved through message passing.

Applications
From the preceding sections, we have observed the
significant utility of Web Workers for scenarios demanding
intensive asynchronous computations. With this in mind, let
us explore several scenarios where Web Workers can
provide substantial benefits:
Intensive calculations
Any task that involves heavy computational processing, such
as mathematical calculations, image processing, or data
manipulation, can benefit from Web Workers. By offloading
these tasks to background threads, the main thread remains
responsive, providing a smoother user experience.
Large data processing
Processing large datasets, such as parsing and analysing
large JSON files, CSV files, or datasets retrieved from APIs,
can be resource intensive. Web Workers enable parallel
processing of data, improving efficiency and reducing the
time required to complete the operation.
Web-based games
Browser-based games often require complex logic and real-
time interaction. Web Workers allow game developers to
perform tasks like physics calculations, AI computations, or
pathfinding algorithms in separate threads, ensuring that
the game remains responsive and immersive.
Real-time data visualizations
Applications that involve real-time data visualization, such
as charts, graphs, or animations, often require continuous
updates to the UI. Web Workers can handle data processing
tasks in the background while the main thread focuses on
rendering the visualizations, ensuring smooth and
responsive updates.

Pros and cons


Here are some of the advantages of using Web Workers as a
parallelism Design Pattern:
• Web Workers allow intensive tasks to be offloaded from
the main thread, preventing blocking and ensuring a
smoother user experience. This can lead to faster
processing times and better performance, especially for
computationally heavy tasks.
• By running tasks in background threads, Web Workers
keep the main thread free to handle user interactions
and update the UI. This results in a more responsive and
interactive user interface, enhancing overall usability.
• Web Workers promote modular code design by
encapsulating logic in separate threads. This improves
code organization, readability, and maintainability,
making it easier to manage complex applications.
• Web Workers are supported by all major modern
browsers which makes them a reliable solution for
achieving parallelism.
However, there are also a set of disadvantages associated
with this Design Pattern:
• Although Web Workers are widely supported, there are
still some older browsers and mobile devices that may
not fully support them. Developers may need to provide
fallbacks or alternative solutions for users on
unsupported platform.
• Communication between the main thread and Web
Workers occurs through message passing, which
introduces some overhead.
• Web Workers operate in isolated environments and
cannot directly access the DOM or shared variables with
the main thread. While this promotes thread safety, it
also adds complexity when sharing data between
threads, requiring explicit message passing and
synchronization mechanisms.
• Creating multiple Web Worker instances can consume
additional memory, especially if each worker requires its
own copy of large datasets or dependencies.
Despite these challenges, Web Workers remain a powerful
tool for introducing parallelism and improving performance
in web applications, particularly for tasks that benefit from
background processing and concurrent execution.

Concurrency
As the word itself states, concurrency from a JavaScript
Design Pattern perspective means enabling the execution of
multiple tasks simultaneously, making efficient use of the
available resources. In the context of asynchronous
JavaScript, concurrency allows different parts of a program
to execute independently without blocking the execution of
other tasks.

Concurrency vs parallelism
In the previous segment, we briefly introduced the concept
of parallelism and Web Workers. To gain a better
understanding from afar, it may seem that concurrency is
the same as parallelism. However, there is a notable
difference between the two.
As we have seen previously, parallelism involves the
simultaneous execution of multiple tasks or processes,
where each task runs independently and potentially on
different physical or logical processors. In an execution
sense, parallel tasks run concurrently but have the
capability to execute simultaneously without sharing
resources or interfering with each other.
Concurrency, in contrast, refers to the system’s capability to
execute multiple tasks or processes, which may potentially
overlap in time. While the formal definition of concurrency
may appear similar to that of parallelism, the crucial
distinction lies in execution. Whereas parallel tasks, in
implementing parallelism, do not share resources and do
not interfere with each other, concurrent tasks may not run
simultaneously. In fact, they may share resources and take
turns in execution, interleaving their operations.
In summary, while concurrency can be achieved even on a
single processor system through interleaved execution,
parallelism typically requires multiple execution units
working simultaneously.

Approaches
We have already explored the approaches needed to
implement concurrency in detail earlier in this chapter.
However, for the sake of continuity, let us provide a brief
summary of these approaches:
Event Loop
JavaScript is inherently single-threaded, meaning it can
execute only one piece of code at a time. However, with
asynchronous operations, tasks can be scheduled to run in
the background while the main thread continues executing
other tasks. This is managed by the Event Loop, which
continuously checks for tasks in the event queue and
executes them when the main thread is free.
Callbacks
One of the traditional ways to achieve concurrency in
JavaScript is through callbacks. Asynchronous functions
accept callback functions as arguments, which are invoked
once the operation is completed. While waiting for the
asynchronous operation to finish, other tasks can continue
executing.
Promises
Promises provide a more structured way to handle
asynchronous operations and concurrency compared to
callbacks. They represent the eventual completion or failure
of an asynchronous operation and allow chaining of
asynchronous tasks. Promises can be executed concurrently
by initiating multiple asynchronous operations, and their
resolutions are handled independently.
Async/await
Introduced in ES2017, async/await is a syntactic sugar built
on top of Promises, offering a more intuitive way to write
asynchronous code. The async keyword defines a function
that returns a Promise, and the await keyword pauses the
execution of the async function until the Promise is
resolved. With async/await, concurrency is achieved by
awaiting multiple Promises concurrently, allowing for
cleaner and more readable code.
Concurrency in JavaScript is crucial for building responsive
and efficient applications, especially for tasks like making
network requests, handling user input, and performing I/O
operations without blocking the main thread. However, it is
essential to manage concurrency carefully to avoid issues
like race conditions, deadlocks, and excessive resource
consumption.

Batch processing
Batch processing is a crucial Design Pattern in JavaScript,
particularly in scenarios where handling numerous
asynchronous tasks efficiently is paramount. This pattern
involves grouping multiple operations or data elements into
batches, which are then processed either sequentially or
concurrently. By consolidating tasks into batches,
JavaScript applications can optimize resource utilization,
improve performance, and streamline the management of
asynchronous operations. This section explores the
fundamentals of batch processing as a Design Pattern in
JavaScript, delving into its benefits, implementation
strategies, and best practices for leveraging its power in
various application scenarios.

Problem statement
In modern JavaScript applications, developers frequently
encounter scenarios where multiple complex operations
need to be performed asynchronously. One common
scenario involves making successive API requests in a web
application. Without batch processing, each API request
would be sent individually, potentially leading to issues such
as network latency, increased server load, and slower
overall response times. Let us examine a sample of such
problematic code:
1. // Problematic code without batch processing
2. async function fetchDataFromAPI(urls) {
3. let results = [];
4. for (let url of urls) {
5. try {
6. let response = await fetch(url);
7. let data = await [Link]();
8. [Link](data);
9. } catch (error) {
10. [Link](`Error fetching data from ${url}:
${error}`);
11. }
12. }
13. return results;
14. }
15.
16. let urls = ['[Link]
'[Link]
'[Link]
17.
18. fetchDataFromAPI(urls)
19. .then(results => {
20. [Link](results);
21. })
22. .catch(error => {
23. [Link](error);
24. });
In the above code, the fetchDataFromAPI function iterates
over an array of URLs and makes individual HTTP requests
using the fetch API. Each request is processed sequentially,
waiting for the response before initiating the next request.
This approach can lead to increased latency and decreased
efficiency, especially when making multiple requests to the
same server or endpoint.

Implementation
Let us implement the batch processing method in the code
example we saw earlier to understand how this Design
Pattern can simplify the critical problem that might arise in
daily JavaScript programming.

Step 1: Define the batch processing function


The very first step that we need to do is to create a function
that will process batches of task or operations. The function
should ideally take two arguments – an array of items to
process and a batch size indicating how many items to
process in a batch. Within this function, we will iterate over
the input array in batches, perform the operations, and
collect the results:
1. async function processBatch(urls, batchSize) {
2. let results = [];
3. for (let i = 0; i < [Link]; i += batchSize) {
4. let batchUrls = [Link](i, i + batchSize);
5. let batchResults = await
[Link]([Link](url => fetchData(url)));
6. [Link](...batchResults);
7. }
8. return results;
9. }
In the provided code, we have defined the function
processBatch for implementing batch processing. This
function takes two arguments: a list of URLs stored in the
argument urls and a variable batchSize that denotes the
size of each batch. Within the function, we initialize an
empty array called results to store the results from the
network requests. We then iterate through the list of URLs
using a for loop, incrementing by the batchSize value.
Inside the loop, we slice the URLs array based on the
batchSize, creating batches of URLs. For example, if the
batchSize is 3 and we have a list of 12 URLs, there will be
4 batches of 3 URLs each.
We use [Link] to asynchronously fetch responses for
all the URLs in the current batch, storing them in the
variable batchResults. This variable contains only the
responses for the current batch. The batchResults are then
spread to ensure a cloned copy and pushed into the results
array. Once the entire operation is complete, the results
array is returned.
Step 2: Implement the individual async task function
The next step now is to create the actual asynchronous task
function that needs to be performed for every item in a
batch. This function should handle individual asynchronous
tasks, such as making API requests, reading files, or
performing computations. It should return a Promise that
resolves with the result of the operation.
1. async function fetchData(url) {
2. try {
3. let response = await fetch(url);
4. return await [Link]();
5. } catch (error) {
6. [Link](`Error fetching data from ${url}:
${error}`);
7. return null;
8. }
9. }
In the first step, we had defined a variable called
batchResults that would take a batch of URLs and pass
every item inside the batch to an asynchronous function. In
this step, we define an asynchronous function called
fetchData that accepts a URL as an argument. The function
utilizes the async/await syntax to handle asynchronous
operations in a synchronous-like manner.
Within the function, a try-catch block is implemented to
handle potential errors during the network request. The
Fetch API is used to initiate the network request
asynchronously, and the await keyword is employed to wait
for the response.
If the network request is successful, the response is
returned. If an error occurs during the request, it is caught
in the catch block. In this case, an error message is printed
to the console, and null is returned to indicate a failed
request.

Step 3: Define the input data


Now that we have created the function that will handle the
batch processing, let us define the input data. In this step,
we will prepare the input data that needs to be processed in
batches. This could be an array of URLs, file paths, database
records, or any other data that requires batch processing.
The only thing that we need to ensure is that the data is
structured in such a way that it can be easily partitioned
into batches.
1. let urls = [
2. '[Link]
3. '[Link]
4. '[Link]
5. // Add more URLs as needed
6. ];
In the current example, we have defined a list of sample
URLs representing APIs from which data needs to be
fetched. These URLs are stored in a variable called urls,
which will be passed to a batch processing function for data
retrieval.

Step 4: Execute batch processing


Now that we have all the necessary components for
implementing batch processing, let us invoke the function
responsible for processing complex logic using this Design
Pattern. We will call the batch processing function we
created and pass the input data along with the desired
batch size. This step initiates the processing of the input
data in batches, asynchronously handling each batch of
tasks according to the defined logic. Once all batches are
processed, we can access the results or perform any
necessary post-processing steps:
1. const batchSize = 2; // Specify the batch size
2. processBatch(urls, batchSize)
3. .then(results => {
4. [Link](results);
5. })
6. .catch(error => {
7. [Link](error);
8. });
In the provided code, we first define a variable called
batchSize to specify the desired batch size for processing
the asynchronous code, i.e., fetching data from the URLs in
batches. Next, we invoke the processBatch function,
passing the urls variable created in step 3 along with the
batchSize variable. Since this function returns a Promise,
we can use the then and catch methods to handle its
resolution or rejection. If the fetch API calls are successful,
the then block will execute and print the results. If there
are any network failures, they will be caught in the catch
block.
By following these steps, we can implement batch
processing in a generic manner to efficiently handle large
volumes of tasks or operations in JavaScript. This Design
Pattern is versatile and applicable to various scenarios
where processing tasks in batches can improve performance
and resource utilization.

Applications
Batch processing as a Design Pattern can be useful in
various scenarios in JavaScript, especially when dealing
with asynchronous operations, large datasets, or resource
intensive tasks. Here is a list of scenarios where batch
processing can be beneficial:

API requests
As demonstrated in the example from the previous section,
batch processing can be effectively employed to handle API
requests. By grouping multiple requests and processing
them in batches, we can significantly reduce network
latency and enhance overall performance, especially when
making multiple requests to the same server or across
multiple servers.

Database operations
Batch processing can be used for inserting, updating, or
deleting multiple records in a database, optimizing database
transactions and reducing overhead.

Image processing
For tasks like resizing, cropping, or applying filters to
multiple images, batch processing can streamline the image
processing workflow and improve processing time.

Data migration
During data migration processes, batch processing can be
used to transfer data in manageable chunks, minimizing
downtime and ensuring data integrity.

Report generation
For generating reports from large datasets or processing
data for analytics purposes, batch processing can optimize
the generation process and improve responsiveness.

Job queues
Batch processing can be integrated with job queues to
process queued tasks in batches, improving throughput and
reducing processing overhead.
By leveraging batch processing in these scenarios,
JavaScript applications can achieve better performance,
scalability, and resource efficiency, ultimately enhancing
the user experience and optimizing system operations.

Pros and cons


Here are some of the advantages of using batch processing
as a Design Pattern:
• Performance optimization: Batch processing can
significantly improve performance by reducing overhead
associated with initiating and managing individual tasks.
Processing tasks in batches can utilize resources more
efficiently and minimize latency.
• Resource efficiency: By processing tasks in batches,
applications can optimize resource usage, including
CPU, memory, and network bandwidth. This can lead to
better scalability and lower infrastructure costs.
• Reduced network overhead: When making multiple
network requests, batch processing can reduce network
overhead by consolidating multiple requests into fewer
transactions. This can result in faster response times
and reduced load on servers.
• Simplified management: Managing tasks in batches
can simplify task coordination, error handling, and
monitoring. It allows for better control over the
execution flow and facilitates easier tracking of progress
and completion.
• Optimized I/O operations: In scenarios involving file
processing, database operations, or disk I/O, batch
processing can optimize I/O operations by reducing the
frequency of read/write operations and maximizing data
locality.
However, there are also a set of disadvantages associated
with this Design Pattern:
• Complexity in implementation: Implementing batch
processing requires careful design and coordination,
especially when dealing with dependencies between
tasks, error handling, and ensuring consistency across
batches. This complexity can increase development time
and maintenance overhead.
• Potential for resource saturation: Processing large
batches of tasks simultaneously can lead to resource
saturation, such as high CPU utilization, memory
consumption, or contention for shared resources. Proper
resource management and throttling mechanisms may
be necessary to mitigate these risks.
• Limited real-time responsiveness: Batch processing
is inherently batch-oriented, which means it may not be
suitable for scenarios requiring real-time or near-real-
time responsiveness. Tasks may need to wait for the next
batch cycle to be processed, leading to potential delays
in processing. In some applications, batch processing
may introduce data staleness issues, especially when
processing stale or outdated data in batches. This can
impact the freshness and accuracy of results.
• Difficulty in handling dynamic workloads: Batch
processing may struggle to adapt to dynamically
changing workloads or fluctuating demand. Scaling up
or down based on workload changes may require
additional infrastructure or dynamic adjustment of batch
sizes.
While batch processing offers numerous benefits for
optimizing performance and resource utilization, it is
essential to consider its limitations and trade-offs when
choosing this approach for a particular use case.

Promise optimization
Earlier in the chapter, we have read about Promises while
understanding the history of asynchronous programming in
JavaScript. Later, we also came across it while
understanding concurrency as a Design Pattern. Promises,
in itself, are objects used to represent the eventual
completion or failure of an asynchronous operation. Promise
optimization focuses on strategies to enhance the efficiency
and effectiveness of using Promises in JavaScript
applications.
While Promise optimization is not typically referred to as a
Design Pattern in the same sense as, say, the Singleton or
Observer Patterns, it encompasses a set of best practices
and techniques for effectively using Promises in
asynchronous JavaScript programming.
So, let us look at several approaches for Promise
optimization:

Avoiding nested Promises


Much like the callback hell that we had seen earlier in the
chapter, irresponsible usage of Promises can lead to
something like Promise hell. One of the main goals of
Promise optimization is to prevent the nesting of Promises.
Instead, it is recommended to chain Promises using
methods like .then() and .catch() or utilize async/await
syntax for cleaner and more manageable code.
1. // Bad approach with nested Promises
2. asyncFunction1()
3. .then(result1 => {
4. return asyncFunction2(result1)
5. .then(result2 => {
6. return asyncFunction3(result2);
7. });
8. })
9. .catch(error => {
10. [Link]('Error:', error);
11. });
12.
13. // Improved approach with Promise chaining
14. asyncFunction1()
15. .then(result1 => asyncFunction2(result1))
16. .then(result2 => asyncFunction3(result2))
17. .catch(error => {
18. [Link]('Error:', error);
19. });
In the provided code example, two contrasting approaches
are demonstrated. The first part illustrates a less favorable
approach that relies on nested Promises, leading to a
potentially convoluted structure akin to callback hell, often
referred to as Promise hell. This occurs when there is a
sequential dependency among asynchronous requests,
resulting in a nested arrangement of Promises. However, by
strategically organizing Promises and utilizing methods like
.then() and .catch() effectively, we can circumvent
Promise hell and maintain a cleaner and more manageable
code structure.

Promise chaining
Promise chaining involves linking multiple asynchronous
operations together sequentially, making the code more
linear and easier to understand. This can be achieved by
returning a Promise from within the .then() method
callback or using async/await syntax:
1. // Chaining Promises using async/await
2. async function sequentialAsyncOperations() {
3. try {
4. const result1 = await asyncFunction1();
5. const result2 = await asyncFunction2(result1);
6. const result3 = await asyncFunction3(result2);
7. return result3;
8. } catch (error) {
9. [Link]('Error:', error);
10. }
11. }
12.
13. sequentialAsyncOperations();
In the provided code example, we showcase Promise
chaining using async/await. Three variables, namely
result1, result2, and result3, are created to demonstrate
sequential dependency. Using async/await ensures that
each asynchronous function executes only after the
completion of the preceding request, thereby establishing a
clear Promise dependency structure.

Error handling
Proper error handling is crucial for robust asynchronous
code. Promise optimization emphasizes the use of .catch()
blocks to handle errors gracefully and prevent uncaught
exceptions from crashing the application. Let us see this in
code example:
1. // Error handling with catch block
2. asyncFunction()
3. .then(result => {
4. // Handle result
5. })
6. .catch(error => {
7. [Link]('Error:', error);
8. });

Parallel Promises
In some scenarios, it is beneficial to execute asynchronous
operations in parallel to improve performance. Promise
optimization involves techniques like [Link]() or
[Link]() to execute multiple Promises
concurrently and handle their results collectively:
1. // Parallel Promises using [Link]()
2. [Link]([asyncFunction1(), asyncFunction2(),
asyncFunction3()])
3. .then(results => {
4. // Handle results array
5. })
6. .catch(error => {
7. [Link]('Error:', error);
8. });
The code snippet above demonstrates the usage of the
[Link]() method. This method is static and accepts an
iterable of Promises as input, returning a single Promise.
When all input Promises are fulfilled (including an empty
iterable), the returned Promise is fulfilled with an array
containing the fulfilment values of each input Promise.
Conversely, if any of the input Promises are rejected, the
returned Promise rejects with the reason of the first
rejection encountered. The [Link]() method
functions similarly to [Link](), but with a crucial
distinction. It resolves when all Promises in the iterable
argument have settled, regardless of whether they were
fulfilled or rejected. Unlike [Link](), which rejects
immediately if any Promise in the iterable rejects,
[Link]() waits for all Promises to settle. It
returns a single Promise that resolves to an array of objects,
with each object representing the outcome of each Promise.
These objects contain a status property indicating whether
the Promise was fulfilled or rejected, along with a value or
reason property containing the fulfilment value or rejection
reason, respectively.
Promise reuse
Promises are reusable entities, and optimizing their usage
involves maximizing their reusability to avoid unnecessary
instantiation. This can be achieved by defining Promise-
producing functions that return the same Promise instance
for repeated usage:
1. // Reusing the same Promise instance
2. const sharedPromise = asyncFunction();
3.
4. [Link](result => {
5. // Handle result
6. });
7.
8. [Link](error => {
9. [Link]('Error:', error);
10. });
The provided code example illustrates the reusability of
Promises. We define a variable named sharedPromise,
which contains an asynchronous function returning a
Promise. It is worth noting that it is not mandatory to
immediately chain a .then() and a .catch() method after
the definition. Instead, we can reuse the Promise at any
point in the code following its declaration.

Memory management
Since Promises are objects, memory management is an
essential consideration in Promise optimization. Avoiding
unnecessary creation of Promises and ensuring proper
disposal of unused Promises can help prevent memory leaks
and optimize memory usage:
1. // Ensure proper disposal of Promises
2. let Promise = asyncFunction();
3.
4. // After using the Promise, set it to null or undefined
5. Promise = null;
In the provided code example, we clear the Promise by
setting it to null after we have successfully utilized it. This
practice ensures that the Promise is no longer referenced
and can be garbage collected, potentially freeing up
memory resources.

Performance considerations
Promise optimization also involves considerations for
performance, such as minimizing unnecessary asynchronous
operations, optimizing asynchronous code for execution
speed, and reducing the overall latency of asynchronous
workflows, as shown in the code example:
1. // Minimize unnecessary asynchronous operations
2. const data = fetchData(); // Synchronous operation
3.
4. // Optimize asynchronous code for execution speed
5. asyncFunction()
6. .then(result => {
7. // Handle result
8. })
9. .catch(error => {
10. [Link]('Error:', error);
11. });
By following these steps and techniques, you can effectively
implement Promise optimization in your JavaScript code,
resulting in cleaner, more efficient, and maintainable
asynchronous workflows.

Conclusion
In this chapter, we have learnt about and explored Design
Patterns that are concerned with asynchronous
programming in JavaScript. We started with its historical
context and the challenges faced by developers working
with synchronous code. We explored various approaches to
tackle asynchronicity, including callbacks, Promises,
async/await, generators, and Web APIs. Understanding how
events are handled in JavaScript, we delved into the Event
Loop mechanism, which ensures smooth asynchronous
execution by continuously checking for tasks in the event
queue.
Moving on to Design Patterns for asynchronous JavaScript,
we discussed throttling and debouncing, both aimed at
controlling the rate of function execution. Throttling
regulates the frequency of function calls, while debouncing
ensures a function is executed only after a period of
inactivity. Exploring parallelism and concurrency, we
differentiated between the two concepts. Parallelism
facilitates the simultaneous execution of multiple tasks,
leveraging Web Workers and parallel processing libraries
for optimal resource utilization. Concurrency, on the other
hand, allows tasks to overlap in time, managing
independent execution without blocking other operations.
We examined batch processing as a Design Pattern for
handling multiple asynchronous operations efficiently,
breaking down complex tasks into manageable batches.
Finally, we explored Promise optimization techniques, such
as [Link]() and [Link](), to streamline
asynchronous code execution and improve performance. By
mastering these Design Patterns, developers can effectively
manage asynchronous programming challenges, optimize
performance, and build robust and responsive JavaScript
applications. In the next chapter, we will be looking how to
integrate JavaScript Design Patterns in modern JavaScript
frameworks like React and Vue.
Points to remember
• Asynchronous JavaScript programming emerged as a
solution to facilitate the smooth execution of modern
JavaScript applications. Initially, callbacks were used,
but developers encountered the issue of nested
callbacks, commonly referred to as callback hell. To
address this challenge, JavaScript introduced Promises,
offering a more streamlined approach to handling
asynchronous requests.
• A further improvement called async/await was
introduced in ES2017, which significantly simplified the
handling of Promises. This addition made asynchronous
code appear more synchronous, enhancing readability
and maintainability.
• JavaScript also introduced generators that provide a
powerful way to work with sequences of data that can be
paused and resumed, and Web APIs that provide a way
for web developers to interact with various web services
and functionalities, such as fetching data from servers,
manipulating the DOM, handling user input, and more.
• Event Loop ensures smooth asynchronous execution by
continuously checking for tasks in the event queue and
then dynamically shifting it to the main thread.
• Throttling controls the frequency of function calls by
restricting the number of function calls made within a
specific time frame. Conversely, debouncing, while
similar to throttling, ensures that a function is executed
only after a period of inactivity, effectively delaying its
invocation until a pause in activity occurs.
• Parallelism facilitates the simultaneous execution of
multiple tasks, leveraging Web Workers and parallel
processing libraries for optimal resource utilization.
• Concurrency, on the other hand, allows tasks to overlap
in time, managing independent execution without
blocking other operations. Promises, Event Loop,
callbacks, and async/await are examples of concurrency.
• Batch Processing is a technique for handling multiple
asynchronous operations efficiently, breaking down
complex tasks into manageable batches.
• Promises, when optimized, can significantly enhance
performance. Best practices include avoiding nested
Promises, effectively chaining Promises, handling errors
gracefully, implementing parallel Promises, reusing
Promises, and considering memory and performance
implications.

Exercises
1. Which of the following statements is true?
a. Handling network requests using the traditional
synchronous JavaScript does not block the UI.
b. Debouncing is the process of restricting the number of
function calls made within a specific time frame.
c. Web Workers work on a separate thread from the main
thread, thereby reducing the chances of UI being
overwhelmed for computationally expensive processes.
d. Promises cannot be reused.
2. Which of the following approaches cannot be used
for optimizing Promises?
a. Reusing the Promises by assigning it to a variable.
b. Nesting Promises when they sequential and dependent
on each other.
c. Using [Link]() and [Link]() when
there are multiple requests.
d. Effectively using the .catch() to handle errors in
Promises.
3. What is the name of the Design Pattern that a
function is executed only after a period of
inactivity?
a. Debouncing
b. Batch processing
c. Throttling
d. Concurrency

Answers
1. c
2. b
3. a
CHAPTER 6
Design Patterns in React

Introduction
Modern web applications primarily rely on cutting-edge
JavaScript frameworks such as React and Vue. These
frameworks are built on component-based architecture,
emphasizing modularity and code reusability. In this
chapter, we will explore how established Design Patterns
can be effectively utilized within React to improve
application structure, maintainability, and scalability. We
will examine both the built-in patterns inherent to React
component system, along with popular Design Patterns
commonly adopted by developers to tackle specific
challenges within React. By grasping and implementing
these design principles, we can develop well-organized,
efficient, and easier-to-maintain JavaScript applications.

Structure
This chapter will cover the following topics:
• Overview of React
• Built-in patterns in React
• Popular Design Patterns specific for React
• Using JavaScript Design Patterns with React

Objectives
Upon finishing this chapter, we will be one step closer to
crafting efficient modern JavaScript applications. By the end
of it, we will delve into the optimal utilization of the built-in
patterns within a modern JavaScript framework like React.
Furthermore, we will learn how to integrate the Design
Patterns we have acquired thus far to complement these
built-in patterns. Consequently, we will be equipped to
develop efficient enterprise-level JavaScript applications
that are robust, streamlined, and simpler to maintain.

Overview of React
As explored in previous chapters, JavaScript has been a
cornerstone of web development since the mid-1990s. While
it has evolved significantly over time, there is always room
for improvement. Around 2010, Google shook up the
development community with the release of AngularJS, an
open-source JavaScript-based web framework. AngularJS
offered numerous advantages over existing libraries,
providing a comprehensive solution for UI development, and
easing the burden on developers. However, its steep
learning curve deterred some from adopting it for their
projects.
Three years later, Facebook (now Meta) introduced React,
followed closely by Vue, a library created by Evan You.
These two libraries quickly revolutionized web development
with their user-friendly interfaces, minimal learning curves,
efficient code development, and speedy UI rendering. As of
April 2024, ReactJS boasts an estimated 20.8 million
projects, highlighting the widespread adoption and
effectiveness of these libraries.
In this segment, we will briefly discuss React, first of the
two preferred libraries of many top product companies for
application development. We will explore what makes it so
popular and the advantages it offers over traditional
JavaScript applications.

Challenges faced in traditional JavaScript


Before we revisit the front-end libraries that revolutionized
the web development industry, let us first explore the
challenges faced by the community prior to their arrival.
Here are some of the major issues encountered by the front-
end community:

Overreliance on DOM manipulation


Traditional JavaScript relies on direct manipulation of the
DOM to update the UI. This approach can become
cumbersome and prone to errors, especially in large
interactive applications. Such practices often result in
spaghetti code a term used to describe unstructured and
hard-to-maintain code.

Code reusability
Before the advent of front-end libraries, reusing UI
components involved complex inheritance patterns or
writing the same code repeatedly. This made codebases
bulky and updates tedious.

Scalability
As applications grew, managing state and keeping the UI
consistent became a significant challenge. Traditional
JavaScript lacked a structured approach for handling
complex UIs.
These are just a few of the major challenges developers
encountered while coding in traditional JavaScript.
React
React was conceived by Jordan Walke, an engineer at Meta
(formerly Facebook), in 2011. Initially deployed on
Facebook News Feed, React’s demonstration proved highly
effective, prompting its integration into Instagram in 2012.
Recognizing its immense potential, Facebook officially open-
sourced React at JSConf in the USA in 2013. This move
enabled the developer community to embrace React, fueling
its widespread adoption and rapid growth. In this segment,
we will briefly examine the challenges encountered by the
development community prior to the advent of React and
the solutions it brings to the table.
React addressed these issues by introducing a component-
based architecture and a virtual DOM. Firstly, React adopts
a component-based approach, breaking down the UI into
reusable components. Each component manages its own
state and logic, preventing cascading changes that affect
the entire codebase. This approach fosters modularity,
maintainability, and facilitates code reuse.
Furthermore, React incorporates the concept of a virtual
DOM, which serves as a virtual representation of the actual
DOM. When changes occur, React employs a sophisticated
diffing algorithm in conjunction with the virtual DOM. This
algorithm determines the minimal set of changes needed on
the UI and updates only those elements, avoiding the need
to refresh the entire page or restructure the entire DOM.
This approach greatly enhances performance, especially for
complex UIs.
Lastly, React encourages the adoption of JavaScript XML
(JSX), an optional syntax extension that permits writing
HTML-like structures within JavaScript code. This enhances
readability and simplifies UI development, making it more
intuitive.
The simplicity and effectiveness of React along with its
compatriot, Vue, resonated with the web development
community at large. Despite their distinct approaches to
problem solving, both resulted in faster and more efficient
UI rendering, improved code structure, and code that is
easier to read and maintain. This proved to be a significant
advantage for enterprise-level applications, leading to swift
adoption by developers.
As of 2024, a majority of the world’s top web applications
are built on these two front-end libraries, underscoring their
widespread acceptance and impact on the industry.

Built-in patterns in React


React does not strictly enforce Design Patterns, but its core
features naturally align with certain approaches, leading to
established patterns for building React applications. These
patterns prioritize reusability, maintainability, and efficient
code. Below are some common built-in Design Patterns in
React:

Components, state, and props


Component-based architecture is the most fundamental
feature of React. The logic is simple: complex UIs are
broken down into smaller, reusable components. Each of
these components manages its own state and renders the UI
based on its state. Components, states, and props are
deeply interconnected. Understanding one without
considering the others is futile. In this segment, we will
begin by refreshing our understanding of components
before delving deeper into comprehending its primary
constituents: state and props. For instance, let us take a
look at BPB Publication Instagram page to check how we
can divide it into atomic components:
Figure 6.1: A typical React website can be divided into several components
As depicted in Figure 6.1, a full web page resembling the
Instagram layout of BPB Publications can be constructed
using React by segmenting each small section of the web
page into individual components. For example, the row of
circular elements showcasing story highlights saved by BPB
Publications can be divided into distinct components for
stories, which can then be reused to generate multiple story
highlights. Likewise, each post can individually utilize the
PostComponent, facilitating the creation of multiple posts
uploaded to the company’s Instagram profile.
React provides two methods for defining a component:
functional components (using functions) and class-based
components (using classes). The primary distinction lies in
the usage of state, a property exclusively accessible to and
used by the individual component. Class-based components
manage internal state through the state property available
exclusively to class, whereas functional components are
simpler and stateless.
To reinforce our understanding of these two component
distinctions, we will recreate two components from the
figure above. We will employ the class-based component
style for one and the functional style for the other.

Class-based components
Based on Figure 6.1, let us begin by examining the class-
based approach to writing components:
1. import React, { Component } from 'react';
2.
3. class Post extends Component {
4. constructor(props) {
5. super(props);
6. [Link] = {
7. isLiked: false, // Initial like state
8. };
9. }
10.
11. handleLike = () => {
12. [Link](prevState => ({
13. isLiked: ![Link]
14. }));
15. }
16.
17. render() {
18. const { profilePic, handle, caption, content } =
[Link];
19. const likeIcon = [Link] ? '[Link]' :
'[Link]';
20.
21. return (
22. <div className="post">
23. <div className="post-header">
24. <img src={profilePic} alt="Profile Pic"
className="profile-pic" />
25. <span className="handle">{handle}</span>
26. </div>
27. <div className="post-content">
28. <p className="caption">{caption}</p>
29. {content && <img src={content} alt="Post
Content" className="post-image" />}
30. <button className="like-button" > {[Link]}>
31. <img src={likeIcon} alt="Like Button" />
32. </button>
33. </div>
34. </div>
35. );
36. }
37. }
38.
39. export default Post;
In the example above, we have employed the class-based
approach to create the Post component. As depicted in the
code, we have defined a class named Post that extends
React’s Component class. Similar to a standard JavaScript
class, we have implemented a constructor and two methods.
Notably, the render method is crucial, as React utilizes it to
render the UI. Within this method, we initially declare
several variables received as props (we will delve into this
further in the next section). Subsequently, we compose
HTML using the JSX syntax, seamlessly intertwining
JavaScript logic with native HTML. This JSX, disguised as
HTML within the render method of the Post class, is
utilized to render the UI showcased in Figure 6.1.
As previously mentioned, the key distinction between a
class-based component and a functional component in React
is the utilization of state. In the example provided, we have
established a state object within the props. Within this state,
we have defined a Boolean variable called isLiked to
determine if the current post has been liked by the user. As
observed later in the code, we utilize this state variable to
determine the icon displayed for the like button.
Additionally, we have implemented a handler function on
the like button named handleLike. This function simply
modifies the value of the state variable isLiked using
React’s setState method. According to React’s state
definition, whenever the value of a state variable changes—
essentially, whenever the internal state of a component
changes—it is prudent to re-render that component to
reflect the latest alterations. Consequently, the Post
component is re-rendered each time a user clicks the like
button, ensuring the incorporation of the updated image
defined within the render method.
This approach leverages classes from JavaScript ES6 to
construct React components, with state serving as a central
focus. It proves particularly beneficial for components
requiring intricate rendering logic.

Functional components
Next, we will delve into the method of writing components
using functions:
1. import React, { useState } from 'react';
2.
3. const Story = ({ profilePic, handle }) => {
4. const [isViewing, setIsViewing] = useState(false); //
Initial viewing state
5.
6. const handleView = () => {
7. setIsViewing(prevIsViewing => !prevIsViewing);
8. };
9.
10. return (
11. <div className="story">
12. <img
13. src={profilePic}
14. alt="Profile Pic"
15. className={isViewing ? 'story-profile-pic-active' :
'story-profile-pic'}
16. > 17. />
18. {isViewing && (
19. <div className="story-content">
20. <span className="story-handle">{handle}
</span>
21. </div>
22. )}
23. </div>
24. );
25. };
26.
27. export default Story;
In the provided code, we have crafted the Story component
using the functional approach to writing components in
React. Unlike classes, this method employs simpler
JavaScript functions. Here, we emulate the behavior of
internal state using the useState hook and employ
destructuring to establish a state variable and a method for
modifying that variable.
In the code, isViewing represents the state variable, while
setIsViewing functions as the method for altering its value.
Furthermore, the useState hook accepts a default value,
which we have set to false. This variable is subsequently
utilized to set the class name for the image and display the
handle name.

State
In the preceding segments, we have explored the concept of
state. As demonstrated in the examples, state represents
the properties specific to a component. It is not imposed by
external segments, parent components, or child
components; rather, it exclusively belongs to the component
itself. When a state is updated, it signifies a change in the
component’s internal logic.
For instance, consider the isLiked property of the state in
the Post component, which determines the image displayed
on the UI. By logical inference, if a user dislikes a post, the
image should reflect this change on the UI. Thus, a state
change prompts a re-render of the component.
Furthermore, this internal state can be passed on to a child
component, and the value received by the child component
is referred to as props.

Props
In the preceding segments, we have also explored the
concept of props, although, we have not delved in greater
detail. The primary difference between state and props is
that state, as we have seen before, is exclusive for the
component itself. Props are the values or properties
received by a component from its parent component and
which can be used in the component.
Let us illustrate props with an example. In the class-based
component Post, we have defined props within the
constructor of the class. These props represent the values
received from the parent component. To provide context,
consider the following code snippet in the parent
component:
1. <Post
2. profilePic="[Link]"
3. handle="bpbPublications"
4. caption="The latest in technology for 2024"
5. content="[Link]"
6. />
In the constructor of the Post component, we utilize the
keyword super to access the props passed from the parent
component, subsequently making them available within the
class via [Link]. Within the render method, we employ
destructuring to extract specific properties from these
props. These properties are then utilized throughout the JSX
within the render method to facilitate UI rendering.
Similarly, for the functional component Story, envision the
parent component importing and invoking the Story
component as follows:
1. <Story
2. profilePic="[Link]"
3. handle="bpbPublications"
4. />
As depicted in the code above, two props, profilePic and
handle, are passed to the functional component Story.
These props are subsequently received as arguments within
the component and are utilized in its rendering.
An important distinction between state and props is that
while props are injected into a component and cannot be
modified within the component itself, state is exclusive to
the component and can be modified internally. However, if
props are defined as functions in the parent component,
their values and invocations can be passed to the
component.

Components as Design Patterns


In the preceding segments, we have revisited the
fundamental concepts of React—components, state, and
props. Components inherently embody a Design Pattern that
fosters code reusability, readability, and maintenance. In
this section, we will delve into the myriad benefits of
components, aligning them with the advantages offered by
the Design Patterns discussed earlier in this book.
Singleton Pattern
As we have discussed previously, the Singleton Pattern
advocates for a single instance of state throughout the
application, minimizing complications and the risk of state
pollution. Components can serve as a global state
management system, exemplified by tools like Redux or
React context, ensuring the existence of only one instance
of state.
Observer Pattern
React’s built-in state and props mechanisms can be
leveraged to implement the Observer Pattern. Components
can subscribe to changes in state or props and
automatically update themselves when changes occur.
Strategy Pattern
In enterprise-level applications, components provide the
flexibility of conditional rendering. They can encapsulate
various strategies for rendering or behavior and
dynamically switch between them. As we have previously
discussed, this can be achieved by passing props or
modifying the state to alter the component’s behavior.
Composite Pattern
React’s component tree structure inherently follows the
Composite Pattern. Components can be composed of other
components, allowing for the creation of complex UIs from
simple building blocks.
Proxy Pattern
React components can serve as proxies that control access
to another component or resource. This is achieved through
conditional rendering or by implementing access control
logic within components.

React Hooks
React Hooks is a special feature tailored for functional
components in React. As we have discussed previously, the
key distinction between functional components and class-
based components lies in the absence of state in functional
components. While class-based components possess an
internal state, functional components are inherently simpler
and do not possess a state property.
However, state plays a pivotal role in determining the
behavior and rendering of components in React, as well as
in distributing props to child components. To enable
functional components to exhibit state-like functionality,
React introduced the concept of hooks. In this section, we
will first understand the concept of hooks before
understanding how it performs the duties of a Design
Pattern.
It is important to note that we would not delve into the
intricate details of every hook. Instead, we will explore
examples of a few hooks to demonstrate their usability, set a
tone for explaining how they qualify as Design Patterns, and
refresh our knowledge.

Types of Hooks
According to React’s official documentation, Hooks
empower us to leverage various React features within our
components. React offers several built-in Hooks for this
purpose. Additionally, we have the flexibility to combine
these built-in Hooks to create our own custom Hooks. Let us
have a look at all the built-in hooks offered by React.
State Hooks
As we have seen previously, state helps component
remember information, such as user input in a form
component. While class-based components feature the state
property defined within the constructor, React provides two
hooks to replicate this behavior in stateless functional
components:
• useState: The useState Hook is used to declare a state
variable in a functional component. It provides a simple
way to manage local component state, especially when
the state logic is straightforward or involves primitive
data types. It returns an array with two elements: the
current state value and a function to update that state.
useState is ideal for managing simple or small pieces of
state like form inputs, toggles, or counters.
• useReducer: The useReducer hook is used for more
complex state management scenarios where the state
depends on multiple actions or has intricate update
logic. It works by defining a reducer function that
specifies how the state should change based on the
dispatched action. It returns an array with the current
state and a dispatch function to trigger updates.
useReducer is better suited for scenarios involving
complex state transitions, such as managing a shopping
cart, handling form submissions, or working with deeply
nested state.
Context Hooks
Context lets a component receive information from a distant
parent without having to pass props through several
components and thereby taking unnecessary space.
Let us try to visualize a DOM tree in the following figure to
better grasp this topic:

Figure 6.2: A DOM tree structure with the root node splitting down into child
nodes
As illustrated in Figure 6.2, consider a DOM tree where a
value must be passed from the root node (depicted by text
Root inside the circle) to a child node at the bottom of the
tree (denoted by the text L4). Passing values via props from
the root node to the child node would involve passing a
property unnecessarily through three components that have
no use for it other than to pass it along. This can be
circumvented by leveraging the useContext Hook.
Ref Hooks
Previously, we became acquainted with the state of a
component, a property holding information crucial for
rendering. Refs, however, function differently. They enable
a component to store information not intended for
rendering, such as a DOM node to modify its style or a
timeout ID. Unlike state, updating a ref does not trigger a
re-render of the component. Refs are often referred to as an
escape hatch from the React paradigm. This term signifies a
mechanism that allows developers to bypass the declarative
and state-driven approach of React when necessary. In
essence, refs provide a way to perform imperative tasks—
like directly manipulating the DOM or interacting with
browser APIs—that React typically abstracts away. This
makes them exceptionally useful for integrating React
components with non-React systems or handling specific
scenarios where React’s declarative approach falls short.
We utilise the useRef Hook for creating such refs or
references:
1. import React, { useRef } from 'react';
2.
3. function MyComponent() {
4. // Create a ref object
5. const inputRef = useRef(null);
6.
7. // Function to focus the input field
8. const focusInput = () => {
9. [Link]();
10. };
11.
12. return (
13. <div>
14. {/* Assign the ref to the input element */}
15. <input ref={inputRef} type="text" />
16. <button > Input</button>
17. </div>
18. );
19. }
20.
21. export default MyComponent;
For example, in the code snippet above, we have employed
the useRef hook to define a variable that stores the input
text element. Upon clicking the button, we utilize this ref
variable to bring the element into focus. As evident from the
example, using the useRef hook does not trigger a re-
render of the component. Nonetheless, it proves highly
beneficial for storing a DOM node that can be subsequently
utilized for rendering the component based on a condition.
Effect Hooks
The Effect Hooks are among the most commonly and
extensively used Hooks in React. Every React component
follows a distinct lifecycle, guiding its journey from creation
to destruction. React provides several methods for class-
based components to tap into these lifecycle breakpoints,
enabling developers to take critical or definitive actions.
For example, consider a form component that prompts users
to input their personal information. Before the user clicks
the save or update button to send the information to the
server, it is crucial to perform validation on the entered
information, especially in data-sensitive industries like
banking. By leveraging componentDidUpdate, a lifecycle
method triggered every time a component is updated, we
can run validation after each update—whenever the user
enters information on the UI. This validation can then notify
the user of any errors, such as an incorrect email address or
account number.
Unlike class-based components, functional components in
React do not have access to lifecycle methods. Instead, they
rely on Effect Hooks to fulfill this purpose. Effect Hooks
enable a component to connect to and synchronize with
external systems, effectively emulating the behavior of
lifecycle methods. This encompasses tasks such as
interacting with the network, manipulating the browser
DOM, implementing animations, integrating widgets from
other UI libraries, and interfacing with other non-React
code. We utilize the useEffect Hook to implement effects in
functional components:
1. import React, { useEffect, useState } from 'react';
2.
3. function MyComponent() {
4. const [count, setCount] = useState(0);
5.
6. useEffect(() => {
7. // Update the document title with the current count
8. [Link] = `Count: ${count}`;
9. });
10.
11. return (
12. <div>
13. <p>Current count: {count}</p>
14. <button => setCount(count +
1)}>Increment</button>
15. </div>
16. );
17. }
18.
19. export default MyComponent;
In the provided code snippet, we have utilized the
useEffect Hook to display the number of times a button has
been clicked by the user on the title of the webpage. Each
time the user clicks the button, we employ the useState
Hook to update the count, triggering a re-render of the
component. Consequently, with each re-render, the
useEffect hook is invoked, updating the webpage title with
the latest count stored in the state.
Performance Hooks
Re-rendering a component can be computationally
expensive. While it may not be a major concern for smaller
components with minimal state or props, medium to larger
components can experience performance issues, especially
if even minor changes in state or receiving new props
trigger a re-render of the entire component.
Using the analogy of the DOM tree image from the previous
segment, imagine if a DOM node at the top of the tree
requires re-rendering. This would entail that all its children
nodes and elements (potentially half of the entire tree) will
also be destroyed and re-rendered. Consequently, this could
slow down the application, adversely impacting the user
experience.
To mitigate this problem, functional components utilize
performance hooks to optimize re-rendering performance by
skipping unnecessary work. For example, React can be
instructed to reuse cached calculations or to skip a re-
render if the data has not changed since the previous
render.
To skip calculations and unnecessary re-rendering of the
components, we use one of the following hooks:
• useMemo: This Hook allows us to cache the result of
an expensive calculation. For instance, consider a
component that displays a list of products with their
total prices. Calculating the total price involves summing
up all the products in the list, which can be
computationally expensive, especially for large lists. By
utilizing useMemo, we can memoize the total price
calculation. This means that the calculation is cached,
and it is only recalculated when the list of products
changes. This optimization helps improve performance
by avoiding unnecessary recalculations.
• useCallback: This Hook allows us to cache a function
definition before passing it down to an optimized
component. For instance, let us imagine that we are
constructing a parent component responsible for
rendering a list of items, each equipped with an onClick
event handler. Passing a new function reference to each
item on every render could inadvertently trigger
unnecessary re-renders of the items, even if the function
logic remains unchanged. By leveraging useCallback,
you can memoize the onClick event handler function,
ensuring that it remains the same unless its
dependencies change. This optimization strategy
effectively prevents unnecessary re-renders of the items,
enhancing performance and efficiency.

Hooks as Design Patterns


React Hooks provide a way to use state and other React
features without writing a class. They offer a more
functional and simpler way to managing state and side-
effects in React components. Here is how Hooks serve the
purpose of Design Patterns in JavaScript:
Observer Pattern
The useState and useEffect hooks allow components to
observe changes in state and trigger side effects in
response. This is akin to the Observer Pattern where
observers (components) are notified of changes in the
subject (state).
Factory Pattern
As we have previously learned, in addition to the various
built-in Hooks provided by React, developers can also create
custom Hooks by combining different Hooks provided by
React. Custom Hooks in React serve as factories for
encapsulating and reusing stateful logic across components.
By creating custom Hooks, developers can abstract complex
logic, providing a simple interface for consuming
components. This modular approach enhances code
maintainability and encourages code reuse across different
parts of the application.
Strategy Pattern
Hooks like useReducer allow components to encapsulate
different strategies for managing state. By providing
different reducer functions, components can switch between
different state management strategies dynamically.
Adapter Pattern
Hooks can be used to adapt external APIs or libraries to fit
the React component model. For example, a custom Hook
can encapsulate the logic for interacting with a third-party
API and provide a React-friendly interface for components
to consume.
Proxy Pattern
Hooks can act as proxies to control access to certain
resources or functionalities within components. For
instance, a custom Hook can implement access control logic
to restrict certain actions based on user permissions or
authentication status.

Higher-order components
Higher-order components (HOCs) represent an advanced
technique in React for reusing component logic. Essentially,
a higher order component is a function that takes a
component and returns a new component. While HOCs are
not officially a part of the React API, they emerge as a
Design Pattern from React’s compositional nature.
While a component transforms props and state into UI, an
HOC transforms a component into another component. In
fact, popular libraries like Redux and relay, utilized by
several top React applications, employ the concept of HOCs
extensively. For instance, Redux’s connect and relay’s
createFragmentContainer methods are based on the HOC
pattern.
To cite an example, imagine we have several components in
our application that must be accessed only by authenticated
members. Instead of adding the same authentication logic to
each and every component, we can instead create an HOC
to handle the authentication process:
1. import React from 'react';
2. import { Redirect } from 'react-router-dom';
3.
4. const withAuth = (WrappedComponent) => {
5. const AuthenticatedComponent = (props) => {
6. const isAuthenticated =
checkIfUserIsAuthenticated(); // Function to check if user
is authenticated
7.
8. if (isAuthenticated) {
9. return <WrappedComponent {...props} />;
10. } else {
11. return <Redirect to="/login" />;
12. }
13. };
14.
15. return AuthenticatedComponent;
16. };
17.
18. export default withAuth;
In the provided example, we have created a HOC named
withAuth. This HOC accepts a component as input. It then
verifies whether the user is authenticated. If the user is
authenticated, it returns the original component as is.
Otherwise, it returns the login component. This enables
conditional rendering based on the authentication status.
It can then be used by a component in this fashion:
1. import React from 'react';
2. import withAuth from './withAuth';
3.
4. const Dashboard = () => {
5. return (
6. <div>
7. <h1>Welcome to the Dashboard</h1>
8. {/* Dashboard content */}
9. </div>
10. );
11. };
12.
13. export default withAuth(Dashboard);
As of May 2024, HOCs are not actively used in modern
React code. However, they retain strategic importance as a
Design Pattern that proved extremely beneficial for several
years. Though not actively utilized, they are still employed
by some React developers.

HOCs as Design Patterns


HOCs, in itself, are an epitome of a Design Pattern in React.
They serve as a powerful tool for enhancing and reusing
component logic. They enable developers to apply cross-
cutting concerns, such as state management, data fetching,
or authentication, to multiple components without repeating
code. Here is how HOCs can serve the purpose of Design
Patterns in JavaScript:
Decorator Pattern
HOCs closely resemble the Decorator Design Pattern for
JavaScript. They act as decorators that wrap components
with additional functionality or behavior. They enhance the
original component by adding props, state, or methods
without modifying its source code directly. This allows for a
clean separation of concerns and promotes reusability.
Factory Pattern
HOCs can serve as factories for creating specialized
versions of components with specific configurations or
behaviors. By accepting parameters and returning a new
component, HOCs enable the creation of component
variations tailored to different use cases or environments.
Strategy Pattern
HOCs allow developers to encapsulate alternative strategies
for implementing certain behaviors or functionalities within
components. By composing components with different
HOCs, developers can switch between different strategies
dynamically, depending on specific requirements or
conditions.
Adapter Pattern
HOCs can adapt the interface of components to integrate
with external libraries, APIs, or data sources. They translate
the data or behavior provided by external sources into a
format that is compatible with React components, enabling
seamless integration and interoperability.
Composite Pattern
HOCs facilitate component composition by enabling the
combination of multiple higher-order components to create
more complex and feature-rich components. This
composability allows developers to build sophisticated UI
structures by assembling smaller, reusable building blocks.
Proxy Pattern
HOCs can serve as proxies for controlling access to
components or resources based on certain conditions or
permissions. They can wrap components with logic to
enforce access control rules, validate inputs, or handle error
conditions before rendering them to the UI.

Popular Design Patterns specific for React


React is justifiably one of the most popular JavaScript
libraries, as of 2024. Since it is widely being used for
building the UI, there are various Design Patterns that
React developers often employ to create scalable,
maintainable, and efficient applications. In this section, we
will explore several strategies or Design Patterns identified
by the React community over time. These patterns are
extensively utilized by React developers to architect their
applications effectively.

Container/Component Pattern
The Container/Component Pattern, also known as the
Container/Presentation Pattern or Smart/Dumb
Components Pattern, is a Design Pattern commonly used in
React applications to manage state and UI concerns
effectively. Let s visualize how smaller atomic components
can together build an entire page using the following image:
Figure 6.3: Container/Component (Container/Presentation) Pattern can be
combined to generate a complex UI
As depicted in Figure 6.3, this pattern encompasses two
distinct components that synergize to render UI effectively.
The fundamental concept underlying this Design Pattern is
to enforce the separation of concerns by dividing
components into two distinct categories: one set of
components manages logic, while the other set focuses
solely on presentation or rendering duties.
To mitigate potential confusion arising from the term
component when referring to the Design Pattern, let us
adopt the alternative nomenclature Container/Presentation
to understand this pattern further. With this approach in
mind, let us delve into each of these components
individually to gain a comprehensive understanding of this
pattern.

Container components
The first part of the Container/Presentation Design Pattern
is the Container component. The Container components are
responsible for managing state, fetching data, and
interacting with any external APIs or service. They typically
hold the state of the application and pass down the data and
behavior to their child components (in this case, the
presentation components). In case the application is
powered by Redux which manages the global state, it is the
Container components which would be connected to the
Redux store to dispatch actions and retrieve global state:
1. // Container Component
2. import React, { Component } from 'react';
3. import { connect } from 'react-redux';
4. import { fetchData } from './actions';
5.
6. class UserContainer extends Component {
7. componentDidMount() {
8. [Link]();
9. }
10.
11. render() {
12. const { isLoading, users, error } = [Link];
13.
14. return (
15. <div>
16. {isLoading ? (
17. <p>Loading...</p>
18. ) : error ? (
19. <p>Error: {[Link]}</p>
20. ):(
21. <UserList users={users} />
22. )}
23. </div>
24. );
25. }
26. }
27.
28. const mapStateToProps = state => ({
29. isLoading: [Link],
30. users: [Link],
31. error: [Link]
32. });
33.
34. const mapDispatchToProps = dispatch => ({
35. fetchData: () => dispatch(fetchData())
36. });
37.
38. export default connect(mapStateToProps,
mapDispatchToProps)(UserContainer);
The provided code exemplifies the Container aspect of the
Container/Presentation Design Pattern. Within the
UserContainer component, a ternary conditional operator
is employed to determine whether to render a loading
screen, display an error message, or render another
component, UserList. Notably, this code integrates with the
global state management package Redux to handle global
state. Redux’s HOC, connect, is utilized for the
UserContainer component, alongside mapStateToProps
to access the global state for conditional rendering and
mapDispatchToProps, which is utilized within the lifecycle
method componentDidMount to fetch data required by
the UserList component. In essence, this component serves
as the Container part of the Design Pattern, responsible for
managing logic, data fetching, and interacting with external
libraries like Redux.
Presentation components
The second part of the Container/Presentation Design
Pattern is the Presentation component, which is solely
dedicated to the presentation aspect of the application. Also
referred to as dumb components, Presentation components
typically employ simpler functional components to focus on
UI rendering. They receive data and callbacks as props and
render UI elements based on this data. As these components
solely handle UI rendering, they do not internally manage
state and are typically stateless. Instead, they rely on props
passed from their parent container components to render UI
elements. Presentation components boast high reusability
since they are agnostic to the application’s state and data-
fetching logic:
1. // Presentation Component
2. import React from 'react';
3.
4. const UserList = ({ users }) => (
5. <ul>
6. {[Link](user => (
7. <li key={[Link]}>{[Link]}</li>
8. ))}
9. </ul>
10. );
11.
12. export default UserList;
In the provided code snippet, we have created the UserList
component, previously utilized in the Container components
section. In the preceding example, the UserContainer
component passes the list of users as props to the UserList
component. As evident from the code above, this component
lacks any logic or state. Its sole purpose is to utilize the
users value received in props to render a list of usernames.
In this way, the Container component assumes the heavier
and more intricate task of managing logic and data
maintenance, while the Presentation component simply
receives this data from its parent Container component to
render the UI. Working in tandem, both components
efficiently render complex UI elements.

Advantages
The Container/Component (Container/Presentation) Design
Pattern offers the following advantages in building React
applications:
• Separation of concerns: The pattern separates
concerns between managing state and rendering UI,
making the codebase more modular and easier to
maintain.
• Reusability: Presentation components are highly
reusable as they are not coupled with specific data or
state management logic.
• Testability: Container components, being responsible
for state management and logic, can be easily tested in
isolation without concerning UI rendering.
In conclusion, the Container/Component Pattern is a
powerful Design Pattern for structuring React applications,
offering clear separation of concerns between state
management and UI rendering. By encapsulating stateful
logic within container components and keeping presentation
components focused solely on rendering UI elements,
developers can create more modular, reusable, and
maintainable codebases. This pattern promotes better
organization, testability, and scalability of React
applications, making it a preferred approach for building
complex user interfaces with React.

Render Props Pattern


The Render Props Design Pattern is a technique employed
in React for code sharing between components, especially
useful for creating reusable components that share behavior
or state. In this pattern, a component receives a prop that is
essentially a function returning React elements. This
empowers the component to determine what to render by
invoking this function with the appropriate parameters.

Implementation
To implement the Render Props Design Pattern, two key
components are involved: a parent component and a child
component that share code or behavior. The implementation
typically follows four straightforward steps:
1. The parent component encapsulates some functionality,
or a state and the child component require access to
that functionality or state.
2. To achieve this, following the React convention of
passing information, the parent component will transmit
this behavior or state to the child component through
props. It is crucial to understand that this behavior or
state resides within the parent component or is a part of
it. Hence, the parent component will convey a function
to the child component via props. Typically, this function
will accept some arguments and return React elements.
3. The child component will then invoke this function
provided by the parent component via props and pass
any required parameters.
4. Finally, the parent component executes the function
and renders the result, possibly passing along additional
props or state.
Let us translate these textual steps into a code
implementation. To facilitate our understanding of the
implementation of this Design Pattern, let us consider the
example of a counter and a button where clicking the button
will increase the count of the counter.
Firstly, we will create a Counter component that manages
the count state. This component will also render a Button
component through a render prop, allowing the button to
increment the count when clicked:
1. // [Link]
2. import React, { useState } from 'react';
3.
4. const Counter = ({ render }) => {
5. const [count, setCount] = useState(0);
6.
7. const incrementCount = () => {
8. setCount(count + 1);
9. };
10.
11. return (
12. <div>
13. {render(count, incrementCount)}
14. </div>
15. );
16. };
17.
18. export default Counter;
19.
In this code example:
1. The Counter component uses the useState hook to
create a state variable called count, initialized to 0. The
setCount function is used to update this state.
2. The incrementCount function updates the count state
by calling setCount with the new value: count + 1.
3. The Counter component does not directly render any
specific UI elements (like a button). Instead, it uses a
Render Props Pattern. It receives a render prop from its
parent component and invokes this prop, passing the
current count value and the incrementCount function
as arguments.
4. The parent component defines what the UI should look
like by providing a function as the render prop. This
function is responsible for rendering the UI (e.g., a
button) and attaching the incrementCount function as
the click handler.
Next, let us create the Button component:
1. // [Link]
2. import React from 'react';
3.
4. const Button = ({ onClick, label }) => {
5. return <button > 6. };
7.
8. export default Button;
9.
In the above code example, we have created the Button
component, which receives the text to display on the button
and the click handler via its props. Serving as a
presentation component, Button simply receives its props
and utilizes them to render the button UI.
Now, we will combine the two and add a wrapper
component above the Counter component to complete the
implementation of the Render Props Design Pattern:
1. import React from 'react';
2. import Counter from './Counter';
3. import Button from './Button';
4.
5. const App = () => {
6. return (
7. <div>
8. <Counter
9. render={(count, incrementCount) => (
10. <Button label=
{`Clicked ${count} times`} />
11. )}
12. />
13. </div>
14. );
15. };
16.
17. export default App;
18.
In the provided code, we have completed the
implementation by importing both the Counter and Button
components and using them together. Inside the App
component, we have used the Counter component and
passed the Button component within the render props of
the Counter component. This way, the Counter component
passes its internal state to the Button component and
receives the callback whenever the button is clicked to
update its state and pass the updated state to be displayed
on the button. This showcases how the Render Props
Pattern facilitates sharing behavior between components.

Advantages
The Render Props Design Pattern offers the following
advantages in building React applications:
• Reusability: Components can be reused across
different contexts since they are not tightly coupled.
• Flexibility: The child component has control over what
gets rendered, allowing for greater customization.
• Encapsulation: Parent components can encapsulate
complex logic or state management, keeping child
components simple and focused on presentation.
• Composability: Components can be composed together
easily, enabling the creation of more complex UIs from
simpler building blocks.
In conclusion, the Render Props Pattern in React offers a
versatile solution for creating reusable components with
shared behavior or state. By passing a function prop from a
parent to a child component, render props enable
encapsulation, flexibility, and composability in building UIs.
This pattern promotes clean, modular code and encourages
the separation of concerns, making it easier to manage
complex applications and promote code reusability.

Context API Pattern


The context API is a feature provided by React that allows
us to manage global state in the application and share data
between the components without having to pass props
manually through every level of the component tree. It is
considered a Design Pattern, specifically a Context Design
Pattern. Essentially, it is a way of structuring the React
application to manage and share state in a more efficient
and scalable manner. Let us try to imagine how props
passing would work across a large DOM tree by checking
out the image below:
Figure 6.4: It is worthless to pass props across several levels of a DOM tree
To illustrate the problems solved by the context API, let us
consider a scenario where a component at the bottom of the
DOM tree requires a prop from a component at the top. This
situation is depicted in Figure 6.4, where the component
denoted by the text L4 at the bottom needs a prop from the
component denoted by the text Root at the top.
Passing the prop through multiple levels of the component
tree introduces unnecessary complexity and potential for
errors. Each intermediate component in the hierarchy needs
to receive and pass along the prop, even if it does not
directly use or modify it. This can clutter the code and make
it harder to understand and maintain. Additionally,
accidental modifications to the prop can occur as it
traverses through the component hierarchy.
This is where the context API comes in to provide a solution
by allowing components to share data without explicitly
passing props through every level of the component tree. It
provides a way to access data from a parent component
without the need for intermediate components to pass the
data along. This simplifies the code and reduces the
potential for errors.

Implementation
To implement the Context API Pattern, follow these three
steps:
1. Creating a context: We start by creating a context
that can be consumed by any component at any level of
the tree. This is done using the createContext function
provided by React:
1. const MyContext = [Link]();
2. Providing context values: Next, we wrap the part of
the component tree that needs access to the context
within the Provider component:
1. <[Link] value={/* some value */}>
2. {/* Your component tree */}
3. </[Link]>
3. Consuming context values: To access the context
values, use the Consumer component or the
useContext Hook. The Consumer component uses the
Render Prop Pattern, while the useContext Hook allows
you to consume context values in functional components:
1. // using Consumer wrapper
2. <[Link]>
3. {value => /* render something based on the context
value */}
4. </[Link]>
5.
6. // useContext hook
7. import React, { useContext } from 'react';
8.
9. function MyComponent() {
10. const value = useContext(MyContext);
11. // Use the context value here
12. }
Now, let us apply these steps to create a code example:
1. // [Link]
2. import React from 'react';
3.
4. const ThemeContext = [Link]('light');
5.
6. export default ThemeContext;
7.
8. // [Link]
9. import React from 'react';
10. import ThemeContext from './ThemeContext';
11. import Toolbar from './Toolbar';
12.
13. class App extends [Link] {
14. render() {
15. return (
16. <[Link] value="dark">
17. <Toolbar />
18. </[Link]>
19. );
20. }
21. }
22.
23. // [Link]
24. import React from 'react';
25. import ThemeContext from './ThemeContext';
26.
27. function Toolbar() {
28. return (
29. <div>
30. <ThemedButton />
31. </div>
32. );
33. }
34.
35. // [Link]
36. import React from 'react';
37. import ThemeContext from './ThemeContext';
38.
39. function ThemedButton() {
40. const theme = useContext(ThemeContext);
41. return <button style={{ background: theme
}}>Themed Button</button>;
42. }
In the provided code example, we have created four files:
• [Link]: This file creates the context using
the createContext() function from React. The default
value for the context is set to 'light'.
• [Link]: This file serves as the container or wrapper
component for our application. It provides the
[Link] component to make the
context available to all components in the tree. It also
renders the Toolbar component.
• [Link]: This file represents an intermediate node or
level in our DOM tree. It would need to pass props in the
absence of a context. However, with the context API, it
does not need to directly pass props down to its child
components.
• [Link]: This file represents a component
that requires the context provided by the wrapper
component (App). It uses the useContext() hook to
consume the context and render the button with the
appropriate theme.
By using the context API, we can avoid prop drilling and
make the theme context available to any component in the
tree without the need for intermediate components to pass
down props.

Advantages
The Context API Design Pattern offers the following
advantages in building React applications:
• Global state management: Context API allows us to
create a global state that can be accessed by any
component at any level in our DOM tree, without the
need to pass props down manually through multiple
levels of the component tree. This makes managing
application-wide state much easier.
• Avoids props drilling: In large applications with
deeply nested component hierarchies, passing props
through multiple layers can become cumbersome and
error prone. Context eliminates this problem by
providing a centralized way to share data without the
need for prop drilling.
• Simplifies component composition: Context
simplifies the process of composing components by
providing a way to share data between them. This allows
us to create more modular and reusable components
without worrying about how they will access shared
data.
• Encourages separation of concerns: By decoupling
the state management from the component hierarchy,
context encourages a cleaner separation of concerns
within your application. Components can focus on
rendering UI and handling user interactions, while
context providers handle state management.
• Performance optimization: Context API comes with
built-in optimizations to prevent unnecessary re-renders
of components that consume context values. It uses a
subscription-based model to only update components
when the relevant context value changes, improving
performance.
• Scalability: As your application grows, managing state
becomes increasingly complex. Context provides a
scalable solution for managing state that can easily
accommodate the growing needs of your application.
Overall, the Context API Pattern in React offers a powerful
and flexible way to manage application state and share data
between components, making it a valuable tool for building
complex and maintainable applications.

Using JavaScript Design Patterns with React


React is a JavaScript library used for building user
interfaces, and while it encourages a different paradigm
compared to traditional JavaScript Design Patterns, we can
still apply some of these patterns within React applications.
Let us have a look at some of the ways in which we can
incorporate traditional JavaScript Design Patterns in
JavaScript.

Module Pattern
The Module Design Pattern is a way of organizing code into
separate, self-contained modules that encapsulate related
functionality. React, on its own, emphasizes on creating
components that are essentially modular in nature. So, in
the context of React, we can apply the Module Design
Pattern to create reusable components.
Here is how we can utilize the Module Design Pattern in
React:
1. Identify functionalities: We begin by outlining the
fundamental behaviors or features of our application.
For example, in case of a social media page, we can
consider functionalities such as displaying user posts,
interacting with posts (like, comment, share), displaying
comments, etc. So, the first order of the day is to
determine the functionalities that can be encapsulated
into separate modules. These functionalities could be UI
components, data fetching logic, state management, or
any other reusable piece of code. We begin by outlining
the fundamental behaviors or features of our
application.
2. Create React components: Once we have identified
and outlined every functionality, the next step is to
create a React component to represent these
functionalities. We need to create separate files for each
component to keep them organized.
3. Encapsulate logic: Once we have identified the core
functionalities of our application, it’s crucial to ensure
that each component encapsulates its own logic and
functionality. Clear boundaries should be established to
define the purpose of each component, avoiding
ambiguity, and promoting reusability and
maintainability. By adhering to this principle,
components become more self-contained and easier to
understand, maintain, and reuse across different parts of
the application.
4. Export and import components: We need to export
each component from their respective files using the
export keyword and import them into other files where
they are needed using the import keyword.
5. Pass props: Now, we need to utilize props to pass data
and functions between parent and child components.
This allows us to customize the behavior of each module
as per the requirements.
6. Compose components: For the final part of the
puzzle, we will compose larger UIs by combining these
modular components together. This promotes reusability
and maintainability by keeping the codebase organized
and easy to understand.
Let us look at an example to put these steps into action:
In this example, we will demonstrate how to structure a
page by creating distinct components for each significant
element. Each component will encapsulate a specific
functionality or part of the page, contributing to a modular
and organized architecture:
1. // [Link]
2. import React from 'react';
3.
4. const Header = () => {
5. return (
6. <header>
7. <h1>My Blog</h1>
8. </header>
9. );
10. };
11.
12. export default Header;
13.
In the provided example, we initiate the page structure by
creating a dedicated component responsible for rendering
the header. This component serves the specific purpose of
incorporating a header into the webpage, thereby
promoting clarity and modularity in the codebase:
1. // [Link]
2. import React from 'react';
3.
4. const PostList = ({ posts }) => {
5. return (
6. <div>
7. <h2>Latest Posts</h2>
8. <ul>
9. {[Link](post => (
10. <li key={[Link]}>
11. <h3>{[Link]}</h3>
12. <p>{[Link]}</p>
13. </li>
14. ))}
15. </ul>
16. </div>
17. );
18. };
19.
20. export default PostList;
Following the modular approach, we proceed to craft a
component tailored for displaying a list of posts. This
component is designed to accept a prop named posts,
which is anticipated to be an array. Through iteration over
this array, the component dynamically generates a list, with
each item containing a title and body, facilitating the
showcasing of post titles and content on the user interface.
1. // [Link]
2. import React from 'react';
3.
4. const Footer = () => {
5. return (
6. <footer>
7. <p>&copy; 2024 My Blog</p>
8. </footer>
9. );
10. };
11.
12. export default Footer;
13.
In alignment with the modular design, we proceed to
construct a footer component, as demonstrated in the
provided code example.
Subsequently, we integrate all these distinct components
into a wrapper application. In this application, we curate a
list of posts, which is then supplied to the PostList
component. This furnishes the functionality to generate a
comprehensive list of posts within the application’s
interface:
1. // [Link]
2. import React from 'react';
3. import Header from './Header';
4. import PostList from './PostList';
5. import Footer from './Footer';
6.
7. const App = () => {
8. const posts = [
9. { id: 1, title: 'First Post', body: 'This is the content of
the first post.' },
10. { id: 2, title: 'Second Post', body: 'This is the content
of the second post.' },
11. { id: 3, title: 'Third Post', body: 'This is the content of
the third post.' },
12. ];
13.
14. return (
15. <div>
16. <Header />
17. <PostList posts={posts} />
18. <Footer />
19. </div>
20. );
21. };
22.
23. export default App;
In this example, Header, PostList, and Footer components
are separate modules, each responsible for rendering a
specific part of the blog UI. App component composes these
modules together to create the blog application.
This modular approach makes it easier to manage and
maintain the codebase, as each component focuses on a
specific aspect of the application’s UI. Additionally, it allows
for reusability, as these components can be used in other
parts of the application or even in different projects.

Factory Pattern
The Factory Design Pattern is a Creational Design Pattern
that provides an interface for creating objects without
specifying their concrete classes. In the context of React, we
can implement the Factory Pattern to create components
dynamically based on certain conditions or configurations.
Let us delve directly into an example to illustrate how we
can implement the Factory Design Pattern in a React
application:
1. import React from 'react';
2.
3. // Define different types of components
4. const Button = ({ onClick, children }) => (
5. <button >6. );
7.
8. const Input = ({ onChange, value }) => (
9. <input type="text" value=
{value} />
10. );
11.
12. // Factory function to create components based on type
13. const ComponentFactory = {
14. createComponent: (type, props) => {
15. switch (type) {
16. case 'button':
17. return <Button {...props} />;
18. case 'input':
19. return <Input {...props} />;
20. default:
21. throw new Error('Invalid component type');
22. }
23. },
24. };
25.
26. // Example usage of the factory function
27. const MyComponent = () => {
28. const buttonComponent =
[Link]('button', {
29. onClick: () => alert('Button clicked!'),
30. children: 'Click me',
31. });
32.
33. const inputComponent =
[Link]('input', {
34. onChange: (e) => [Link]([Link]),
35. value: '',
36. });
37.
38. return (
39. <div>
40. {buttonComponent}
41. {inputComponent}
42. </div>
43. );
44. };
45.
46. export default MyComponent;
In this example, we have defined two components that can
be dynamically generated using our component factory. The
Button component receives a click event handler and
content to display as props, while the Input component
receives a change event handler and a value prop.
Next, we create a component factory where users can
specify the type of element they want to produce and
provide the necessary props. Using a switch statement, we
can then return the appropriate component instance to the
caller of the factory function.
As we can see later in the code, in the MyComponent
component, we use the ComponentFactory to create
instances of Button and Input components with specific
props.
In this way, we can abstract the creation of components and
make the code more flexible and maintainable, especially
when dealing with dynamic component creation or
component variations based on certain conditions.
Observer Pattern
The Observer Design Pattern belongs to the Behavioral
category of Design Patterns where an object (known as the
subject) maintains a list of its dependents (observers) and
notifies them of any state changes, usually by calling one of
their methods.
In the context of React, we can implement the Observer
Pattern using a combination of React’s state management
and lifecycle methods.
For this example, we will use functional components and
hooks to implement the observer pattern in React:
1. import React, { useState, useEffect } from 'react';
2.
3. // Subject component
4. const SubjectComponent = () => {
5. const [subjectState, setSubjectState] =
useState('Initial state');
6. const [observers, setObservers] = useState([]);
7.
8. // Function to update subject state
9. const updateState = () => {
10. setSubjectState('New state');
11. };
12.
13. useEffect(() => {
14. // Notify observers when state changes
15. notifyObservers();
16. }, [subjectState]);
17.
18. // Function to register observers
19. const addObserver = (observer) => {
20. setObservers([...observers, observer]);
21. };
22.
23. // Function to notify observers
24. const notifyObservers = () => {
25. [Link]((observer) =>
[Link](subjectState));
26. };
27.
28. return (
29. <div>
30. <h2>Subject Component</h2>
31. <p>State: {subjectState}</p>
32. <button > State</button>
33. </div>
34. );
35. };
36.
37. // Observer component
38. const ObserverComponent = ({ subject }) => {
39. const [observerState, setObserverState] =
useState('');
40.
41. // Function to update observer state
42. const update = (newState) => {
43. setObserverState(newState);
44. };
45.
46. useEffect(() => {
47. // Add itself as an observer to the SubjectComponent
48. [Link]({ update });
49. return () => {
50. // Cleanup: Remove itself as an observer
51. [Link]({ update });
52. };
53. }, [subject]);
54.
55. return (
56. <div>
57. <h2>Observer Component</h2>
58. <p>State: {observerState}</p>
59. </div>
60. );
61. };
62.
63. // App component
64. const App = () => {
65. return (
66. <div>
67. <SubjectComponent />
68. <ObserverComponent subject=
{SubjectComponent} />
69. </div>
70. );
71. };
72.
73. export default App;
In the provided code example, we have created three
components: SubjectComponent, which represents the
subject (object) to be observed; ObserverComponent,
responsible for displaying the state it observes; and App,
which serves as a wrapper for these two components.
In the SubjectComponent, we have initialized the state
using the useState hook to maintain the initial state. We
have defined an updateState method to update the state
value and an empty array called observers to store
observer components. Additionally, we have implemented a
method to add observers to the observers array and a
notifyObservers method to notify all observers whenever
the state changes. We have used the useEffect hook with a
dependency on the subjectState variable to trigger the
notifyObservers function whenever subjectState changes.
In the ObserverComponent, we receive a subject in the
props, which will be added to the state. We then trigger the
addObserver method of the SubjectComponent, thereby
establishing a link between the two components. This
connection allows the ObserverComponent to observe
changes in the state of the SubjectComponent.
This basic example shows how we can achieve an Observer
Pattern in React.

Decorator Pattern
The Decorator Pattern is a Structural Design Pattern that
allows behavior to be added to individual objects
dynamically, without affecting the behavior of other objects
from the same class. HOCs in React perfectly demonstrate
the implementation of the Decorator Pattern. As we have
seen earlier, they are often used to enhance the behavior of
the existing components without modifying them:
1. // [Link]
2. import React from 'react';
3.
4. const withBorder = (WrappedComponent) => {
5. return class WithBorder extends [Link] {
6. render() {
7. return (
8. <div style={{ border: '1px solid red' }}>
9. <WrappedComponent {...[Link]} />
10. </div>
11. );
12. }
13. };
14. };
15.
16. export default withBorder;
In this example, we have created an HOC called
withBorder, which enhances any component by adding a
simple red border. The HOC receives the component as a
prop and wraps it with a div that applies the border styling.
This allows us to apply consistent styling across multiple
components without repeating the border logic in each
individual component:
1. // [Link]
2. import React from 'react';
3. import withBorder from './withBorder';
4.
5. class MyComponent extends [Link] {
6. render() {
7. return <div>Hello, World!</div>;
8. }
9. }
10.
11. export default withBorder(MyComponent);
To utilize this HOC, we merely wrap the component inside
the invocation of the withBorder HOC. This enables us to
reuse the withBorder HOC to enhance other components
as well, without altering their original implementation. This
exemplifies the Decorator Pattern in React using HOC.
Conclusion
In this chapter, we delved into one of the most prominent
modern JavaScript frameworks and libraries: React. Given
that a vast majority of web applications, as of 2024, are built
using React, we explored ways to enhance their efficiency
through the application of Design Patterns.
To begin with, we looked at a brief overview of React before
diving into its built-in patterns such as the component-based
architecture, states, props, hooks, and HOCs. We then
explored several Design Patterns tailored for React,
including the Container/Presentation, context API, and
Render Props Patterns. Concluding the React section, we
implemented JavaScript Design Patterns in React to
illustrate their practical application.
In summary, React is designed to optimize performance,
streamline code, and enhance readability and
maintainability. It inherently embodies Design Patterns that
align with established JavaScript principles. Incorporating
traditional JavaScript Design Patterns into React
applications further enhances their efficiency and
effectiveness. In the next chapter, we will take on the
popular UI framework VueJS and integrate Design Patterns
to build Vue applications.

Points to remember
• React and Vue emerged as solutions to address the
challenges encountered by developers when using
traditional JavaScript. By minimizing the need for direct
DOM manipulation, they significantly enhanced code
reusability. Consequently, they facilitated the
development of larger and more intricate web
applications with greater efficiency and scalability.
• Both React and Vue adhere to a component-based
architecture, wherein each component serves as a
standalone entity comprising its own encapsulated logic,
data, and properties. These components are akin to
LEGO blocks, capable of being interconnected to
construct intricate applications. Their presence
inherently embodies various Design Patterns such as the
Modular Pattern, Singleton Pattern, Strategy Pattern,
and Composite Pattern, among others, thereby
promoting code modularity and reusability.
• While React and Vue both employ internal state
management and prop passing to child components,
React benefits from patterns tailored specifically for its
ecosystem. These include the Container/Presentation
Pattern, which divides components into logic-holding
and UI-rendering roles, the context API for global state
management (later popularized by Redux), and render
props, facilitating component rendering through props.
These patterns enhance React’s capabilities and
promote cleaner, more efficient code organization.
• In conclusion, whether through its native capabilities or
through patterns specific to their ecosystems, React can
leverage traditional JavaScript Design Patterns to
enhance code development. These patterns serve as
tried and true strategies for optimizing application
architecture, promoting reusability, and improving
overall code quality in both frameworks.

Exercises
1. Which traditional JavaScript Design Pattern is best
emulated by the concept of HOCs in React?
a. Singleton Pattern
b. Strategy Pattern
c. Modular Pattern
d. Decorator Pattern
2. Which of the following statement is NOT true?
a. The Container/Presentation Design Pattern divides
components into logic-holding and UI-rendering roles.
b. Computed properties and watchers help maintain
reactivity of the code in React.
c. Render props basically ideates that we can pass React
components or elements via props.
d. The Context API Pattern of React helps in avoiding
drilling of props over the entire DOM tree.
3. Which of the following use-cases best describe the
implementation of Decorator Pattern in React?
a. Implementation of components, states, and props in a
React application.
b. Using React’s state management and lifecycle methods
to keep components abreast of changes in other
components.
c. Using HOCs to enhance components.
d. Creating functions that can spew out multiple versions
of components.

Answers
1. d
2. b
3. c
CHAPTER 7
Design Patterns in Vue

Introduction
Modern web applications primarily rely on cutting-edge
JavaScript frameworks such as React and Vue. These
frameworks are built on component-based architecture,
emphasizing modularity and code reusability. In this
chapter, we will explore how established Design Patterns
can be effectively utilized within these frameworks to
improve application structure, maintainability, and
scalability. We will examine both the built-in patterns
inherent to React and Vue component systems, along with
popular Design Patterns commonly adopted by developers
to tackle specific challenges within these frameworks. By
grasping and implementing these design principles, we can
develop well-organized, efficient, and easier-to-maintain
JavaScript applications.

Structure
This chapter will cover the following topics:
• Overview of Vue
• Design Patterns with Vue
• Popular Design Patterns specific for Vue
• Using JavaScript Design Patterns with Vue

Objectives
Upon finishing this chapter, we will be one step closer to
crafting efficient modern JavaScript applications. By the
end of it, we will delve into the optimal utilization of the
built-in patterns within a modern JavaScript framework like
Vue. Furthermore, we will learn how to integrate the
Design Patterns we have acquired thus far to complement
these built-in patterns. Consequently, we will be equipped
to develop efficient enterprise-level JavaScript applications
that are robust, streamlined, and simpler to maintain.

Overview of Vue
As explored in previous chapters, JavaScript has been a
cornerstone of web development since the mid-1990s. The
modern JavaScript revolution started with introduction of
AngularJS by Google in 2010. While offering numerous
advantages, the steep learning curve associated with
AngularJS often deterred users from actively implementing
it in their projects.
Three years later, Facebook (now Meta) introduced React,
followed closely by Vue, a library created by Evan You.
These two libraries quickly revolutionized web development
with their user friendly interfaces, minimal learning curves,
efficient code development, and speedy UI rendering. As of
April 2024, Vue is shortly behind ReactJS with statistics on
popular websites claiming that close to 2 million live
websites use [Link] to empower their UI, highlighting its
widespread adoption and effectiveness.
In this segment, we will briefly discuss Vue, one of the two
preferred libraries used by several top product companies
for application development. We will explore what makes
Vue so popular and the advantages they offer over
traditional JavaScript applications.

Challenges faced in traditional JavaScript


Before we revisit the basic principles of Vue, let us first
recap the challenges faced by the community prior to their
arrival. Here are some of the major issues encountered by
the front-end community:

Overreliance on DOM manipulation


Traditional JavaScript relies on direct manipulation of the
DOM to update the UI. This approach can become
cumbersome and prone to errors, especially in large
interactive applications. Such practices often result in
spaghetti code a term used to describe unstructured and
hard-to-maintain code.

Code reusability
Before the advent of front-end libraries, reusing UI
components involved complex inheritance patterns or
writing the same code repeatedly. This made codebases
bulky and updates tedious.

Scalability
As applications grew, managing state and keeping the UI
consistent became a significant challenge. Traditional
JavaScript lacked a structured approach for handling
complex UIs.
These are just a few of the major challenges developers
encountered while coding in traditional JavaScript.
Vue
Shortly after React’s debut, Evan You introduced Vue in
2014. A former Google engineer, You sought to overcome
the constraints of existing frameworks like AngularJS. He
envisioned a lightweight solution that was simpler to learn
and use than the more complex frameworks of the era, one
that could readily adapt to diverse project requirements
and development methodologies.
Earlier, we explored the challenges encountered by
developers before React’s introduction in 2013. While
React addressed these issues, it still presented a slight
learning curve and followed an opinionated approach to
application structure. Vue, inspired by React, emerged as a
more approachable and adaptable alternative.
Vue adopted a more gradual learning curve by offering a
simpler syntax and API compared to React, making it more
accessible for developers familiar with native HTML, CSS,
and JavaScript. Additionally, Vue provides options for data
binding (one-way or two-way) and component
communication, accommodating various development
styles and project needs. Like React, Vue also utilizes the
virtual DOM approach for efficient updates and seamless
UI rendering.
Moreover, Vue popularized the Single File Component
(SFC) approach, allowing all necessary entities—HTML,
CSS, and JavaScript—to be stored in a single file. This
promotes better code organization and maintainability.
The simplicity and effectiveness of React and Vue
resonated with the web development community at large.
Despite their distinct approaches to problem solving, both
resulted in faster and more efficient UI rendering,
improved code structure, and code that is easier to read
and maintain. This proved to be a significant advantage for
enterprise-level applications, leading to swift adoption by
developers.
As of 2024, a majority of the world’s top web applications
are built on these two front-end libraries, underscoring
their widespread acceptance and impact on the industry.

Design Patterns with Vue


Much like its counterpart React, [Link] does not strictly
enforce any particular Design Pattern, but it does provide
features that naturally lend themselves to certain patterns.
Here are some common features of Vue that act as a
Design Pattern:

Components and SFCs


Like React, Vue also adopts the concept of component-
based architecture. Applications are constructed by
assembling reusable, self-contained components, each
dedicated to a specific part of the user interface. However,
unlike React, where components often utilize JSX—a syntax
that blends JavaScript and HTML—Vue components
typically take the form of SFCs. These SFCs consolidate
HTML, JavaScript, and CSS within a single file, enhancing
component encapsulation and simplifying management.
This adherence to the separation of concerns principle
promotes code organization and maintainability in Vue
applications.
Let us take a quick look at what a component in Vue looks
like:
1. <!-- [Link] -->
2. <template>
3. <div class="blog-post">
4. <h3>{{ title }}</h3>
5. <p>{{ content }}</p>
6. </div>
7. </template>
8.
9. <script>
10. export default {
11. props: {
12. title: String, // Title of the blog post
13. content: String // Content of the blog post
14. }
15. };
16. </script>
17.
18. <style scoped>
19. .blog-post {
20. margin-bottom: 20px;
21. border: 1px solid #ccc;
22. padding: 10px;
23. }
24. </style>
In the provided example, we have crafted a BlogPost
component, designed to render a blog post, following the
SFC format. This structure is indicative of the approach
taken by Vue for component composition. Within the SFC,
the <template> tag contains the HTML markup,
interspersed with expressions enclosed in double curly
braces for data interpolation. The <script> tag
accommodates the logic and JavaScript functionality of the
component. Lastly, the <style> tag encapsulates the
component’s styling directives, ensuring a cohesive
presentation. This modular organization facilitates clarity
and maintainability within the component. To use this
above component, we do the following:
1. <!-- [Link] -->
2. <template>
3. <div>
4. <h2>My Blog</h2>
5. <BlogPost v-for="(post, index) in posts"
:key="index" :title="[Link]" :content="[Link]"
/>
6. </div>
7. </template>
8.
9. <script>
10. import BlogPost from './[Link]'; // Import
BlogPost component
11.
12. export default {
13. components: {
14. BlogPost // Register BlogPost component
15. },
16. data() {
17. return {
18. posts: [ // Array of blog posts
19. { title: 'First Post', content: 'This is the content of
my first post.' },
20. { title: 'Second Post', content: 'This is the content
of my second post.' },
21. { title: 'Third Post', content: 'This is the content
of my third post.' }
22. ]
23. };
24. }
25. };
26. </script>
In the Blog component, we have imported and registered
the BlogPost component, making it available for use within
the template. Following the component-based architecture
proposed by Vue, we have defined a local data property
within the component to manage its internal state,
analogous to React’s state property. This encapsulation
ensures that the data is scoped to the Blog component and
does not interfere with the state of other components.

Components as Design Patterns


Components in [Link] emulate several Design Patterns
commonly used in JavaScript, including:
Constructor Pattern
In JavaScript, objects can be created using constructor
functions, which act as blueprints for creating instances of
objects. Similarly, Vue components can be thought of as
constructors for UI elements. When we define a Vue
component, we are essentially creating a blueprint for that
UI element, and we can instantiate multiple instances of
that component throughout our application.
Factory Pattern
The Factory Pattern is a Design Pattern used to create
objects without specifying their exact type or class. In a
similar way, Vue components act as factories by defining a
blueprint for UI elements. Once a component is defined, it
can be reused across the application to create multiple
instances, ensuring consistency and avoiding redundant
code.
Module Pattern
The Module Pattern encapsulates private and public
members within a single object, providing a way to
organize and encapsulate code. Vue components
encapsulate their own state, behavior, and markup, which
aligns with the principles of the Module Pattern. Each
component acts as a self-contained module that
encapsulates its own functionality and can be reused
throughout the application.
Composite Pattern
The Composite Pattern is used to compose objects into tree
structures to represent part-whole hierarchies. Vue
components can be composed together to build complex
UIs from simpler, smaller parts. This hierarchical
composition of components mirrors the principles of the
Composite Pattern, where objects are composed
recursively to form larger structures.
By leveraging these Design Patterns, Vue components
provide a structured and modular approach to building UIs
in JavaScript applications. They promote code reusability,
encapsulation, and maintainability, making it easier to
develop and maintain complex applications over time.

State management using Vuex


As applications grow in size and complexity, managing
state within individual components can become
challenging. As illustrated in the previous chapter, Chapter
6, Design Patterns in React, Figure 6.4, as the DOM tree
expands, passing information between components (which
is the crucial for a smooth application behavior) becomes
increasingly complicated. In React, developers commonly
use the context API or third-party libraries like Redux to
maintain a global state accessible across components.
Similarly, for [Link] applications, Vuex provides a state
management pattern and library, offering a centralized
store for managing the application state. It acts as a
centralized store for all the components in an application.
Let us have a quick look at an example of Vuex in action:
1. // store/[Link]
2.
3. import Vue from 'vue';
4. import Vuex from 'vuex';
5.
6. [Link](Vuex);
7.
8. export default new [Link]({
9. state: {
10. count: 0
11. },
12. mutations: {
13. increment(state) {
14. [Link]++;
15. },
16. decrement(state) {
17. [Link]--;
18. }
19. },
20. actions: {
21. increment({ commit }) {
22. commit('increment');
23. },
24. decrement({ commit }) {
25. commit('decrement');
26. }
27. },
28. getters: {
29. getCount(state) {
30. return [Link];
31. }
32. }
33. });
34.
In the provided example, a Vuex store is initialized using
the [Link] method. Within the store configuration, a
global state is defined, which includes a variable count
representing the counter. Mutations are declared to modify
the state, including an increment mutation to increase the
count variable and a decrement mutation to decrease it.
Actions are defined as intermediaries between components
and the store, facilitating asynchronous operations or
complex state changes. Getters are implemented to
retrieve values from the global state.
Now, let us have a Vue component access the global state
provided by Vuex:
1. <!-- [Link] -->
2.
3. <template>
4. <div>
5. <h2>Counter: {{ count }}</h2>
6. <button @click="increment">Increment</button>
7. <button @click="decrement">Decrement</button>
8. </div>
9. </template>
10.
11. <script>
12. export default {
13. computed: {
14. count() {
15. return this.$[Link];
16. }
17. },
18. methods: {
19. increment() {
20. this.$[Link]('increment');
21. },
22. decrement() {
23. this.$[Link]('decrement');
24. }
25. }
26. };
27. </script>
In the provided code, the component accesses, and
dispatches actions from the Vuex store to increment and
decrement a value. Thanks to Vuex, there is no longer a
need to pass props to components at different levels of the
DOM tree. Unlike React’s Context API, there is no need to
wrap components with specific tags to connect them to the
store. Vue components can seamlessly access the global
state and dispatch actions without any additional setup.

Vuex as Design Patterns


Vuex integrates several Design Patterns commonly used in
JavaScript, making it a structured and efficient state
management solution for [Link] applications. These
patterns work together seamlessly to ensure centralized
and reactive state handling.
Singleton Pattern:
The Vuex store is implemented as a singleton object,
ensuring that only one instance of the store exists
throughout the application. This guarantees that all
components share and access the same state, promoting
consistency in state management.
Observer Pattern:
Building on the centralized nature of the singleton, Vuex
also leverages the Observer Pattern for its reactivity
system. When the state in the store changes, all
components dependent on that state are automatically
updated. This ensures that the user interface stays in sync
with the data, offering a smooth and reactive user
experience.
Facade Pattern:
To further simplify state interactions, Vuex employs the
Façade Pattern. Instead of exposing the internal
complexities of state management, Vuex provides a clean
and unified interface through actions, mutations, and
getters. This allows components to focus on their
functionality without being burdened by the underlying
state-handling logic.
By combining these Design Patterns, Vuex offers a
powerful and scalable solution for managing state in [Link]
applications, enhancing code organization, maintainability,
and reusability.

Custom directives and mixins


In [Link], custom directives and mixins serve as powerful
tools for implementing Design Patterns and enhancing
reusability in your code. Directives allow us to abstract
away DOM manipulation logic from the components,
promoting a cleaner and more declarative codebase.
Custom directives encapsulate specific behaviors that can
be applied to elements in our templates. By encapsulating
DOM manipulation logic within directives, we can separate
concerns between your component’s business logic and its
presentation layer, leading to more maintainable code. To
add to that, custom directives promote reusability.
Mixins allow us to encapsulate and reuse code across
multiple components. This is particularly useful for sharing
common functionality such as methods, computed
properties, or lifecycle hooks. Mixins promote the
composition of behavior, allowing us to mix in functionality
from multiple sources into a single component. This is more
flexible than inheritance and helps avoid the issues
associated with deep component hierarchies. Mixins allow
us to dynamically inject behavior into components at
runtime, giving a greater flexibility in modifying and
extending component functionality.
Now, let us see these features in action. To understand
these features better, we will be considering the scenario
of a form validation. We want to implement a consistent
validation approach across all forms to ensure unified user
experience.

Custom directive approach


We can create a custom directive called v-validate that
attaches validation logic to form inputs. This directive can
perform validation checks based on certain rules and
display error messages accordingly:
1. // Custom directive definition
2. [Link]('validate', {
3. bind(el, binding, vnode) {
4. // Validation logic here
5. [Link]('blur', () => {
6. // Perform validation
7. if (!isValid) {
8. // Show error message
9. [Link].$emit('show-error', errorMessage);
10. }
11. });
12. }
13. });
In the provided code snippet, a custom directive named
validate is defined. This directive is designed to attach a
blur event listener to the element it is applied to and
trigger a callback function for validation. If the validation
fails, the directive has the capability to emit an error
message. Using this in a component is extremely simple:
1. <input type="text" v-validate="validateRules">

Mixins approach
The mixin approach is easier to comprehend than the
directive approach. We can create a validation mixin that
provides reusable validation methods and error handling
logic. This mixin can be applied to any component that
needs form validation:
1. // Validation mixin
2. const ValidationMixin = {
3. methods: {
4. validate() {
5. // Validation logic
6. if (!isValid) {
7. [Link](errorMessage);
8. }
9. },
10. showError(message) {
11. // Show error message
12. }
13. }
14. };
In the provided code snippet, a mixin for validation is
created, encapsulating methods intended for validation and
error handling. The mixin contains a methods property
housing the validate method for handling validation and
the showError method for managing errors. To utilize this
mixin, it can be imported and added to a component’s
properties:
1. import ValidationMixin from './ValidationMixin';
2.
3. export default {
4. mixins: [ValidationMixin],
5. // Component definition
6. }
7.
Finally, we can use it in the component like this:
1. <input type="text" @blur="validate">

Custom directives and mixins as Design Patterns


Custom directives and mixins in [Link] emulate several
Design Patterns commonly used in JavaScript, including:
Decorator Pattern
Custom directives can be used to decorate elements with
additional behavior or styling. For example, we can create
a v-tooltip directive to add tooltip functionality to
elements. Mixins, meanwhile, can be used to dynamically
add behavior or properties to components. This is similar to
the Decorator Pattern, where functionality is added to
objects dynamically.
Observer Pattern
Both custom directives and mixins can be used for
observing changes to DOM elements and component data
respectively, and trigger actions accordingly. They can be
used to add reactive behavior to components, similar to
how observers react to changes in the state of an object.
Strategy Pattern
Custom directives can be used to implement different
strategies for handling specific behaviors. For example, we
can create multiple directives for validating form inputs
using different validation strategies (e.g., required, email,
numeric). Similarly, mixins can encapsulate different
strategies for handling specific functionality and apply
them to components as needed. This allows us to switch
between different strategies dynamically based on the
component’s requirements.
By leveraging custom directives and mixins in [Link], we
can emulate these Design Patterns to create more modular,
maintainable, and reusable code that adheres to best
practices in software development.

Popular Design Patterns specific for Vue


[Link], being a versatile and powerful front-end framework,
supports various Design Patterns to organize and structure
the code effectively. Some popular Design Patterns specific
to [Link] include:

Computed properties and watchers


Computed properties in Vue are indeed powerful and often
overlooked as a Design Pattern. However, their misuse can
lead to unintended consequences. Computed properties are
defined within the computed property of a Vue component
and are used for performing calculations or
transformations on existing data. Computed properties are
only re-evaluated when their dependencies change,
ensuring efficient computation. It is crucial to note that the
result of computed properties is cached until their
dependencies change, preventing unnecessary re-
evaluation on each render cycle. Thus, computed properties
offer a powerful tool for managing derived data in Vue
applications, enhancing performance and maintainability
when used appropriately.
Watchers in [Link] allow us to perform asynchronous or
expensive operations in response to changes in data. Unlike
computed properties, which are synchronous, watchers
provide more flexibility and control over how you respond
to changes in data. Watchers are defined in the watch
property of a Vue component. Their job is to keep an eye
out for any changes that happen to a particular variable
and then take necessary action.
Let us understand the difference between the two with an
example:
1. // Vue component
2. export default {
3. data() {
4. return {
5. firstName: 'John',
6. lastName: 'Doe',
7. };
8. },
9. computed: {
10. fullName() {
11. return `${[Link]} ${[Link]}`;
12. },
13. },
14. watch: {
15. firstName(newValue, oldValue) {
16. [Link](`firstName changed from ${oldValue}
to ${newValue}`);
17. },
18. lastName(newValue, oldValue) {
19. [Link](`lastName changed from ${oldValue}
to ${newValue}`);
20. },
21. },
22. };
In this example, we have a Vue component with firstName
and lastName data properties. We also have a computed
property fullName which concatenates firstName and
lastName. Additionally, we have watchers set up to log
messages whenever firstName or lastName changes. The
difference between the two is that the computed property
will trigger its invocation only when either of the
firstName or the lastName property changes. However,
watchers will run every time to check if the newValue is
not equal to the oldValue. Only when the two values are
not equal, the code inside the property will be executed.
Computed properties are useful when you need to derive a
value from existing data. For instance, if you have a
firstName and lastName in your data, you can use a
computed property to concatenate them into a fullName.
Watchers are useful when you need to perform some action
or computation in response to changes in specific data
properties. For example, you might want to make an HTTP
request whenever a certain data property changes.

Advantages
Both computed properties and watchers offer the following
advantages in building Vue applications:
• Performance optimization: By offloading expensive
computations from the template and leveraging the
reactivity system of Vue, computed properties
contribute to overall performance optimization. They
ensure that computations are only performed when
necessary, reducing unnecessary overhead.
• Code reusability: Computed properties allow us to
encapsulate common calculations or transformations in
one place, making it easy to reuse them across multiple
components. This promotes code reusability and helps
follow the Don’t Repeat Yourself (DRY) principle.
• Dependency tracking: [Link] automatically tracks
dependencies between computed properties and
reactive data. If any of the dependencies change, Vue
knows to recompute the computed property. This
eliminates the need for manual dependency
management and ensures that your computed
properties always reflect the latest data state.
• Asynchronous operations: Watchers allow us to
perform asynchronous operations in response to
changes in data. This is particularly useful for scenarios
like making HTTP requests, performing animations, or
updating external libraries asynchronously.
• Fine-grained control: Watchers provide fine-grained
control over how you respond to data changes. You can
execute different actions based on the old and new
values of the watched property, enabling you to
implement complex logic tailored to your specific
requirements.
• Dynamic responses: Watchers allow us to
dynamically respond to changes in data by executing
custom logic. This flexibility enables us to implement
dynamic behaviors, such as conditionally triggering
actions or adjusting UI elements based on the observed
data changes.
In summary, computed properties and watchers in [Link]
offer several advantages, including improved performance,
code readability, reusability, and fine-grained control over
data-driven behaviors. They are essential tools for
managing complex data transformations, asynchronous
operations, and dynamic responses in [Link] applications.
Scoped styles
Scoped styles in [Link] provide a mechanism for defining
component-specific styles that are isolated from other
components in the application. This ensures that styles
defined within a component do not inadvertently affect
other parts of the application. To illustrate this concept,
consider a traditional web application where multiple style
files are used to manage CSS changes. However, as the
application grows larger, managing styles across different
components becomes increasingly complex. Additionally, a
seemingly innocuous change to a CSS property, such as h1,
in one file can unintentionally alter the styling of all h1
elements throughout the application. Scoped styles in
[Link] address these challenges by encapsulating styles
within individual components, enhancing modularity and
reducing the risk of unintended styling side effects.
Here is how scoped styles work in Vue:
In [Link], each component typically has its own template,
JavaScript logic, and styles. Scoped styles allow you to
define CSS styles that are only applied to the HTML
elements within that component’s template. When we
define styles for a component in Vue, by default, those
styles are global, meaning they could affect other
components. However, when you add the scoped attribute
to the <style> tag of a component, Vue automatically
scopes those styles so that they only apply to the elements
in that component’s template. Vue achieves scoped styles
by automatically adding a unique identifier to the CSS
selectors within a scoped <style> block. This unique
identifier is typically a data attribute that is added to the
root element of the component’s template. So, when we
define a CSS rule like h1 { color: red; } within a scoped
<style> block, Vue will automatically transform it into
something like [data-v-xxxxxx] h1 { color: red; }, where
xxxxxx is a unique identifier.
1. <template>
2. <div class="example">
3. <h1>Hello, world!</h1>
4. </div>
5. </template>
6.
7. <script>
8. export default {
9. name: 'ExampleComponent'
10. }
11. </script>
12.
13. <style scoped>
14. /* These styles will only apply to the h1 element within
this component */
15. h1 {
16. color: red;
17. }
18. </style>
In this example, the color: red; style will only be applied to
the <h1> element within this component because of the
scoped attribute on the <style> tag. It would not affect
any other <h1> elements in your application.

Advantages
Scoped styles offer the following advantages in building
Vue applications:
• Modularity: Scoped styles promote modularity by
encapsulating styles within individual components.
Each component can have its own styles without
worrying about conflicts with styles from other
components. This makes it easier to reason about and
maintain your styles, especially in larger applications
with many components.
• Isolation: Scoped styles provide isolation for your
components, ensuring that the styles you define would
not unintentionally affect other parts of your
application. This isolation helps prevent style conflicts
and makes it easier to develop and maintain complex
user interfaces.
• Performance: Scoped styles can improve performance
by reducing the amount of CSS that needs to be
processed by the browser. Since scoped styles only
apply to specific components, the browser doesn’t need
to parse and apply styles that are not relevant to the
current component, resulting in faster rendering times.
Overall, scoped styles in [Link] offer a powerful way to
manage and maintain styles in your application, promoting
modularity, isolation, predictability, reusability, and
performance. By encapsulating styles within individual
components, scoped styles help you build more
maintainable and scalable user interfaces.

Dynamic component loading


Dynamic component loading is a powerful Design Pattern
in [Link] that allows us to load components dynamically at
runtime, based on certain conditions or user interactions.
This approach is particularly useful when you have
components that are not always needed or when you want
to load components asynchronously to improve
performance.
There are several ways to dynamically load components in
Vue:
1. The bind directive: Vue provides a built-in directive
called v-bind:is which allows us to dynamically bind a
component to a variable or expression. This variable or
expression evaluates to the name of the component you
want to render.
1. <component v-bind:is="componentName">
</component>
2. Dynamic import: With ES6 dynamic import syntax,
you can asynchronously load components at runtime.
Vue supports this natively using import() function.
1. const MyComponent = () =>
import('./[Link]');
3. Async components: Vue allows us to define
components asynchronously using a factory function.
This function returns a Promise that resolves to the
component definition.
1. [Link]('async-component', () =>
import('./[Link]'));
Using these methods, we can conditionally load
components based on user interactions, data from APIs, or
any other runtime conditions. For example, we can load
different components based on the route the user navigates
to:
1. const router = new VueRouter({
2. routes: [
3. { path: '/home', component: () =>
import('./[Link]') },
4. { path: '/about', component: () =>
import('./[Link]') },
5. { path: '/contact', component: () =>
import('./[Link]') }
6. ]
7. });
8.
Dynamic component loading is particularly beneficial for
large applications where loading all components upfront
might impact performance negatively. By loading
components only when needed, you can improve the initial
loading time and reduce the overall bundle size of your
application.

Using JavaScript Design Patterns with Vue


[Link] is a versatile framework that allows us to employ
various JavaScript Design Patterns to organize and
structure our code effectively. Here is how we can apply
some traditional JavaScript Design Patterns within a [Link]
application:

Singleton Pattern
We can create singleton objects in [Link] using either
global variables or Vue instances. For example, we might
use a Singleton Pattern to manage global application state
or to create a centralized event bus:
1. // Singleton Pattern with a global variable
2. const EventBus = new Vue();
3.
4. // Usage
5. EventBus.$emit('event', data);
As shown in this code, this EventBus variable can be used
anywhere across the application, and it will only have a
single point of reference always.

Module Pattern
Similar to React, Vue allows for the encapsulation of
related functionality into modules using components. Each
Vue component serves as a module with its own
encapsulated data, methods, and computed properties. As
we have seen previously, Vue components are self-
contained entities with their own set of properties, data,
and styling. Despite their self-contained nature, these
components can seamlessly compose together to construct
more complex and feature-rich user interfaces. This
modular approach enhances code organization, reusability,
and maintainability in Vue applications, mirroring the
benefits offered by React components.

Factory Pattern
Like React, we can use factory functions to create Vue
components dynamically based on certain criteria:
1. function createComponent(type) {
2. if (type === 'button') {
3. return [Link]('my-button', {
4. // button component options
5. });
6. } else if (type === 'input') {
7. return [Link]('my-input', {
8. // input component options
9. });
10. }
11. }
12.
13. // Usage
14. const ButtonComponent =
createComponent('button');
As shown in the above code, we have a factory function that
takes in a type and returns a Vue component, serving the
exact purpose of the Factory Design Pattern.

Observer Pattern
[Link] inherently uses the Observer Pattern with its
reactivity system. Data properties in Vue components are
automatically observed, and any changes trigger re-
renders. Additionally, we have seen the usage of watchers
and computed properties that also follow the principles of
Observer Pattern by observing a change and then
triggering a re-render if required.
By leveraging these Design Patterns, we can effectively
structure and manage our [Link] applications, making them
more modular, maintainable, and scalable.

Conclusion
In this chapter, we delved into understanding one of the
two most prominent modern JavaScript frameworks: Vue.
Given that a vast majority of web applications, as of 2024,
are built using Vue, we explored ways to enhance its
efficiency through the application of Design Patterns.
We examined the built-in patterns, such as components and
SFCs, Vuex for state management, custom directives, and
mixins. Subsequently, we explored Vue-specific Design
Patterns like computed properties and watchers, scoped
styles, and dynamic component loading. Finally, we
discussed the integration of traditional JavaScript Design
Patterns with [Link].
In summary, much like its counterpart React, Vue was
designed to offer numerous advantages over the usage of
traditional JavaScript. Its application and implementation
help optimize performance, and aids in streamlining code
and enhancing its readability. Vue has been built in such a
fashion that it inherently adopts Design Patterns that are
on the same lines with established JavaScript principles. By
introducing JavaScript Design Patterns into Vue, we further
enhance the effectiveness of Vue.
We have now read about basic Design Patterns and their
applications in modern UI libraries and frameworks like
React and Vue. In the next chapter, we will be delving into
the practical applications of advanced Design Patterns.

Points to remember
• React and Vue emerged as solutions to address the
challenges encountered by developers when using
traditional JavaScript. By minimizing the need for direct
DOM manipulation, they significantly enhanced code
reusability. Consequently, they facilitated the
development of larger and more intricate web
applications with greater efficiency and scalability.
• Vue adheres to component-based architecture, wherein
each component serves as a standalone entity
comprising its own encapsulated logic, data, and
properties. These components are akin to LEGO blocks,
capable of being interconnected to construct intricate
applications. Their presence inherently embodies
various Design Patterns such as the Modular Pattern,
Singleton Pattern, Strategy Pattern, and Composite
Pattern, among others, thereby promoting code
modularity and reusability.
• Vue leverages custom directives, mixins, computed
properties, watchers, scoped styles, and dynamic
component loading to enhance coding efficiency. These
built-in or Vue-specific Design Patterns enable
developers to craft maintainable and robust Vue code,
further streamlining development workflows and
improving application performance.
• In conclusion, whether through the native capabilities
or through patterns specific to its ecosystem, Vue can
leverage traditional JavaScript Design Patterns to
enhance code development. These patterns serve as
tried and true strategies for optimizing application
architecture, promoting reusability, and improving
overall code quality in both frameworks.

Exercises
1. Which traditional JavaScript Design Pattern is
best emulated by the concept of custom directives
in Vue?
a. Singleton Pattern
b. Strategy Pattern
c. Modular Pattern
d. Decorator Pattern
2. Which of the following statement is not true?
a. The in-built computed properties offered by Vue
components can track changes on certain variables
and trigger re-rendering if required.
b. Redux helps in maintaining the reactivity of
components in Vue.
c. We can implement the Singleton Pattern in Vue by
creating global variables.
d. Scoped styles in Vue suggest that a given set of styles
can only be applicable to a single component without
it affecting any other components in the application.
3. Which of the following use cases best describe the
implementation of the Façade Design Pattern in
Vue?
a. Implementing Vuex to maintain a global state that can
be used by applications to render the UI.
b. Loading components dynamically to avoid importing
them when they are not required.
c. Using computed properties to keep a track of changes
and invoking functions for re-rendering, if required.
d. Creating atomic components to build up a complex
piece of UI.

Answers
1. d
2. b
3. a
CHAPTER 8
Advanced Design Patterns
in JavaScript

Introduction
In this chapter, we explore advanced Design Patterns in
JavaScript, focusing on the Module Pattern, MV (Model-
View) patterns and their variations, and the art of
combining multiple Design Patterns for robust and scalable
solutions. The Module Pattern harnesses closures to create
encapsulated modules, while MV patterns like Model-
View-Controller (MVC), Model-View-Presenter (MVP),
and Model-View-Mediator-Controller (MVMM) offer
structured approaches to organizing code. By mastering
these patterns and their combinations, developers can
architect flexible, maintainable, and efficient JavaScript
applications, ensuring clean separation of concerns and
promoting code reusability.

Structure
This chapter will cover the following topics:
• Introduction to advanced Design Patterns
• Module Pattern
• MV* patterns
• Combining Design Patterns

Objectives
By completing this chapter, we will officially transition into
the expert stage of writing about JavaScript Design
Patterns. By finishing this chapter, we will delve into more
advanced concepts in JavaScript Design Patterns. These
insights will enable us to develop modern enterprise-level
JavaScript applications using contemporary JavaScript
frameworks. We will gain the ability to systematically
organize code with greater clarity and effectiveness.

Introduction to advanced Design Patterns


Having mastered the fundamentals of Design Patterns –
Creational, Structural, and Behavioral Design Patterns,
that form the foundations of object-oriented JavaScript, we
are now ready to tackle more intricate problems. In this
chapter, we will delve into the realm of advanced Design
Patterns, specially concentrating on those that address
complex object behavior, communication, and interaction.
As projects grow in size and complexity, the code becomes
difficult to understand, modify and maintain. Advanced
Design Patterns emerge as powerful tools to address these
challenges with well-established, reusable solutions. In this
chapter, we will be focusing solely on three areas: Module
Pattern, MV* (Model-View-X) patterns and combining
Design Patterns. Module Pattern helps in code organization
by encapsulating code within reusable modules. Taking a
leaf out of the Angular books, MV* concentrates on
separating the presentation (View), data (Model) and user
interactions (Controller). For the final part, we will be
combining different patterns to create powerful and flexible
solutions. Without much further ado, let us delve into the
realm of advanced Design Patterns.

Module Pattern
In earlier chapters, we have encountered numerous
mentions of this pattern. The inherent functionality of
modern JavaScript frameworks like React and Vue closely
mirrors this Design Pattern. The Module Design Pattern in
JavaScript serves as a method for encapsulating code into
self-contained units known as modules. This approach
encourages modularity and compartmentalization,
enhancing code manageability, comprehension, and
maintenance. Modules can contain private members and
methods that are shielded from external access, exposing
only essential functionality through a public interface. In
this book, we will explore the Revealing Module Pattern,
one of the most important variants of the Module Pattern
that is not dependent on any third-party library.

Revealing Module Pattern


The Revealing Module Design Pattern encapsulates
functionality within JavaScript, offering a clean and
organized way to structure code. It is particularly useful for
creating modules with private and public methods and
variables, ensuring a clean namespace and preventing
conflicts.
For instance, imagine you are building a utility library with
helper functions. Using this pattern, you can define private
methods for internal calculations and expose only the
public methods that need to be accessed externally. This
not only maintains a clean namespace but also provides
better control over the module’s functionality and prevents
accidental misuse of internal logic. The Revealing Module
Design Pattern operates on these two JavaScript principles:
• Encapsulation: Encapsulation is a software
engineering concept that aims to bundle data and
methods that operate on the data into a single unit,
hiding the internal implementation details from the
outside world.
• Closures: Closures allow inner functions to have
access to the variables and parameters of the outer
function, even after the outer function has finished
executing. This mechanism is crucial for implementing
private variables and methods in JavaScript.

Implementation
The Revealing Module Design Pattern typically involves
defining an anonymous function that returns an object
containing the methods and properties that we want to
expose publicly. Within this function, we define any private
variables or functions that are only accessible within the
module. Then, we selectively reveal the public members of
the module by attaching them to the returned object:
1. const AuthModule = (() => {
2. // Private variables
3. let loggedIn = false;
4. let username = '';
5.
6. // Private function
7. function login(user) {
8. loggedIn = true;
9. username = user;
10. [Link](`${username} logged in
successfully.`);
11. }
12.
13. function logout() {
14. loggedIn = false;
15. username = '';
16. [Link]('Logged out successfully.');
17. }
18.
19. // Public members (revealed)
20. return {
21. isLoggedIn: () => loggedIn,
22. getUsername: () => username,
23. login,
24. logout
25. };
26. })();
27.
28. // Usage
29. [Link]([Link]()); // Output:
false
30. [Link]('john_doe');
31. [Link]([Link]()); // Output: true
32. [Link]([Link]()); // Output:
"john_doe"
33. [Link]();
34. [Link]([Link]()); // Output:
false
In the provided code example, we have crafted a module
called AuthModule using an Immediately Invoked
Function Expression (IIFE) to encapsulate its
functionality.
An IIFE is a JavaScript function that is executed
immediately after it is defined. It is enclosed within
parentheses and followed by a pair of parentheses for
invocation, like this: (function() { ... })().
IIFEs are commonly used to create isolated scopes and
avoid polluting the global namespace. This is particularly
useful for defining modules, as it enables private variables
and functions to exist within the IIFE, while exposing only
the public interface.
Within the AuthModule, we have defined two private
variables, loggedIn and username, along with two
functions—login and logout—accessible only within the
module. The login function sets the loggedIn variable to
true and assigns the provided value to the private
username variable. Conversely, the logout function resets
these values.
Towards the end of the module, we reveal certain private
values by returning an object containing references to
these variables. This approach allows us to selectively
expose information to the external environment. As
demonstrated in the console statements below, when the
user is not logged in, the module exposes the isLoggedIn
value as false. Subsequently, after attempting to log in
using the module’s login method, the isLoggedIn value
switches to true, and the getUsername method returns
the username supplied during login. Likewise, upon
logging out, the values are reset, and isLoggedIn reverts
to false.
This example demonstrates how the Revealing Module
Pattern can be used to create a modular and reusable
authentication system in JavaScript, enhancing code
organization, encapsulation, and abstraction.

Advantages
The revealing module offers several advantages in making
code more efficient. Let us look at the various advantages
offered by Revealing Module Design Pattern:
• Encapsulation: In the Revealing Module Pattern,
private variables and functions are hidden from the
global scope, thereby preventing accidental
modification and interference.
• Name spacing: Modules help prevent naming conflicts
by encapsulating functionality within a specific scope.
Since every module is defined as a const, we cannot
have more than one module with the same name.
• Abstraction: By revealing only necessary methods and
properties, the complexity of the underlying
implementation is abstracted, promoting a cleaner
interface.
• Reusability: Modules can be easily reused across
different parts of an application or in other projects.
The inherent functionality of components in modern
JavaScript frameworks like React and Vue mirrors this
advantage the most and this feature of reusability
makes these two frameworks so popular amongst front-
end engineers.
• Testability: Encapsulated modules are easier to test
since their internal state and behavior can be isolated
from other parts of the codebase.

Disadvantages
While it offers numerous advantages, there are several
disadvantages as well to using this pattern:
• Increased memory usage: Each instance of a module
creates its own scope, potentially consuming more
memory.
• Difficulties with asynchronous operations:
Handling asynchronous operations within modules
might require additional complexity.
In summary, the Revealing Module Pattern is a useful
technique for structuring JavaScript code, providing
encapsulation, name spacing, abstraction, reusability, and
testability benefits. However, it is essential to consider its
limitations and suitability for specific use cases.

MV* patterns
The MV* Design Patterns in JavaScript represent a
systematic approach to structuring code in web
applications, aiming to improve maintainability, scalability,
and reusability. These patterns facilitate the separation of
concerns by dividing an application into distinct
components, each responsible for a specific aspect of the
application’s functionality.
At its core, MV* pattern separates an application into three
primary components (as you can find from its
abbreviation):
• Model: The Model represents the data and the
business logic of the application. It encapsulates the
application’s state and behavior, providing methods for
data manipulation and validation. In JavaScript, Models
are often implemented as classes or data structures that
manage the application’s data.
• View: The View is responsible for presenting the user
interface to the user. It displays data from the Model
and provides a means for users to interact with the
application. Views in JavaScript are typically
implemented using HTML templates combined with
CSS styles for presentation.
• Controller/ViewModel/Presenter: The Controller
acts as an intermediary between the Model and the
View. It receives input from the user through the View,
processes that input, and updates the Model
accordingly. It also updates the View to reflect changes
in the state of the Model. The specific responsibilities of
the Controller can vary depending on the chosen MV*
variant.
By separating concerns in this way, MV* patterns promote
code organization, making it easier to understand,
maintain, and extend the application over time.
Additionally, these patterns encourage code reusability, as
components can be more easily isolated and tested
independently.
In this section, we will be looking at three variants of the
MV* pattern where we replace the * with the respective
module. We will be looking at the MVC, MVP, and MVMM
pattern.

MVC pattern
The initial variation of the MV* pattern we will examine is
the MVC Design Pattern. In the preceding section, we
thoroughly explored and grasped the definition and
responsibilities of each component within the MV* pattern.
MVC divides an application’s concerns into three primary
components: Model, View, and Controller.
In the MVC pattern, the Model component oversees data
validation, manipulation, and storage independently of the
user interface (View) and user input handling (Controller).
The View component presents data to the user and
captures user interactions (input). Meanwhile, the
Controller manages the application’s flow and business
logic, determining actions based on user input and
updating the Model accordingly. In MVC, the Controller
can be implemented as event handlers or functions that
respond to user input, such as button clicks or form
submissions.
The general flow of the MVC pattern proceeds as follows:
• The user interacts with the View (e.g. clicks a button).
• The View sends a notification (event) to the Controller,
indicating the user’s action.
• The Controller receives the notification, processes it,
and decides what action to take.
• The Controller may interact with the Model to retrieve
or update data based on the user’s action.
• Once the Model is updated, the Controller notifies the
View to update the presentation accordingly.
• The View retrieves the updated data from the Model
and re-renders itself to reflect the changes, completing
the cycle.
Let us understand this with a simple example:
Model:
1. class TodoModel {
2. constructor() {
3. [Link] = [];
4. }
5.
6. addTodo(todo) {
7. [Link](todo);
8. }
9.
10. removeTodo(index) {
11. [Link](index, 1);
12. }
13.
14. getAllTodos() {
15. return [Link];
16. }
17. }
View:
1. class TodoView {
2. constructor() {
3. [Link] = [Link]('todo-
list');
4. }
5.
6. renderTodos(todos) {
7. [Link] = todos
8. .map((todo, index) => `<li>${todo} <button data-
index=»${index}">Remove</button></li>`)
9. .join('');
10. }
11. }
Controller:
1. class TodoController {
2. constructor() {
3. [Link] = new TodoModel();
4. [Link] = new TodoView();
5. [Link]();
6. }
7.
8. bindEvents() {
9. const addButton = [Link]('add-
button');
10. const inputField =
[Link]('todo-input');
11.
12. // Add Todo Event
13. [Link]('click', () => {
14. const todoText = [Link]();
15. if (todoText) {
16. [Link](todoText);
17. [Link]([Link]());
18. [Link] = '';
19. }
20. });
21.
22. // Remove Todo Event (Event Delegation)
23. [Link]('click', (event)
=> {
24. if ([Link] === 'BUTTON') {
25. const index =
parseInt([Link]('data-index'), 10);
26. [Link](index);
27. [Link]([Link]());
28. }
29. });
30. }
31. }
32.
33. // Initialize the application
34. const controller = new TodoController();
Sample HTML structure:
1. <!DOCTYPE html>
2. <html lang="en">
3. <head>
4. <meta charset="UTF-8">
5. <meta name="viewport" content="width=device-
width, initial-scale=1.0">
6. <title>Todo MVC Example</title>
7. </head>
8. <body>
9. <div>
10. <input type="text" id="todo-input"
placeholder="Enter a new todo" />
11. <button id="add-button">Add</button>
12. </div>
13. <ul id="todo-list"></ul>
14. <script src="[Link]"></script>
15. </body>
16. </html>
In the provided code, we have employed a straightforward
example of a to-do application, allowing users to add and
remove items from their to-do lists. As illustrated, we have
developed three distinct components:
• Model (ToDoModel): This component stores all the
to-dos and provides functions to manipulate this data. It
is responsible for managing the to-do data
independently.
• View (ToDoView): The View renders the list of to-dos
stored in the ToDoModel. It is responsible for
displaying the to-dos to the user.
• Controller (ToDoController): This component
interacts with both the View and the Model. It exposes
an event listener capable of detecting button clicks and
adding new items to the to-do list.
The renderTodos method is part of the View in the MVC
pattern. It is responsible for updating the DOM to reflect
the current state of the to-do list.
• It takes an array of todos as input.
• It uses the map method to generate an HTML string
for each to-do, including a Remove button with a data-
index attribute to identify the to-do’s index.
• The generated HTML is joined into a single string and
set as the innerHTML of the todoList element.
This ensures that every time the to-do list changes (e.g.,
adding or removing a to-do), the View is updated to reflect
the latest state of the application.
These components collectively facilitate the functionality of
the to-do application, maintaining a clear separation of
concerns and enhancing maintainability and scalability.

Advantages
The MVC pattern offers the following advantages in writing
JavaScript applications:
• Separation of concerns: MVC separates different
aspects of an application, making it easier to maintain
and update code because changes in one component do
not directly affect others.
• Reusability: Each component in MVC can be reused in
different parts of the application or in different
applications altogether, promoting code reuse and
reducing redundancy.
• Testability: Because of its modular structure, each
component in MVC can be tested independently,
allowing for more thorough and efficient testing of the
application.
• Scalability: MVC helps in scaling the application by
organizing code into manageable components, making
it easier to add new features or modify existing ones
without causing significant disruptions.

Disadvantages
There are also a few disadvantages of using the MVC
pattern:
• Complexity: Implementing MVC in smaller
applications might introduce unnecessary complexity. It
is essential to assess the size and requirements of the
project before opting for MVC.
• Potential overhead: In smaller applications, the
overhead of implementing MVC might outweigh its
benefits. MVC is best suited for larger, more complex
applications where the separation of concerns is crucial.
• Learning curve: New developers may initially
struggle to understand the connection and interaction
among the three parts of the MVC pattern.

MVP pattern
The second variation of the MV* pattern that we will
explore is the MVP pattern. As we can see, the primary
difference between the MVC and the MVP patterns is in the
final component. Instead of the Controller in MVC, the MVP
pattern uses a Presenter to complement the Model and the
View, making it the Model-View-Presenter pattern.
MVP is a Design Pattern commonly used in JavaScript
applications, particularly in frameworks like AngularJS.
Much like its counterpart, the MVC pattern, the Model and
View components retain similar responsibilities. The Model
represents the data and business logic of the application. It
manages the application’s data, processes it, and enforces
business rules. The View represents the user interface
(UI) of the application, responsible for displaying data to
the user and capturing input.
The final piece of the puzzle is the Presenter component.
Similar to the Controller in the MVC pattern, the Presenter
acts as an intermediary between the Model and the View.
However, in MVP, the Presenter is specifically responsible
for handling user interactions with the View. It fetches data
from the Model and updates the View accordingly. Like the
Controller, the Presenter communicates directly with both
the View and the Model, ensuring that the View and Model
remain unaware of each other.

MVC vs. MVP Design Patterns


Before we examine a code example demonstrating the
implementation of the MVP Design Pattern, it’s crucial to
understand the differences between the MVC and MVP
Design Patterns to clarify any potential doubts.
The first difference lies in the role of the View
component. In MVC, the View is more active and can
directly observe the Model, updating itself based on
changes in the Model. In contrast, in MVP, the View is
passive and relies on the Presenter to update it. The View
doesn’t directly observe the Model.
Next, we need to understand the crucial difference
between the Presenter and the Controller. The
Controller in MVC handles user input and updates the
Model but doesn’t update the View directly. The Presenter
in MVP handles both updating the Model and updating the
View, acting as a mediator between the two.
The final difference between the two patterns is in the flow
of interaction. In MVC, the user interacts with the View |
The View notifies the Controller | The Controller updates
the Model | The Model notifies the View of the change | The
View then renders the update. In MVP, the user interacts
with the View | The View informs the Presenter | The
Presenter updates both the Model and the View.
Now, let us look at an example demonstrating how to
implement the MVP Design Pattern in JavaScript:
1. // Model
2. class Model {
3. constructor() {
4. [Link] = [];
5. }
6.
7. addData(item) {
8. [Link](item);
9. }
10.
11. getAllData() {
12. return [Link];
13. }
14. }
15.
16. // View
17. class View {
18. constructor() {
19. [Link] = [Link]('list');
20. [Link] = [Link]('input');
21. [Link] =
[Link]('addBtn');
22. }
23.
24. getInput() {
25. return [Link];
26. }
27.
28. render(data) {
29. [Link] = '';
30. [Link](item => {
31. const li = [Link]('li');
32. [Link] = item;
33. [Link](li);
34. });
35. }
36.
37. setAddButtonHandler(handler) {
38. [Link]('click', handler);
39. }
40. }
41.
42. // Presenter
43. class Presenter {
44. constructor(model, view) {
45. [Link] = model;
46. [Link] = view;
47.
48.
[Link]([Link]
nd(this));
49. [Link]();
50. }
51.
52. handleAddButton() {
53. const input = [Link]();
54. if (input) {
55. [Link](input);
56. [Link]();
57. }
58. }
59.
60. updateView() {
61. const data = [Link]();
62. [Link](data);
63. }
64. }
65.
66. // Initialization
67. [Link]('DOMContentLoaded', ()
=> {
68. const model = new Model();
69. const view = new View();
70. const presenter = new Presenter(model, view);
71. });
In the provided code, we have implemented the MVP
pattern in JavaScript. As shown, we have created three
separate classes for the Model, View, and Presenter
components.
First, let us look at the Model class, which represents our
Model component in the MVP Design Pattern. This class
initializes with an empty data array and contains methods
to add items to this data array and fetch the latest data. It
is a straightforward implementation that handles the data
management aspect of our application.
Next, let us examine the View class It is responsible for
displaying data and capturing user input but does not
contain any business logic. In the View class, we initialize
various elements (list, input, and button) as variables in the
constructor. The class includes a method to fetch the value
of the user input, a method to render the UI, and a method
to set up an event listener on the add button to handle click
events.
Finally, the Presenter mediates between the Model and
the View, handling user interactions and updating the
Model and View accordingly. In the Presenter class, we
first initialize the View and the Model in the constructor.
We then set the listener on the View by calling the
setAddButtonHandler method of the View and invoke the
updateView method to initialize the View with the current
data.
Next, we add the handleAddButton method, which
handles the click event for the add button. This method
retrieves the input value from the View, adds the input
value to the Model, and updates the View to reflect the new
state of the Model. Finally, we add the updateView
method, which fetches the current data from the Model and
passes it to the View for rendering.
To kickstart the entire process, we initialize the MVP
components and start the application is placed inside a
DOMContentLoaded event listener to ensure that the
DOM is fully loaded before accessing elements.

Advantages
The MVP pattern offers the following advantages in writing
JavaScript applications:
• Separation of concerns: MVP separates the
application into three distinct components — Model,
View, and Presenter. This separation makes the
codebase easier to understand, maintain, and extend
because each component has a specific responsibility.
• Flexibility and extensibility: Since the View is only
responsible for displaying data and capturing user
input, it can be easily replaced or updated without
affecting the underlying business logic (Presenter) or
data management (Model). This makes the application
more flexible and extensible.
• Testability: MVP facilitates unit testing because of its
clear separation of concerns. The business logic resides
in the Presenter, which can be tested independently of
the View and Model. This allows for easier testing with
mock objects or stubs.
• UI/UX improvements: MVP promotes cleaner and
more maintainable UI code by separating the
presentation logic (handled by the Presenter) from the
UI layout and styling. This separation allows UI/UX
designers and developers to work independently and
iterate on the user interface without affecting the
application’s logic.
• Reduced dependency: In MVP, the View does not
contain any business logic. This reduces dependencies
between the UI and the underlying application logic,
making it easier to change or update either component
without affecting the other.

Disadvantages
There are also a few disadvantages of using the MVC
pattern:
• Complexity: Implementing MVP requires additional
layers of abstraction (especially the Presenter)
compared to other patterns like MVC. This can lead to
increased complexity, especially in smaller projects
where the added structure may not be necessary.
• Potential for inconsistency: Since MVP does not
enforce strict rules on how components interact, there
is potential for inconsistencies in how the pattern is
implemented across different parts of the application or
by different team members. This can lead to
maintenance challenges and codebase inconsistencies
over time.
• Over-engineering: In some cases, especially for
smaller or less complex applications, implementing
MVP may be considered over-engineering. The added
layers of abstraction and separation may not provide
significant benefits compared to simpler patterns like
MVC.
MVMM pattern
The final variation of the MV* pattern is the Model-View-
Mediator-Model (MVMM) Design Pattern. It adds an
additional layer, the Mediator, which helps to decouple the
components further. In JavaScript, it is particularly useful
for building complex user interfaces where you need a
clear separation of concerns and easy maintainability.
Let us break down each component in the MVMM pattern:
• Model: Like the previous MV* patterns, the Model
represents the data and the business logic of the
application. It is responsible for managing the
application’s data, responding to requests for
information, and also updating itself when data changes
occur.
• View: In MVMM, the View is responsible for presenting
the data to the user and handling user interactions. It
listens to changes in the Model and updates the UI
accordingly.
• Mediator: In MVMM, we introduce a new component
called the Mediator. It acts as an intermediary between
the Model and the View. It facilitates communication
between them, ensuring that they remain loosely
coupled. The Mediator listens to user interactions from
the View, updates the Model accordingly, and notifies
the View of any changes in the Model that require UI
updates. It helps to minimize direct dependencies
between the Model and the View, making the
application easier to maintain and extend.
• Mediated Model: In some implementations of MVMM,
there is an additional layer called the Mediated Model.
This is essentially a wrapper around the original Model,
providing an interface that the Mediator can interact
with. It allows the Model to remain decoupled from the
Mediator and simplifies the communication between
them.
Let us quickly dive into an example to understand how the
MVMM Design Pattern is implemented:
1. // Mediated Model
2. class MediatedUserModel {
3. constructor(model) {
4. [Link] = model;
5. }
6.
7. setAge(age) {
8. [Link](age);
9. }
10. }
11.
12. // Model
13. class UserModel {
14. constructor(name) {
15. [Link] = name;
16. [Link] = 0;
17. }
18.
19. setAge(age) {
20. [Link] = age;
21. // Notify the mediator that data has changed
22. [Link]();
23. }
24. }
25.
26. // View
27. class UserView {
28. constructor() {
29. [Link] =
[Link]('nameInput');
30. [Link] =
[Link]('ageDisplay');
31. [Link] =
[Link]('submitButton');
32. }
33.
34. bindSubmit(handler) {
35. [Link]('click', () => {
36. const name = [Link];
37. handler(name);
38. });
39. }
40.
41. displayAge(age) {
42. [Link] = age;
43. }
44. }
45.
46. // Mediator
47. class UserMediator {
48. constructor(model, view) {
49. [Link] = new MediatedUserModel(model);
50. [Link] = view;
51.
52. // Bind view events to mediator methods
53. [Link]([Link](this));
54.
55. // Initial update of view
56. [Link]();
57. }
58.
59. updateName(name) {
60. [Link] = name;
61. // Update the model with new age, triggering a
change
62. [Link]([Link]([Link]() *
100));
63. }
64.
65. onDataChange() {
66. [Link]();
67. }
68.
69. updateView() {
70. [Link]([Link]);
71. }
72. }
73.
74. // Usage
75. const userModel = new UserModel('John');
76. const userView = new UserView();
77. const mediator = new UserMediator(userModel,
userView);
Let us start with the core part — the Model. In this case,
we have created a class called UserModel. The
UserModel class represents the data of a user. It has
properties such as name and age, initialized with default
values. It provides a method setAge to update the age of
the user. When the setAge method is called, it updates the
age property and notifies the Mediator (UserMediator) by
calling [Link]().
Next, we look at the Mediated Model —
MediatedUserModel. It acts as a wrapper around the
original UserModel. It is constructed with an instance of
the UserModel, which it holds as its internal Model
property. The purpose of this wrapper is to provide an
interface for the Mediator (UserMediator) to interact with
the underlying UserModel. It delegates method calls to the
underlying UserModel, effectively mediating the
interaction between the Mediator and the Model.
Next, we look at the View — UserView. The UserView
class represents the UI elements related to the user. It has
references to HTML elements such as nameInput,
ageDisplay, and submitButton, presumably obtained
using getElementById. It provides a method bindSubmit
to bind a submit event handler to the submit button. It
provides a method displayAge to update the age display in
the UI.
Finally, we look at the Mediator (UserMediator). It acts as
an intermediary between the Model and the View. It is
constructed with instances of the Mediated Model
(MediatedUserModel) and the View (UserView). It binds
the submit button of the View to the updateName method
of the Mediator. It listens for changes in the Mediated
Model triggered by user interactions and updates the View
accordingly. It provides methods like updateName,
onDataChange, and updateView to handle interactions
between the Model and the View.
The Mediator (UserMediator) facilitates communication
between the Model and the View, using the Mediated
Model (MediatedUserModel) as an intermediary layer to
decouple the Model from the Mediator.

Advantages
The MVMM pattern offers the following advantages in
writing JavaScript applications:
• Separation of concerns: MVMM promotes a clear
separation of concerns by assigning specific
responsibilities to each component. The Model handles
data and business logic, the View manages UI
presentation, and the Mediator facilitates
communication between them. This separation makes
the codebase more organized and easier to understand.
• Flexibility and extensibility: Because of the loose
coupling between components, it is easier to swap or
update individual components without affecting the
overall functionality of the application.
• Decoupling of components: MVMM helps in
achieving a high level of decoupling between the Model,
View, and Mediator. This makes it easier to maintain
and extend the application as changes in one
component don’t directly impact the others.
• Mediated communication: The Mediator acts as a
central hub for communication between the Model and
View. This centralized communication channel
simplifies complex interactions and reduces the
likelihood of tight coupling between components.

Disadvantages
There are also a few disadvantages of using the MVMM
pattern:
• Complexity: The MVMM pattern introduces an
additional layer of abstraction (the Mediator) which can
add complexity to the architecture. Managing
communication between components through the
Mediator can sometimes make the code harder to
understand, especially for developers unfamiliar with
the pattern.
• Potential for over-engineering: Depending on the
complexity of the application, implementing MVMM
may be overkill. For simpler applications, the overhead
of adding a Mediator layer may not be justified, and a
simpler pattern like MVC or MVP may be more
appropriate.
• Increased codebase size: Introducing an additional
layer like the Mediator can lead to an increase in the
size of the codebase. This may result in more code to
maintain and potentially more opportunities for bugs to
occur.
• Learning curve: MVMM may have a steeper learning
curve compared to simpler patterns like MVC or MVP,
especially for developers who are not familiar with the
concept of a Mediator. Training and documentation may
be necessary to onboard new team members effectively.

Combining Design Patterns


Since the start of this book, we’ve explored several Design
Patterns. Each Design Pattern has been created to address
specific problems identified over years of software
engineering. While individual Design Patterns can solve
particular issues, combining multiple Design Patterns can
leverage their strengths to tackle complex design
challenges more effectively.
So, let us first understand the advantages of combining
Design Patterns:
• Address multiple concerns: Real-world applications
often face multiple design concerns simultaneously,
such as separation of concerns, code reusability, and
scalability. Combining patterns allows you to address
these concerns more comprehensively.
• Enhance flexibility and maintainability: Different
patterns bring different benefits. For instance, using
the Singleton Pattern ensures a single instance of a
class, while the Observer pattern allows for flexible
communication between objects. Combining them can
result in a system that is both easy to extend and
maintain.
• Optimize performance and usability: Some patterns
are better suited for specific tasks. By combining them,
you can optimize both performance and usability.
Now that we have a better idea about the advantages of
combining different Design Patterns, let us explore three of
the most relevant Design Pattern combinations.

Singleton with Factory Design Patterns


The Singleton Pattern ensures a class has only one instance
and offers a global point of access to it. Meanwhile, the
Factory pattern establishes an interface for creating an
object but permits subclasses to adjust the type of objects
created. Pairing the Singleton and Factory Design Patterns
is widely used in software design. It permits only one
instance of the factory class, capable of producing various
types of objects. This blend proves handy when we need to
centralize object creation logic while guaranteeing
controlled access to a sole instance of the factory. Now, let
us delve into an example illustrating this concept:
1. // Singleton Pattern implementation for the factory
2. class SingletonFactory {
3. constructor() {
4. if (![Link]) {
5. [Link] = this;
6. }
7. return [Link];
8. }
9.
10. createProduct(type) {
11. let product;
12. switch(type) {
13. case 'A':
14. product = new ProductA();
15. break;
16. case 'B':
17. product = new ProductB();
18. break;
19. default:
20. throw new Error('Unknown product type');
21. }
22. return product;
23. }
24. }
25.
26. const instance = new SingletonFactory();
27. [Link](instance);
28.
29. // Product classes
30. class ProductA {
31. constructor() {
32. [Link] = 'ProductA';
33. }
34. display() {
35. [Link]('This is Product A');
36. }
37. }
38.
39. class ProductB {
40. constructor() {
41. [Link] = 'ProductB';
42. }
43. display() {
44. [Link]('This is Product B');
45. }
46. }
47.
48. // Usage
49. const factory = new SingletonFactory();
50. const productA = [Link]('A');
51. const productB = [Link]('B');
52.
53. [Link](); // Output: This is Product A
54. [Link](); // Output: This is Product B
In the above example, we have created the
SingletonFactory class that ensures only one instance of
the factory exists using the static instance property. The
createProduct method implements the factory logic to
create different products based on the type. ProductA and
ProductB represent different types of products that the
factory can create. Each product class has a display method
to demonstrate its functionality. The factory instance is
created and used to generate products of different types.
The display method of each product is called to show its
specific behavior.
The benefits of combining these two Design Patterns
include:
• Controlled access: The Singleton ensures that there
is a single point of access to the factory, making it
easier to manage the creation logic.
• Centralized object creation: The Factory pattern
centralizes the creation of different product types,
making the code more maintainable and scalable.
• Flexibility: New product types can be added easily
without changing the existing code structure, adhering
to the Open/Closed Principle.
This combination is highly effective in scenarios where you
need a single point of control for creating various objects,
ensuring both efficiency and manageability.

Observer with Mediator Design Patterns


The Observer pattern establishes a one-to-many
relationship between objects. When one object changes
state, all its dependents are automatically notified and
updated. On the other hand, the Mediator Pattern
encourages loose coupling by preventing objects from
directly referencing each other. It facilitates effortless
modification of interactions between objects. Combining
these two patterns proves highly beneficial when handling
intricate communication among multiple objects within a
system. While the Observer pattern enables objects to
subscribe to events and respond to changes, the Mediator
Pattern centralizes and oversees communication between
objects. This reduces dependencies and fosters decoupling.
Let us explore an example combining the two:
1. class Mediator {
2. constructor() {
3. [Link] = {};
4. }
5.
6. subscribe(channel, context, func) {
7. if (![Link][channel]) {
8. [Link][channel] = [];
9. }
10. [Link][channel].push({ context, func });
11. }
12.
13. publish(channel, ...args) {
14. if (![Link][channel]) {
15. return false;
16. }
17. [Link][channel].forEach(subscriber => {
18. [Link]([Link], args);
19. });
20. }
21. }
22.
23. class Component {
24. constructor(name, mediator) {
25. [Link] = name;
26. [Link] = mediator;
27. }
28.
29. notify(event, data) {
30. [Link](event, data);
31. }
32.
33. subscribe(event, func) {
34. [Link](event, this, func);
35. }
36. }
37.
38. // Usage
39. const mediator = new Mediator();
40.
41. const componentA = new Component('ComponentA',
mediator);
42. const componentB = new Component('ComponentB',
mediator);
43.
44. [Link]('eventX', data =>
[Link](`${[Link]} received: ${data}`));
45. [Link]('eventX', data =>
[Link](`${[Link]} received: ${data}`));
46.
47. [Link]('eventX', 'Hello from
ComponentA');
48. [Link]('eventX', 'Hello from
ComponentB');
49.
In the above example, we have created two classes —
Mediator and Component. The Mediator class manages
the channels and subscriptions between components and
provides methods to subscribe to channels and publish
messages to subscribers. The Component class represents
the individual components in the system. It has methods to
subscribe to channels and notify the mediator to
publish messages. We have then created two components
— ComponentA and ComponentB. Both components are
subscribed to the same channel eventX. Each component
reacts to messages published on the channel by logging the
received data.
The benefits of combining these two Design Patterns are:
• Decoupling: The Mediator Pattern helps decouple
components by allowing them to communicate
indirectly through the mediator object, reducing direct
dependencies.
• Centralized communication: All communication
between components is managed centrally through the
mediator, making it easier to track and manage.
• Scalability: Adding new components or channels is
straightforward and does not require changes to
existing components, promoting system scalability.
This combination is particularly useful in systems where
multiple components need to communicate with each other
but should remain loosely coupled. It promotes flexibility,
maintainability, and scalability in complex applications.

Decorator with Strategy Design Patterns


The Decorator Pattern attaches additional responsibilities
to an object dynamically, while the Strategy Pattern defines
a family of algorithms, encapsulates each one, and makes
them interchangeable. Combining these two can be
beneficial when we need to dynamically change an object’s
behavior and also allow switching between different
algorithms or strategies. The Decorator Pattern is useful
for adding responsibilities to objects dynamically, while the
Strategy Pattern defines a family of algorithms and makes
them interchangeable.
Here is an example demonstrating their combination:
1. // Strategy Pattern
2. class Strategy {
3. execute(data) {
4. throw new Error('Strategy#execute needs to be
overridden');
5. }
6. }
7.
8. class ConcreteStrategyA extends Strategy {
9. execute(data) {
10. [Link]('Strategy A executed with data:', data);
11. }
12. }
13.
14. class ConcreteStrategyB extends Strategy {
15. execute(data) {
16. [Link]('Strategy B executed with data:', data);
17. }
18. }
19.
20. class Context {
21. constructor(strategy) {
22. [Link] = strategy;
23. }
24.
25. setStrategy(strategy) {
26. [Link] = strategy;
27. }
28.
29. executeStrategy(data) {
30. [Link](data);
31. }
32. }
33.
34. // Decorator to add logging
35. class LoggingDecorator {
36. constructor(context) {
37. [Link] = context;
38. }
39.
40. executeStrategy(data) {
41. [Link]('Logging before execution');
42. [Link](data);
43. [Link]('Logging after execution');
44. }
45. }
46.
47. // Usage
48. const strategyA = new ConcreteStrategyA();
49. const context = new Context(strategyA);
50.
51. const loggedContext = new
LoggingDecorator(context);
52. [Link]('Test data'); // Logs
around strategy execution
53.
54. [Link](new ConcreteStrategyB());
55. [Link]('Test data'); // Logs
around new strategy execution
In the above example, we have created the abstract class
Strategy and then extended it to define a family of
algorithms (strategies) with a common interface (execute
method) by creating the concrete strategy classes
(ConcreteStrategyA and ConcreteStrategyB)
implementing specific algorithms. The Context class takes
a strategy object as a parameter and provides a method
(executeStrategy) to execute the current strategy. The
LoggingDecorator class decorates the Context object to
add logging functionality around the execution of
strategies. It overrides the executeStrategy method to log
before and after executing the strategy. Strategy objects
are created and passed to the Context object. The
LoggingDecorator wraps the Context object, adding
logging functionality. Strategies can be switched
dynamically by setting a new strategy in the Context
object.
The benefits of combining these two Design Patterns are:
• Separation of concerns: The Strategy Pattern
encapsulates algorithms, while the Decorator Pattern
adds responsibilities to objects. This separation helps
keep the code modular and maintainable.
• Flexibility: Strategies can be changed dynamically at
runtime, and decorators can be added or removed
dynamically, providing flexibility in behavior
modification.
• Reusability: Both patterns promote code reuse by
allowing algorithms and responsibilities to be defined
independently and composed dynamically.
This combination is particularly useful when you need to
encapsulate different algorithms and behaviors and provide
flexibility in choosing and modifying them at runtime. It
promotes code maintainability, scalability, and adaptability
to changing requirements.

Conclusion
In this chapter, we delved into the advanced utilization of
Design Patterns. After examining individual Design
Patterns tailored for specific use cases and their
application in modern JavaScript libraries, we explored
three significant concepts. First, we checked the Module
Pattern and its variant – Revealing Module Design Pattern.
Post that, we delved into the MV* Design Patterns, where
M represents the Model, V represents the View, and the *
symbolizes the various components that can be blended
with these two to craft diverse Design Patterns catering to
advanced needs. We scrutinized three distinct variants of
the MV* patterns — MVC, MVP, and MVMM. Each of these
variants closely mimics the behavior and essence of robust
modern JavaScript libraries. This introduces intricacy to
the code while delivering significantly on reliability,
robustness, and efficiency. Following our exploration of the
MV* patterns, we further examined the technique of
merging different Design Patterns. Real-world applications
frequently confront multiple design concerns
simultaneously, such as separation of concerns, code
reusability, and scalability. By amalgamating different
Design Patterns that address these individual needs, we
tackle multiple problems and optimize performance and
usability. Though there exist numerous permutations and
combinations to merge different Design Patterns, we
focused on the top three combinations capable of resolving
issues commonly encountered by developers.
In the next chapter, we will see how Design Patterns fulfil
the maximum potential for web architects whose primary
job is to ensure a web application has a sound and reliable
structure.

Points to remember
• Design Patterns are crafted to address specific use
cases and tackle particular sets of problems. However,
real-world applications frequently present complex
challenges that are a blend of these individual
problems. To effectively address such complexities, it
becomes imperative to delve into advanced Design
Patterns.
• The Revealing Module Design Pattern is particularly
useful for creating modules with private and public
methods and variables.
• The MV* pattern, short for Model-View-*, maintains the
Model and View components as constants, defining the
data/business logic and the user interface, respectively.
The * denotes the diverse components that can
intertwine with these two to tackle advanced issues.
This MV* pattern draws loose inspiration from the
operational structure of modern JavaScript libraries.
• The MVC pattern adds the Controller to the Model and
View components. The Controller manages the
application’s flow and business logic, determining
actions based on user input and updating the Model
accordingly. The View actively observes the Model and
updates itself.
• In the MVP pattern, the Controller is replaced by the
Presenter component. The Presenter is responsible for
handling user interactions with the View. It fetches data
from the Model and updates the View accordingly.
Unlike MVC, the View is passive in MVP and relies on
the Presenter to update it.
• The MVMM pattern introduces a Mediator and a
Mediated Model. The Mediator listens to user
interactions from the View, updates the Model
accordingly, and notifies the View of any changes in the
Model that require UI updates. The optional Mediated
Model allows the Model to remain decoupled from the
Mediator and simplifies the communication between
them.
• While individual Design Patterns can solve particular
issues, combining multiple Design Patterns can
leverage their strengths to tackle complex design
challenges more effectively.
• Combining Design Patterns can address multiple
concerns by effectively covering both set of problems
that individual Design Patterns can solve. Additionally,
they improve the flexibility and maintainability while
optimizing performance and usability.

Exercises
1. Which of the following Design Patterns utilize a
mediator to listen to user interactions from the
View and then update the Model?
a. MVC pattern
b. MVMM pattern
c. MVP pattern
d. Decorator + Strategy Pattern
2. Which of the following statements is an advantage
provided by combining the Observer and Mediator
Design Patterns?
a. Decoupling components by allowing them to
communicate indirectly through the mediator object,
reducing direct dependencies.
b. Dynamically changing strategies on the fly.
c. Centralized communication channel that simplifies
complex interactions and reduces the likelihood of
tight coupling between components.
3. Which of the following use-cases best describe the
implementation of the MVP Design Pattern?
a. Encapsulating different algorithms and behaviors and
providing flexibility in choosing and modifying them
at runtime.
b. A single point of control for creating various objects,
ensuring both efficiency and manageability.
c. Creating modules with private and public methods
and variables.
d. Allowing UI/UX designers and developers to work
independently and iterate on the user interface
without affecting the application’s logic.

Answers
1. b
2. a
3. d
CHAPTER 9
Design Patterns for Web
Architects

Introduction
The ever-growing complexity of web applications demands
a structured approach to design and development. This
chapter delves into the power of JavaScript Design Patterns
for web architects. We will explore how these established
solutions empower us to make informed decisions that
significantly impact the scalability, availability, and
performance, often denoted by the abbreviation SAP, the
cornerstones of robust web systems. This chapter unveils
the core concepts of web architecture, laying the
foundation for understanding how Design Patterns
influence critical design choices. We will then delve into
the profound impact of Design Patterns on each aspect of
SAP. By mastering these Design Patterns, we will gain the
ability to craft web architectures that are not only
functional but also maintainable, scalable, and performant
in the face of ever-evolving user demands.
Structure
This chapter will cover the following topics:
• Web architecture and web architects
• Role of Design Patterns in designing systems
• Applying Design Patterns to improve scalability
• Applying Design Patterns to improve availability
• Applying Design Patterns to improve performance

Objectives
This chapter will equip us with a deep dive into the world of
web architecture, offering valuable insights from a web
architect’s perspective. We will explore how the Design
Patterns we have encountered can be leveraged to achieve
the three pillars of successful web architecture: SAP.
Here is a breakdown of the impact Design Patterns have on
each aspect of SAP:
• Scalability: We will discover how Design Patterns
empower us to create systems that gracefully handle
growing traffic and data demands.
• Availability: We will explore how Design Patterns
promote fault tolerance and redundancy, ensuring our
applications remain accessible to users.
• Performance: We will gain strategies for optimizing
performance by leveraging established Design Patterns,
leading to a more responsive and efficient user
experience.
By the end of this chapter, we will be well-equipped to craft
web architectures that are not only functional but also
maintainable, scalable, and performant in the face of
evolving user demands.
Web architecture and web architects
Web architecture is the foundation of any successful web
application. It defines the overall structure, behavior, and
interactions within a web system. To illustrate, consider the
analogy of a complex building. An architect meticulously
plans every stage of the layout, determines the logistics
and materials, and defines the functionalities. Similarly, a
web architect must consider every detail of the web
application, including components, technologies, and other
features that contribute to its overall functionality. To
demystify web architecture, we will dissect its core
components and explore the crucial role web architects
play in bringing it all together.

Building blocks of web architecture


Let us take a deeper look into the key elements that make
up the web architecture:
• Components: Web applications consist of various
components that work together seamlessly. For
instance, consider a complex web application like
Instagram. Instagram features components such as
image posts, comments sections, and stories, each
comprising buttons, menus, and forms. However, web
applications encompass more than just these UI
elements. They also include server-side logic that
processes data and interacts with databases, as well as
the databases that store all the application’s
information.
• Tiers: Web architecture is often organized using a
tiered approach. A common structure is the three-tier
architecture, which separates the presentation layer
(UI), the business logic layer (processes user requests),
and the data access layer (interacts with the database).
Continuing with the Instagram example, the
presentation layer includes the photos and posts visible
to users. The business logic layer handles the
application’s requests, such as searching for accounts
or liking posts. The data access layer connects the UI to
the database, processing user requests. This separation
promotes modularity and simplifies maintenance.
• Technologies: A vast array of technologies come into
play to build web applications. Web architects need a
solid understanding of programming languages like
JavaScript, HTML, CSS, server-side languages like
Python or Java, and database technologies like SQL.
• Scalability and performance: A well-designed
architecture ensures the application can handle
increasing user traffic and data demands (scalability)
while delivering a fast and responsive experience
(performance). This is achieved through techniques like
caching, load balancing, and choosing the right
technology stack.
• Security: Web security is paramount. Web architects
must prioritize user data protection and implement
robust security measures to prevent unauthorized
access and vulnerabilities.

The role of a web architect


As discussed earlier, web architecture plays a crucial role
in developing a successful and stable web application.
Consequently, the responsibilities of a web architect
become significantly more important. Think of a web
architect as the maestro of the web development orchestra.
The following illustration is an artistic take on the role of a
web architect:
Figure 9.1: An illustration depicting a web architect at work
Let us examine the role of a web architect in this context:
• Defining the web architecture blueprint: Web
architects analyze project requirements, user needs,
and performance goals. They then translate those needs
into a comprehensive architectural plan that lays out
the components, technologies, and communication
protocols for the application.
• Technology selection: The next important part of
their job role is to choose the right technology stack.
Choosing the right technology stack is crucial. Web
architects leverage their expertise to select the most
suitable programming languages, frameworks, and
database solutions to meet the project’s specific needs
and ensure scalability and performance.
• Integration and communication: Web applications
rarely exist in isolation. Web architects design systems
that can integrate with existing infrastructure and
communicate seamlessly with other applications and
services. At the same time, they need to keep the
application flexible enough to integrate with any
modifications and applications in the future.
• Security considerations: Building secure web
applications is a top priority. Web architects implement
robust security measures to safeguard user data and
prevent cyberattacks.
• Maintainability and futureproofing: A well-designed
web architecture is not only functional but also
maintainable. Web architects plan for future growth
and ensure the system can be easily modified and
updated as needs evolve.
From the previous sections, it is evident that the role of a
web architect involves critical decision-making that can
determine the success of an application. In this chapter, we
will explore how JavaScript Design Patterns can address a
key requirement: handling SAP for web applications. This
discussion assumes that the web architect has chosen
JavaScript as part of the technology stack for either the
front-end, the backend, or both.

Role of Design Patterns in designing systems


With our newly acquired knowledge about web architecture
and the role of architects, let us advance to understanding
how JavaScript Design Patterns can assist web architects in
creating powerful systems. Design Patterns are invaluable
tools for web architects, enabling them to build scalable,
maintainable, and well-structured web applications.
Although we have explored these Design Patterns
independently, their application in web architecture offers
unique advantages. Let us delve into how JavaScript Design
Patterns can aid in designing systems and their significant
roles:
• Standardization and reusability: Design Patterns
help establish a consistent architecture across different
parts of a web application or even across different
projects. This consistency makes it easier to manage the
overall system architecture and ensures that all
components work well together. Patterns enable the
reuse of architectural solutions across various projects.
This not only accelerates development but also ensures
that scalable solutions are employed, which can handle
growth and changing requirements more effectively.
• Simplifying complex architecture: Design Patterns
offer a way to simplify the complexity inherent in large
web applications. They provide a high-level approach to
tackling common architectural challenges, making it
easier to design robust systems. By using Design
Patterns, architects can create abstraction layers that
hide the complexities of lower-level operations. This
makes the architecture more modular and easier to
manage.
• Enhancing code maintainability: Many patterns
promote a clear separation of concerns, which leads to
more modular and easier-to-maintain code. This
modularity is crucial for maintaining large codebases,
allowing architects to isolate and address issues without
impacting other parts of the system.
• Facilitating scalability: Patterns like Singleton,
Observer, and others help manage state and behavior in
a scalable way. This ensures that as the web application
grows, the underlying architecture can handle
increased load and complexity without significant
redesign. Using Design Patterns, architects can create
extensible frameworks that allow for future growth and
feature addition without necessitating major overhauls.
This adaptability is key for evolving web applications.
• Improving performance: Certain Design Patterns
offer strategies for optimizing performance, especially
in resource-sensitive web applications. By managing
resources more efficiently, it leads to better
performance. By leveraging these patterns, web
architects can design systems that are inherently
effective, reducing latency, and improving user
experience.
• Promoting best practices: Design Patterns
encapsulate best practices and serve as architectural
guidelines. By following these patterns, web architects
ensure that the architecture adheres to industry
standards and proven methodologies.
• Enabling effective collaboration: From our previous
encounters with Design Patterns, we know that they
establish standard vocabulary for developers. By using
well-known patterns, team members can easily
understand and communicate the structure and
behaviour of the system. This is particularly beneficial
in large, distributed teams. Patterns also aid in
documentation, making it easier for new team members
to understand the architecture. This accelerates
onboarding and helps maintain continuity in long-term
projects.
For web architects, JavaScript Design Patterns are
invaluable in crafting well-structured, maintainable, and
scalable web applications. They provide a blueprint for
solving architectural challenges, ensuring that the system
can evolve and adapt to changing requirements while
maintaining high performance and quality. By integrating
these patterns into the design process, web architects can
create robust frameworks that support efficient
development and long-term success.

Applying Design Patterns to improve scalability


Starting with this section, we will explore how JavaScript
Design Patterns can enhance the three critical aspects of
web architecture: SAP. We will begin by focusing on the
first pillar of SAP—Scalability. Various Design Patterns
discussed earlier in this book are crucial for helping web
architects design scalable systems. Let us examine each of
these Design Patterns from a web architect’s perspective to
understand how they can improve scalability:

Module Pattern
The Module Pattern encapsulates related functions,
variables, and data into a single unit, providing a public API
while hiding internal implementation. This helps organize
code in a scalable web application, especially for reducing
the complexity in large codebases.
One of the effective uses of the Module Pattern is lazy
loading, where heavy initialization logic is executed only
when required, improving performance by reducing load
time. In the example below, we will demonstrate how lazy
loading can be integrated within the UserService module:
1. const UserService = (function() {
2. // Simulating a heavy initialization logic that is only
executed once
3. let users = [];
4.
5. // Lazy loading - Initialization of the users list happens
only once, on first use
6. function initialize() {
7. if ([Link] === 0) {
8. [Link]("Initializing users...");
9. // Simulated delay for user loading, to showcase
lazy loading
10. users = [{ id: 1, name: 'Alice' }, { id: 2, name:
'Bob' }];
11. }
12. }
13.
14. function addUser(user) {
15. initialize(); // Ensure initialization happens before
use
16. [Link](user);
17. }
18.
19. function getUser(id) {
20. initialize(); // Ensure initialization happens before
use
21. return [Link](user => [Link] === id);
22. }
23.
24. return {
25. addUser: addUser,
26. getUser: getUser
27. };
28. })();
In this code example above tailored for web architects, we
have created a service called UserService. It is responsible
for maintaining a list of users. Inside this service, we have
created methods that can be accessible to developers
outside for adding and fetching users while hiding the
internal implementation. This helps in organizing code into
self-contained modules, making it easier to manage
dependencies, and reducing global scope pollution. This
pattern is particularly useful in large codebases to improve
readability and maintainability.

Revealing Module Pattern


The Revealing Module Pattern is fairly similar to the
Module Pattern. The notable difference is that this pattern
clearly emphasizes exposure of public members. This is
useful for defining a clear API for complex modules.
1. const AuthService = (function() {
2. let isAuthenticated = false;
3.
4. function login(user) {
5. isAuthenticated = true;
6. [Link](`${user} logged in`);
7. }
8.
9. function logout() {
10. isAuthenticated = false;
11. [Link]('Logged out');
12. }
13.
14. function isLoggedIn() {
15. return isAuthenticated;
16. }
17.
18. return {
19. login: login,
20. logout: logout,
21. isLoggedIn: isLoggedIn
22. };
23. })();
24.
25. // Usage
26. [Link]('Alice');
27. [Link]([Link]()); // true
28. [Link]();
29. [Link]([Link]()); // false
30.
In the above code example, we focused on the essential
feature of authentication, which is critical for a web
architect to design. We utilized the Revealing Module
Design Pattern to clearly identify and expose selected
services for public use. This pattern enhances readability
and maintainability by providing a clear API while hiding
implementation details, making it useful for exposing a
clean interface for modules.

Singleton Pattern
From a web architect’s perspective, it is crucial to ensure
that there is only one instance of a global configuration or
database services that impacts every part of the
application. By implementing and advocating the Singleton
Pattern, the web architect can ensure that this critical
feature is limited to a single instance, accessible from any
part of the application.
1. const ConfigManager = (function() {
2. let instance;
3.
4. function createInstance() {
5. const config = {
6. apiUrl: '[Link]
7. timeout: 5000
8. };
9. return config;
10. }
11.
12. return {
13. getInstance: function() {
14. if (!instance) {
15. instance = createInstance();
16. }
17. return instance;
18. }
19. };
20. })();
21.
22. // Usage
23. const config1 = [Link]();
24. const config2 = [Link]();
25. [Link](config1 === config2); // true
In the above example, we have utilized the Singleton
Pattern to have a single instance of the configuration that
is applicable for the entire system. This ensures that only
one instance of these critical resources exists, preventing
inconsistencies. This is ideal for managing global
application state or shared resources like configuration
settings, database connections, or logging services.

Observer Pattern
From a web architect’s perspective, the Observer Pattern is
very crucial for event-driven architecture. Observer
Patterns define a one-to-many dependency between objects
so that when one object changes state, all its dependents
are notified and updated automatically.
1. class EventEmitter {
2. constructor() {
3. [Link] = {};
4. }
5.
6. on(event, listener) {
7. if (![Link][event]) {
8. [Link][event] = [];
9. }
10. [Link][event].push(listener);
11. }
12.
13. emit(event, data) {
14. if ([Link][event]) {
15. [Link][event].forEach(listener =>
listener(data));
16. }
17. }
18. }
19.
20. const eventEmitter = new EventEmitter();
21.
22. function logger(data) {
23. [Link]('Logging data:', data);
24. }
25.
26. [Link]('dataReceived', logger);
27. [Link]('dataReceived', { id: 1, message:
'Hello World!' }); // Logging data: { id: 1, message:
'Hello World!' }
For the above code example from a web architect’s
perspective, we have implemented the functionality of an
emitter. This is useful in scenarios where components need
to react to state changes, such as in event-driven
architectures or when implementing reactive UI updates.
This pattern helps in decoupling components and
improving the modularity of the application.

Factory Pattern
The Factory Pattern provides an interface for creating
objects in a superclass while allowing subclasses to modify
the types of objects that will be created. This functionality
is essential for building web applications.
1. class User {
2. constructor(name) {
3. [Link] = name;
4. }
5. }
6.
7. class Admin {
8. constructor(name) {
9. [Link] = name;
10. }
11. }
12.
13. class UserFactory {
14. createUser(type, name) {
15. switch(type) {
16. case 'user':
17. return new User(name);
18. case 'admin':
19. return new Admin(name);
20. default:
21. throw new Error('Unknown user type');
22. }
23. }
24. }
25.
26. const factory = new UserFactory();
27. const regularUser = [Link]('user',
'Alice');
28. const adminUser = [Link]('admin', 'Bob');
29.
30. [Link](regularUser instanceof User); // true
31. [Link](adminUser instanceof Admin); // true
From a web architect’s perspective, it is crucial to define
how users, their roles (e.g., Administrator, Manager,
Employee), and their permissions are generated for the
web application. In the example above, we utilized the
Factory Pattern to provide a fixed and robust method for
handling user creation. This pattern decouples object
creation from implementation, managing object creation
and introducing an abstraction layer to reduce dependency
on specific classes. It is particularly useful for creating
families of related or dependent objects without specifying
their concrete classes.

Decorator Pattern
A web architect’s job extends beyond creating the initial
structure and skeleton of the application; it also involves
designing a system that is future-proof and can
accommodate modifications without breaking down. The
Decorator Pattern, which adds behavior to objects
dynamically, can help the web architect design a future-
proof system.
1. function Order() {
2. [Link] = 100;
3. }
4.
5. function addShipping(order) {
6. [Link] += 10;
7. }
8.
9. function addTax(order) {
10. [Link] *= 1.15;
11. }
12.
13. const myOrder = new Order();
14. addShipping(myOrder);
15. addTax(myOrder);
16.
17. [Link]([Link]); // 126.5
In the above code example, we have denoted a way for the
web architect to ensure that the object that has been
visualized at the start can be modified in the future and the
Decorator Pattern is something that can be utilized to
achieve the same. It allows for adding responsibilities to
objects without altering their structure. It is useful for
scenarios where we need flexible and reusable code, such
as adding features to UI components or managing
middleware in server-side applications.

Strategy Pattern
A web architect must consider that certain applications
need to alter their behavior dynamically. For such cases, it
is crucial to have a family of encapsulated, interchangeable
algorithms. The Strategy Pattern facilitates this
requirement.
1. class Payment {
2. constructor(strategy) {
3. [Link] = strategy;
4. }
5.
6. setStrategy(strategy) {
7. [Link] = strategy;
8. }
9.
10. execute(amount) {
11. return [Link](amount);
12. }
13. }
14.
15. const creditCardPayment = {
16. pay: (amount) => `Paid ${amount} using credit
card`
17. };
18.
19. const paypalPayment = {
20. pay: (amount) => `Paid ${amount} using PayPal`
21. };
22.
23. const payment = new Payment(creditCardPayment);
24. [Link]([Link](100)); // Paid 100 using
credit card
25.
26. [Link](paypalPayment);
27. [Link]([Link](200)); // Paid 200 using
PayPal
In the example above, we describe one of the most common
features required in today’s web applications that double as
SaaS products—payments. Since payment features may
need future modifications to incorporate new technologies,
the Strategy Pattern is ideal for designing a system that
allows for dynamic interchange of algorithms. It facilitates
switching between different algorithms or strategies at
runtime, which is particularly useful for handling various
payment methods uniformly.

Proxy Pattern
Proxy Pattern provides a surrogate or placeholder for
another object to control access to it. This is useful for
managing access to resources.
1. class APIService {
2. fetchData() {
3. return 'Data from API';
4. }
5. }
6.
7. class APIServiceProxy {
8. constructor() {
9. [Link] = new APIService();
10. [Link] = null;
11. }
12.
13. fetchData() {
14. if (![Link]) {
15. [Link] = [Link]();
16. }
17. return [Link];
18. }
19. }
20.
21. const proxy = new APIServiceProxy();
22. [Link]([Link]()); // Data from API
23. [Link]([Link]()); // Data from API
(cached)
In the above example, we have shown the system of a proxy
that can be useful for caching in a data-hungry application.
It is useful for lazy initialization, access control, logging, or
caching. This pattern can help optimize performance by
delaying the loading of heavy objects until necessary or by
adding security layers.
By incorporating these Design Patterns, a web architect
can create a well-structured, efficient, and scalable web
application that can evolve gracefully over time in the
following way:
• Improved code organization and maintainability:
Design Patterns provide a structured approach to
organizing code, making it easier to manage,
understand, and maintain as the codebase grows.
• Reusability: By promoting reusable components and
clear interfaces, Design Patterns reduce redundancy
and the need to rewrite code, enabling faster
development and scalability.
• Decoupling: Many Design Patterns, such as the
Observer and Strategy Patterns, promote decoupling of
components. This makes it easier to change or extend
parts of the system independently without affecting
other parts.
• Performance optimization: Patterns like the proxy
and decorator can help optimize performance by
managing resource usage and adding functionality
dynamically.
• Enhanced collaboration: Clear patterns and
structures make it easier for multiple teams to work
together on different parts of the application,
facilitating parallel development and integration.

Applying Design Patterns to improve


availability
In simple terms, availability in web architecture refers to a
system’s ability to operate continuously and dependably
over a given period without failing. Below is a list of
JavaScript Design Patterns that can help web architects
enhance system availability by improving its robustness:
Singleton Pattern
The Singleton Pattern ensures a single instance of a
resource, such as a configuration manager, to maintain
consistency and manage resources effectively. This can be
crucial in keeping an application available and
maintainable. Here is an example demonstrating this:
1. class ConfigurationManager {
2. constructor() {
3. if (![Link]) {
4. [Link] = {}; // Load configuration settings
5. [Link] = this;
6. }
7. return [Link];
8. }
9.
10. get(key) {
11. return [Link][key];
12. }
13.
14. set(key, value) {
15. [Link][key] = value;
16. }
17. }
18.
19. const configManager = new ConfigurationManager();
20. [Link](configManager);
21. export default configManager;
In the code example above, we created a configuration
manager that maintains a single instance of configurations,
which can be utilized across the application. An instance of
its usage is:
1. import configManager from './ConfigurationManager';
2.
3. [Link]('apiUrl', '[Link]
4. [Link]([Link]('apiUrl'));
As demonstrated in the example above, the Singleton
Pattern ensures that a single instance of a resource, such
as a database connection or configuration manager, exists.
This approach prevents resource conflicts and manages
resource lifetimes effectively, reducing the overhead of
multiple connections and preventing potential resource
contention.

Observer Pattern
The Observer Pattern implements an event-driven
architecture to enhance responsiveness and decouple
components. Here is an example demonstrating its usage,
which can help a web architect design a system capable of
handling operations responsively:
1. class Observer {
2. constructor() {
3. [Link] = [];
4. }
5.
6. subscribe(fn) {
7. [Link](fn);
8. }
9.
10. unsubscribe(fn) {
11. [Link] = [Link](subscriber =>
subscriber !== fn);
12. }
13.
14. notify(data) {
15. [Link](subscriber =>
subscriber(data));
16. }
17. }
18.
19. // Usage
20. const userObserver = new Observer();
21.
22. function updateUserInterface(data) {
23. [Link](`User Interface Updated: ${data}`);
24. }
25.
26. function logUserChange(data) {
27. [Link](`User Change Logged: ${data}`);
28. }
29.
30. [Link](updateUserInterface);
31. [Link](logUserChange);
32.
33. // Simulate user data change
34. [Link]('User data changed');
In the above code, we have implemented a notification
system where multiple components need to be updated
when user data changes, enhancing the real-time
capabilities of the application. This enhances
responsiveness by allowing parts of the application to react
to events as they occur, rather than relying on periodic
checks or manual invocations. This leads to more efficient
and timely handling of tasks. Observer Patterns facilitate
decoupling of components, making it easier to manage
dependencies and improve the modularity of the system.
This leads to more robust and maintainable code, which is
crucial for high availability.
Decorator Pattern
The Decorator Pattern allows behavior to be added to
individual objects, without affecting the behavior of other
objects from the same class. It helps in adding
functionalities like logging and caching dynamically
without modifying the core logic. This helps in optimizing
performance and monitoring system health without
affecting core functionalities.
1. function withCaching(fn) {
2. const cache = new Map();
3. return async function(...args) {
4. const key = [Link](args);
5. if ([Link](key)) {
6. [Link]('Returning cached data');
7. return [Link](key);
8. }
9. const result = await fn(...args);
10. [Link](key, result);
11. return result;
12. };
13. }
14.
15. async function fetchData(apiEndpoint) {
16. const response = await fetch(apiEndpoint);
17. return [Link]();
18. }
19.
20. const fetchDataWithCaching =
withCaching(fetchData);
21.
22. // Usage
23. fetchDataWithCaching('[Link]
).then([Link]);
24. fetchDataWithCaching('[Link]
).then([Link]); // Cached
For instance, in the above example, we have created a
caching function by implementing the Decorator Pattern.
By adding caching to a function that fetches data from an
API, we can improve the performance and availability of a
code. The Decorator Pattern provides a flexible way to
extend functionalities, allowing the system to adapt to
changing requirements without major overhauls, thus
ensuring continuous availability.

Proxy Pattern
The Proxy Pattern provides a surrogate or placeholder for
another object to control access to it. This can be useful for
implementing lazy initialization, access control, logging, or
caching. It controls access to critical resources, ensuring
that only authorized requests are processed, which helps in
protecting the system from abuse or overload.
1. class APIService {
2. async fetchData(apiEndpoint) {
3. const response = await fetch(apiEndpoint);
4. if (![Link]) throw new Error('Network
response was not ok');
5. return [Link]();
6. }
7. }
8.
9. class APIServiceProxy {
10. constructor() {
11. [Link] = new APIService();
12. [Link] = 3;
13. }
14.
15. async fetchData(apiEndpoint) {
16. let attempts = 0;
17. while (attempts < [Link]) {
18. try {
19. return await
[Link](apiEndpoint);
20. } catch (error) {
21. attempts += 1;
22. [Link](`Attempt ${attempts} failed:
${[Link]}`);
23. if (attempts >= [Link]) {
24. return { error: 'Service unavailable, please try
again later.' };
25. }
26. }
27. }
28. }
29. }
30.
31. // Usage
32. const apiServiceProxy = new APIServiceProxy();
33. [Link]('[Link]
ata').then([Link]);
For instance, in the above code, we have used a proxy to
add retry logic and fallback response to an API request,
improving fault tolerance and thereby, the availability of
the system. Proxy Design Pattern implements lazy
initialization and caching, reducing the load on backend
systems and improving response times. This is particularly
useful for high-demand services where performance
directly impacts availability.
Factory Pattern
The Factory Pattern provides a way to create objects
without specifying the exact class of the object that will be
created. It offers a level of abstraction that separates object
creation from business logic, making the system easier to
maintain and extend. This pattern is critical for ensuring
long-term flexibility and scalability, especially when new
types of objects need to be integrated into the system.
In this example, the NotificationFactory is responsible for
creating different types of notifications (e.g., email, SMS)
based on the input type. This approach encapsulates the
object creation process, making it easier to introduce
additional notification types without altering existing code:
1. class EmailNotification {
2. send(message) {
3. [Link](`Sending email: ${message}`);
4. }
5. }
6.
7. class SMSNotification {
8. send(message) {
9. [Link](`Sending SMS: ${message}`);
10. }
11. }
12.
13. // Factory class responsible for creating notification
objects
14. class NotificationFactory {
15. // Factory method to create notifications based on
the type provided
16. createNotification(type) {
17. switch (type) {
18. case 'email':
19. return new EmailNotification();
20. case 'sms':
21. return new SMSNotification();
22. // Extensibility: Adding a new notification type
is as simple as adding a new case
23. case 'push':
24. return new PushNotification(); // Example of
adding a new type (Push Notification)
25. default:
26. throw new Error('Invalid notification type');
27. }
28. }
29. }
30.
31. // New type of notification that can be added easily
32. class PushNotification {
33. send(message) {
34. [Link](`Sending push notification:
${message}`);
35. }
36. }
37.
38. // Usage
39. const factory = new NotificationFactory();
40. const emailNotification =
[Link]('email');
41. const smsNotification =
[Link]('sms');
42. const pushNotification =
[Link]('push'); // New type added
43.
44. [Link]('Hello via Email!');
45. [Link]('Hello via SMS!');
46. [Link]('Hello via Push Notification!');
In this case, we have used the Factory Design Pattern to
manage object creation for different types of notifications
(email, SMS) to ensure that the correct type is used and it
is easily extendable. It manages the creation of objects in a
centralized manner, ensuring that the appropriate
resources are allocated efficiently.
By implementing these Design Patterns, a web architect
can create a system that is more resilient, scalable, and
responsive. These patterns help in:
• Efficient resource utilization: Ensuring that
resources are used optimally and shared effectively
across the application.
• Modularity and maintainability: Keeping the system
modular and maintainable, which reduces downtime
and facilitates easier updates.
• Fault tolerance: Incorporating mechanisms to handle
failures gracefully, ensuring that the system remains
available even when some components fail.
• Performance optimization: Enhancing performance
through techniques like caching, lazy loading, and load
shedding, which directly contribute to higher
availability.
Incorporating these patterns into the design and
architecture of a web application leads to a more robust
and highly available system, capable of handling high
traffic and resilient to failures.

Applying Design Patterns to improve


performance
From a web architect’s perspective, performance refers to
the speed, responsiveness, and scalability of a website or
web application. It is all about how well the system delivers
a smooth user experience under various conditions. Let us
look at a list of the Design Patterns in JavaScript that can
help improve performance of a system from a web
architect’s point of view:

Module Pattern
The Module Pattern is used to create encapsulated modules
of code. This pattern helps in organizing and grouping
related code, which improves readability and
maintainability. By encapsulating related functionality
within a module, we reduce the risk of naming collisions
and global variable pollution, leading to more efficient code
execution and easier debugging.
1. const HeavyModule = (function() {
2. let module;
3. return function() {
4. if (!module) {
5. // Perform heavy initialization only when needed
6. module = { /* heavy initialization */ };
7. }
8. return module;
9. };
10. })();
11.
Lazy loading is a common performance optimization
technique in web development, especially for large
applications where loading all modules upfront can lead to
longer initial load times. By using the Module Design
Pattern, modules can be loaded only when needed,
reducing the initial load time and improving the perceived
performance of the application. Modules can be loaded on
demand, reducing initial load times and improving page
performance.

Singleton Pattern
The Singleton Pattern ensures that a class has only one
instance and provides a global point of access to it. This is
useful for managing shared resources or configurations.
This can be particularly useful for managing global state or
configuration settings without redundant instances
consuming memory.
1. const Config = (function() {
2. let instance;
3. return {
4. getInstance: function() {
5. if (!instance) {
6. instance = { /* fetch or compute config */ };
7. }
8. return instance;
9. }
10. };
11. })();
Singleton objects can be used to store shared configuration
settings, avoiding the need to re-fetch or re-calculate them
multiple times. Expensive initialization operations are
performed only once, which can improve startup
performance.

Observer Pattern
The Observer Pattern allows an object (the subject) to
notify other objects (observers) when its state changes.
This is particularly useful for implementing event-driven
systems.
1. class Store {
2. constructor() {
3. [Link] = {};
4. [Link] = [];
5. }
6.
7. subscribe(observer) {
8. [Link](observer);
9. }
10.
11. setState(newState) {
12. [Link] = { ...[Link], ...newState };
13. [Link]();
14. }
15.
16. notify() {
17. [Link](observer =>
[Link]([Link]));
18. }
19. }
20.
We can use observers to update UI components only when
necessary, avoiding unnecessary DOM manipulations and
improving UI responsiveness. Usage of the Observer
Design Pattern allows for efficient event handling and state
management, reducing the need for continuous polling or
redundant checks. This can lead to improved
responsiveness and lower CPU usage. It promotes a
decoupled architecture where components can subscribe to
and react to events without needing direct references to
each other, enhancing modularity and ease of maintenance.

Factory Pattern
The Factory Pattern defines an interface for creating
objects but allows subclasses to alter the type of objects
that will be created. This pattern helps in creating objects
without exposing the creation logic. It streamlines the
creation of complex objects, potentially reusing existing
instances or configurations, which can reduce the overhead
of object instantiation.
1. class ObjectPool {
2. constructor(createFn) {
3. [Link] = createFn;
4. [Link] = [];
5. }
6.
7. get() {
8. return [Link] ? [Link]() :
[Link]();
9. }
10.
11. release(obj) {
12. [Link](obj);
13. }
14. }
15.
With the help of the Factory design patter, we can
implement object pooling to reuse objects instead of
creating and destroying them frequently, which can reduce
garbage collection overhead and improve performance. The
Factory Design Pattern allows for more flexible and
optimized object creation strategies, such as caching
frequently used objects, which can improve performance in
resource-intensive applications.

Decorator Pattern
The Decorator Pattern allows behavior to be added to
individual objects, either statically or dynamically, without
affecting the behavior of other objects from the same class.
1. function cacheDecorator(fn) {
2. const cache = new Map();
3. return function(...args) {
4. const key = [Link](args);
5. if ([Link](key)) {
6. return [Link](key);
7. }
8. const result = fn(...args);
9. [Link](key, result);
10. return result;
11. };
12. }
We can use decorators to add caching behavior to
functions, reducing the need for repetitive and expensive
operations. This Design Pattern allows for the dynamic
addition of responsibilities to objects without altering their
code, enabling performance enhancements (e.g., adding
caching or logging) without impacting the core logic. It
reduces the need for code duplication by enabling reusable
decorators, leading to leaner and more maintainable
codebases.

Proxy Pattern
The Proxy Pattern provides a surrogate or placeholder for
another object to control access to it. This is useful for lazy
initialization, logging, access control, and other operations.
Proxies can control access to objects, providing a way to
implement caching, logging, and other performance-related
features without modifying the original object.
1. class ImageProxy {
2. constructor(filename) {
3. [Link] = filename;
4. [Link] = null;
5. }
6.
7. display() {
8. if (![Link]) {
9. [Link] = new RealImage([Link]);
10. }
11. [Link]();
12. }
13. }
We can use proxies to delay the creation of heavyweight
objects until they are absolutely necessary, improving
initial load times. This can significantly improve application
startup times and reduce initial memory usage.
By leveraging these Design Patterns, web architects can
create applications that are not only more efficient but also
easier to maintain and scale. These patterns help optimize
resource usage, reduce unnecessary operations, and
ensure that the application remains responsive under
varying loads.

Conclusion
In this chapter, we delve into the pivotal role of a web
architect in the software development process, exploring
how Design Patterns in JavaScript serve as invaluable tools
to achieve their objectives. A web architect shoulders the
responsibility of crafting the blueprint for a web
application, laying its foundational pillars crucial for its
longevity and functionality. From handpicking technologies
to structuring the architecture with an eye on
maintainability and future adaptability, the web architect’s
decisions profoundly shape the project’s trajectory.
At the heart of this endeavor lies Design Patterns, offering
systematic approaches to common development challenges.
They empower web architects to construct systems that
embody the trifecta of scalability, availability, and optimal
performance—cornerstones of web architecture. Patterns
such as Factory, Module, Proxy, and Decorator, among
others, serve as building blocks for crafting resilient, high-
performance applications. By embracing these patterns,
web architects ensure that their creations evolve gracefully
over time, seamlessly transforming into highly available
systems capable of withstanding heavy traffic and resilient
to failures. Moreover, these patterns optimize resource
utilization, streamline operations, and uphold
responsiveness under varying loads, thus safeguarding the
user experience.
In the next chapter, we will look at the topic of testing and
Design Patterns. We will deep dive into the utilization of
Design Patterns to enhance the effectiveness,
maintainability, and organization of unit tests.

Points to remember
• Web architecture, true to its name, embodies both
artistry and scientific precision in crafting efficient web
applications. It operates on the fundamental principle of
SAP, alongside other guiding principles. By prioritizing
these pillars, web architecture lays the groundwork for
applications that are not only scalable and future-proof
but also resilient under heavy traffic, highly available,
and efficient in resource utilization.
• The role of a web architect demands meticulous
planning, entailing a thorough analysis of project
requirements, user expectations, and performance
objectives. It involves translating these insights into a
cohesive architectural blueprint, detailing the
components, technologies, and communication
protocols essential for the application’s functionality.
Anticipating future needs, web architects craft
architectures that prioritize maintainability and align
seamlessly with the SAP principle—ensuring scalability,
availability, and performance remain at the forefront of
their design considerations.
• The Module Design Pattern serves as a valuable
framework for organizing and consolidating related
code, optimizing application scalability and
performance through streamlined execution and
simplified debugging processes.
• The Singleton Design Pattern guarantees a single
global instance of a shared resource, accessible
throughout the web application. This aids web
architects in enhancing scalability, availability, and
performance by promoting resource isolation and reuse.
• The Decorator Pattern allows dynamic addition of
behavior to objects without altering their structure. It
improves availability through features like logging and
caching, ensuring efficient resource usage and system
monitoring. Performance is optimized by avoiding
extensive subclassing, allowing runtime behavior
addition which keeps memory and processing efficient,
leading to a more responsive and maintainable web
application.
• The Factory Pattern improves scalability by
centralizing object creation, making it easy to add new
types. Availability is enhanced through object pooling,
reducing downtime and latency. This pattern ensures
consistent and controlled instance creation, promoting
clean, maintainable, and reliable code.
• The Proxy Pattern improves availability with access
control, caching, and lazy initialization, reducing load
on critical resources. This pattern adds abstraction for
new functionalities like security checks, leading to a
flexible, robust, and responsive web application
architecture.
• The Observer Pattern enhances scalability by allowing
multiple objects to listen and react to changes in a
subject, facilitating easy addition of new observers
without altering the subject. It improves availability by
ensuring that updates are propagated immediately,
keeping all parts of the system in sync.

Exercises
1. Which of the following features describe the
application of a Decorator Pattern from a web
architecture’s perspective?
a. Pooling objects
b. Propagating updates systematically, as required
c. Organizing related code into compartments
d. Caching
2. Dynamic interchange of algorithms during
runtime helps the system in maintaining high
availability while also promoting scalability. Which
Design Pattern from the list below best describes
this behavior?
a. Decorator
b. Revealing Module
c. Proxy
d. Strategy
3. Which of the following tasks are not a part of a
web architect’s profile?
a. Creating simplified and intuitive designs that improve
the experience of the user
b. Selecting technologies that are future-proof and can
serve the exact needs of the application
c. Analyzing project requirements and user expectations
d. Build the foundation of a web application by
implementing a system that is scalable, highly
available and performing at peak levels

Answers
1. d
2. d
3. a

Join our book’s Discord space


Join the book's Discord Workspace for Latest updates,
Offers, Tech happenings around the world, New Release and
Sessions with the Authors:
[Link]
CHAPTER 10
Testing and Design
Patterns

Introduction
Testing is often understated and undervalued, yet it is a
critical component of software development.
Comprehensive testing can identify existing bugs that could
disrupt the customer experience, thereby helping retain
customers by maintaining quality and improving reliability
and maintainability. Design Patterns are established
solutions to common software design and development
problems. When applied to testing, these patterns can
streamline the test development process, promote code
reuse, and enhance test readability and organization.
Leveraging Design Patterns enables developers to create
tests that are effective in identifying bugs and resilient to
changes in the codebase.
In this chapter, we explore key Design Patterns in
JavaScript, demonstrate how they can be applied to various
testing scenarios, and discuss their roles in creating
structured and maintainable test suites.
Structure
This chapter will cover the following topics:
• Overview
• Writing unit tests for Design Patterns
• Planning unit tests with Design Patterns

Objectives
By the end of this chapter, we will be able to apply
JavaScript Design Patterns to the concept of unit testing in
JavaScript. By incorporating Design Pattern principles into
our unit tests, we can create test cases that effectively
identify bugs in the codebase. Moreover, utilizing the
reliability of Design Patterns will help us write test cases
that are resilient to changes in the codebase, making them
more future-proof. This approach will elevate the quality of
our test cases, ultimately resulting in a better codebase and
an improved user experience. Since this chapter focuses on
how Design Patterns can be effectively tested in a
JavaScript-powered web application and how they can
shape the application, we expect readers to have a basic
understanding of Jest, one of the most popular testing
frameworks. We will not cover the basics of these
frameworks.

Overview
To understand this chapter better, it is imperative for us to
be clear about what testing in JavaScript signifies, the
types of testing and how they are implemented, and the
potential role of Design Patterns in improving the testing
process. We will begin by revisiting the basic concepts and
ideology of testing in JavaScript to set the foundation for
this chapter’s core exploration: integrating Design Patterns
with testing.

Testing
While testing is a broad concept, we will focus specifically
on testing in JavaScript for this chapter. Testing in
JavaScript involves writing and executing code to verify
that JavaScript applications function correctly and meet
specified requirements. This process includes creating tests
that simulate various scenarios and inputs to ensure the
application behaves as expected under different conditions.
Design Patterns play a critical role in making this process
more efficient. By providing predictable structures and
well-defined interactions, Design Patterns make it easier to
isolate and test individual units of code. This predictability
simplifies test creation, reduces the chances of errors, and
improves the maintainability of both the tests and the
application itself.

Types of testing
There are several techniques and methods of testing, but
for a quality analyst (QA) and a web developer testing
JavaScript code, the three standard categories are as
follows:
• Unit testing: Unit testing focuses on testing individual
components or functions in isolation to ensure they
work as intended. Popular frameworks for unit testing
in JavaScript include Jest, Mocha, and Jasmine.
• Integration testing: It tests the interactions between
different modules or components to verify that they
work together correctly. Tools like Enzyme and Testing
Library are commonly used for integration testing in
JavaScript.
• End-to-end testing: It simulates real user interactions
with the application, testing the entire workflow from
start to finish. Frameworks such as Cypress and
Selenium are widely used for E2E testing in JavaScript.
In this chapter, we will focus exclusively on unit testing, as
it is entirely developer-centric and within the scope of our
book. We will be utilizing the Jest framework for testing
JavaScript code in this chapter.

Importance of testing in JavaScript


Testing plays a significant role in the success of a web
application, regardless of the language it is written in.
Specifically for JavaScript, here is how testing contributes
to the success of a web application:
• Ensures code quality: Testing helps identify and fix
bugs early in the development process, leading to
cleaner, more reliable code.
• Facilitates refactoring: With a comprehensive suite
of tests, developers can confidently refactor code,
knowing that any regressions will be caught by the
tests.
• Enhances maintainability: Well-tested code is easier
to maintain and extend, as tests provide a safety net
that ensures new changes do not break existing
functionality.
• Boosts confidence: Automated tests provide
developers with confidence that their code works as
expected, reducing the likelihood of defects in
production.
• Supports continuous integration and continuous
delivery (CI/CD): Automated tests are integral to
CI/CD pipelines, allowing for frequent and reliable code
deployments by ensuring that changes do not introduce
new issues.
• Improves collaboration: Tests serve as
documentation for the expected behavior of the code,
making it easier for team members to understand and
collaborate on the project.
In summary, testing in JavaScript is crucial for developing
high-quality, reliable applications. By systematically
verifying that code behaves as expected, testing helps
prevent bugs, facilitates code changes, and ensures a
smooth and confident development process.

Writing unit tests for Design Patterns


Unit testing is a fundamental aspect of software
development that ensures individual units of code work as
expected. When combined with Design Patterns, it becomes
easier to create robust, maintainable, and testable code.
This section explores how JavaScript Design Patterns can
be leveraged to enhance unit testing, focusing on practical
implementation and strategies.
Design Patterns are proven solutions to common software
design problems. In JavaScript, Design Patterns such as
Singleton, Factory, Observer, and others help structure
code in a way that makes it more manageable and scalable.
When it comes to unit testing, these patterns can also
streamline the process, making it easier to isolate and test
individual units of code.
Let us delve into how specific Design Patterns can be
applied in the context of unit testing.

Singleton Pattern
The Singleton Pattern restricts the instantiation of a class
to a single object. This is useful in scenarios requiring a
single point of control, such as a configuration manager or
a logging service. For example, consider a global
configuration manager for an application. It is responsible
for setting up constants and service configurations needed
throughout the application, such as the base URL for API
requests, user theme settings, and user information. Let us
look at an example demonstrating this:
1. class ConfigManager {
2. constructor() {
3. if (![Link]) {
4. [Link] = {};
5. [Link] = this;
6. }
7. return [Link];
8. }
9.
10. set(key, value) {
11. [Link][key] = value;
12. }
13.
14. get(key) {
15. return [Link][key];
16. }
17. }
18.
19. const instance = new ConfigManager();
20. [Link](instance);
21. export default instance;
22.
Using a mocking framework like Jest, we can test the
Singleton Pattern by ensuring that the instance is unique:
1. import ConfigManager from './ConfigManager';
2.
3. test('ConfigManager instance should be unique', () =>
{
4. const instance1 = ConfigManager;
5. const instance2 = ConfigManager;
6. expect(instance1).toBe(instance2);
7. });
8.
9. test('ConfigManager should store and retrieve values',
() => {
10. const config = ConfigManager;
11. [Link]('appName', 'MyApp');
12. expect([Link]('appName')).toBe('MyApp');
13. });
In the above example, we have imported our class into a
separate test file, typically saved with the .[Link] extension
based on the application’s configuration. This test file
includes two scenarios.
In the first scenario, we create an instance of our
Configuration Manager and store it in instance1. We then
assign the same instance to instance2. According to our
test case, the expression
expect(instance1).toBe(instance2) should return true,
verifying that the two instances are the same.
In the second test case, we verify that the ConfigManager
correctly stores and retrieves values. Using the set method
defined in the class, we set a property appName with the
value MyApp. The test case should then successfully fetch
this value with [Link]('appName').

Factory pattern
The Factory pattern provides a way to create objects
without specifying the exact class of object that will be
created. This pattern is particularly useful for creating
different types of objects based on specific conditions. Let
us consider an application that needs to create different
types of notifications (email, SMS, push).
1. class EmailNotification {
2. send(message) {
3. return `Email sent: ${message}`;
4. }
5. }
6.
7. class SMSNotification {
8. send(message) {
9. return `SMS sent: ${message}`;
10. }
11. }
12.
13. class PushNotification {
14. send(message) {
15. return `Push notification sent: ${message}`;
16. }
17. }
18.
19. class NotificationFactory {
20. static createNotification(type) {
21. switch(type) {
22. case 'email':
23. return new EmailNotification();
24. case 'sms':
25. return new SMSNotification();
26. case 'push':
27. return new PushNotification();
28. default:
29. throw new Error('Invalid notification type');
30. }
31. }
32. }
33.
34. export default NotificationFactory;
In the above example, we have implemented a classic use
of the Factory Design Pattern in JavaScript. We created a
NotificationFactory that determines what kind of
notification should be generated based on the type received
during instantiation. This is how we write a unit test for a
Factory Design Pattern.
1. import NotificationFactory from './NotificationFactory';
2. import EmailNotification from './EmailNotification';
3. import SMSNotification from './SMSNotification';
4. import PushNotification from './PushNotification';
5.
6. test('Factory should create an email notification
object', () => {
7. const notification =
[Link]('email');
8.
expect(notification).toBeInstanceOf(EmailNotification);
9. expect([Link]('Hello')).toBe('Email sent:
Hello');
10. });
11.
12. test('Factory should create an SMS notification
object', () => {
13. const notification =
[Link]('sms');
14.
expect(notification).toBeInstanceOf(SMSNotification);
15. expect([Link]('Hello')).toBe('SMS sent:
Hello');
16. });
17.
18. test('Factory should create a push notification object',
() => {
19. const notification =
[Link]('push');
20.
expect(notification).toBeInstanceOf(PushNotification);
21. expect([Link]('Hello')).toBe('Push
notification sent: Hello');
22. });
In the above code, we created three test cases, each
targeting a different instance that can be created by the
factory. A similar pattern is followed for all three Design
Patterns: first, we create an instance by passing a type to
the NotificationFactory. We then verify that the created
instance is of the type passed as the argument. Finally, we
check if the correct type of notification is generated.

Observer pattern
The Observer pattern allows an object, called the subject,
to maintain a list of its dependents, called observers, and
notify them of state changes. Let’s look at an example
denoting this Observer Design Pattern. We will consider a
newsletter subscription system where users can subscribe
to receive updates.
1. class Newsletter {
2. constructor() {
3. [Link] = [];
4. }
5.
6. subscribe(subscriber) {
7. [Link](subscriber);
8. }
9.
10. unsubscribe(subscriber) {
11. [Link] = [Link](sub =>
sub !== subscriber);
12. }
13.
14. notify(message) {
15. [Link](subscriber =>
[Link](message));
16. }
17. }
18.
19. class Subscriber {
20. constructor(name) {
21. [Link] = name;
22. }
23.
24. update(message) {
25. return `${[Link]} received: ${message}`;
26. }
27. }
28.
29. export { Newsletter, Subscriber };
In this code example, we created two components: a
Newsletter that maintains a list of subscribers and informs
them of all changes, and a Subscriber class that
subscribes to the Newsletter and receives updates from it.
Let us see how we can write a test case for this.
1. import { Newsletter, Subscriber } from './Newsletter';
2.
3. test('Subscribers should be notified of new messages',
() => {
4. const newsletter = new Newsletter();
5. const subscriber1 = new Subscriber('Alice');
6. const subscriber2 = new Subscriber('Bob');
7.
8. [Link](subscriber1, 'update');
9. [Link](subscriber2, 'update');
10.
11. [Link](subscriber1);
12. [Link](subscriber2);
13.
14. [Link]('New Edition Available');
15.
16.
expect([Link]).toHaveBeenCalledWith('Ne
w Edition Available');
17.
expect([Link]).toHaveBeenCalledWith('Ne
w Edition Available');
18. });
In the above code, we used Jest’s spyOn method to monitor
the update method of the two subscriber instances,
subscriber1 and subscriber2. Next, we use the
subscribe method of the Newsletter to add these
subscriber instances to its list of subscribers. We then call
the notify method of the Newsletter to trigger an update
to all its subscribers. Finally, we verify that each
subscriber’s update method was called with the message
passed in the notify method of the Newsletter.

Strategy Pattern
The Strategy Pattern allows a family of algorithms to be
defined, encapsulated, and made interchangeable. This
pattern is useful for defining a suite of algorithms and
selecting one to use at runtime. Let us consider the
example of an application that can handle multiple payment
methods to implement a Strategy Pattern:
1. class CreditCardPayment {
2. process(amount) {
3. return `Processing credit card payment of
${amount}`;
4. }
5. }
6.
7. class PayPalPayment {
8. process(amount) {
9. return `Processing PayPal payment of ${amount}`;
10. }
11. }
12.
13. class BitcoinPayment {
14. process(amount) {
15. return `Processing Bitcoin payment of
${amount}`;
16. }
17. }
18.
19. class PaymentContext {
20. constructor(strategy) {
21. [Link] = strategy;
22. }
23.
24. executeStrategy(amount) {
25. return [Link](amount);
26. }
27. }
28.
29. export { PaymentContext, CreditCardPayment,
PayPalPayment, BitcoinPayment };
In the above example, we have defined a product that
supports three payment categories: Credit Card, PayPal,
and Bitcoin. In the main class, we implemented a method
that allows users to execute and modify a payment strategy
at runtime. Now, let us write unit test cases for this Design
Pattern:
1. import { PaymentContext, CreditCardPayment,
PayPalPayment, BitcoinPayment } from
'./PaymentStrategy';
2.
3. test('Credit card payment strategy should process
payment', () => {
4. const strategy = new CreditCardPayment();
5. const context = new PaymentContext(strategy);
6. const result = [Link](100);
7. expect(result).toBe('Processing credit card payment
of 100');
8. });
9.
10. test('PayPal payment strategy should process
payment', () => {
11. const strategy = new PayPalPayment();
12. const context = new PaymentContext(strategy);
13. const result = [Link](100);
14. expect(result).toBe('Processing PayPal payment of
100');
15. });
16.
17. test('Bitcoin payment strategy should process
payment', () => {
18. const strategy = new BitcoinPayment();
19. const context = new PaymentContext(strategy);
20. const result = [Link](100);
21. expect(result).toBe('Processing Bitcoin payment of
100');
22. });
23.
To write unit test cases for Strategy Design Pattern, we test
all three payment strategies one by one. We initialize the
payment context with the instance of the payment we
require, and then check if the executeStrategy method
returns the correct payment method.

Decorator Pattern
The Decorator Pattern allows behavior to be added to an
individual object, dynamically, without affecting the
behavior of other objects from the same class.
This flexibility makes it ideal for scenarios where
enhancements or modifications are needed for specific
objects without altering the base implementation.
To demonstrate this, we consider a coffee-ordering system
where users can customize their coffee by adding
ingredients like milk or sugar. Each addition dynamically
updates the final price.
1. class Coffee {
2. cost() {
3. return 5;
4. }
5. }
6.
7. class MilkDecorator {
8. constructor(coffee) {
9. // Validate that the coffee object is properly
initialized
10. if (!coffee || typeof [Link] !== 'function') {
11. throw new Error('Invalid coffee object
provided to MilkDecorator.');
12. }
13. [Link] = coffee;
14. }
15.
16. cost() {
17. return [Link]() + 1; // Adds the cost of
milk
18. }
19. }
20.
21. class SugarDecorator {
22. constructor(coffee) {
23. // Validate that the coffee object is properly
initialized
24. if (!coffee || typeof [Link] !== 'function') {
25. throw new Error('Invalid coffee object
provided to SugarDecorator.');
26. }
27. [Link] = coffee;
28. }
29.
30. cost() {
31. return [Link]() + 0.5; // Adds the cost of
sugar
32. }
33. }
34.
35. export { Coffee, MilkDecorator, SugarDecorator };
In the above code, we implemented the Decorator Pattern
by creating a base class Coffee and allowing users to add a
MilkDecorator for milk and a SugarDecorator for sugar.
Each decorator modifies the final price accordingly. Now,
let’s write unit test cases for a Decorator Design Pattern:
1. import { Coffee, MilkDecorator, SugarDecorator } from
'./CoffeeDecorator';
2.
3. test('Cost of coffee should be 5', () => {
4. const coffee = new Coffee();
5. expect([Link]()).toBe(5);
6. });
7.
8. test('Cost of coffee with milk should be 6', () => {
9. const coffee = new MilkDecorator(new Coffee());
10. expect([Link]()).toBe(6);
11. });
12.
13. test('Cost of coffee with milk and sugar should be 6.5',
() => {
14. const coffee = new SugarDecorator(new
MilkDecorator(new Coffee()));
15. expect([Link]()).toBe(6.5);
16. });
To test Decorator Design Pattern, we check for the most
important differentiation for this pattern – the final impact
on the object. In this case, the differentiating factor will be
the cost of the coffee on addition of the decorators. As we
can see in the code above, we test a few permutations of
the decorators by verifying the price of only coffee, coffee
with milk, and coffee with milk and sugar.

Adapter Pattern
The Adapter Pattern allows incompatible interfaces to work
together. This pattern involves a single class, called an
adapter, which is responsible for joining functionalities of
independent or incompatible interfaces. Let us consider a
scenario where we have an old logging system and a new
logging system with different interfaces.
1. class OldLogger {
2. log(message) {
3. return `Old Logger: ${message}`;
4. }
5. }
6.
7. class NewLogger {
8. writeLog(message) {
9. return `New Logger: ${message}`;
10. }
11. }
12.
13. class LoggerAdapter {
14. constructor(logger) {
15. [Link] = logger;
16. }
17.
18. log(message) {
19. return [Link](message);
20. }
21. }
22.
23. export { OldLogger, NewLogger, LoggerAdapter };
24.
In this example, we can see the stark differences between
the two logging systems – OldLogger and NewLogger. By
using the LoggerAdapter, we can seamlessly integrate the
old with the new. Let’s write unit test cases for it:
1. import { OldLogger, NewLogger, LoggerAdapter } from
'./LoggerAdapter';
2.
3. test('OldLogger should log messages correctly', () => {
4. const oldLogger = new OldLogger();
5. expect([Link]('Test message')).toBe('Old
Logger: Test message');
6. });
7.
8. test('LoggerAdapter should adapt NewLogger to
OldLogger interface', () => {
9. const newLogger = new NewLogger();
10. const adapter = new LoggerAdapter(newLogger);
11. expect([Link]('Test message')).toBe('New
Logger: Test message');
12. });
For the Adapter Design Pattern, it’s critical to ensure that
both the older system and the newer system function
seamlessly without issues. Therefore, we test both the older
and the newer system. To verify the newer system, we use
the LoggerAdapter to test if it can log using the old
logger’s implementation method, confirming that the
Design Pattern is working correctly.
Builder Pattern
The Builder Pattern is used to construct a complex object
step by step. It allows you to create different types and
representations of an object using the same construction
process. Let us consider a scenario where we want to build
a complex meal with multiple components.
1. class Meal {
2. constructor() {
3. [Link] = [];
4. }
5.
6. addPart(part) {
7. [Link](part);
8. }
9.
10. showMeal() {
11. return `Meal contains: ${[Link](', ')}`;
12. }
13. }
14.
15. class MealBuilder {
16. constructor() {
17. [Link] = new Meal();
18. }
19.
20. addBurger() {
21. [Link]('Burger');
22. return this;
23. }
24.
25. addDrink() {
26. [Link]('Drink');
27. return this;
28. }
29.
30. addFries() {
31. [Link]('Fries');
32. return this;
33. }
34.
35. build() {
36. return [Link];
37. }
38. }
39.
40. export { Meal, MealBuilder };
In the above example, we created a base class Meal and a
MealBuilder class that allows the base meal to be built
piece-by-piece. By segregating each step, we make the
code configurable and easier to debug. Now, let’s write unit
test cases for this:
1. import { MealBuilder } from './MealBuilder';
2.
3. test('MealBuilder should create a meal with a burger,
drink, and fries', () => {
4. const builder = new MealBuilder();
5. const meal =
[Link]().addDrink().addFries().build();
6. expect([Link]()).toBe('Meal contains:
Burger, Drink, Fries');
7. });
8.
9. test('MealBuilder should create a meal with only a
burger and drink', () => {
10. const builder = new MealBuilder();
11. const meal =
[Link]().addDrink().build();
12. expect([Link]()).toBe('Meal contains:
Burger, Drink');
13. });
Testing the Builder Pattern involves verifying that the
components are added correctly, and the final product is as
expected. In this example, we check multiple combinations
of the meal by adding different ingredients. We verify the
final meal created by utilizing the showMeal method of the
base class.

Prototype Pattern
The Prototype Pattern is used to create new objects by
copying an existing object, known as the prototype. This
pattern is useful when the cost of creating a new object is
high, and it is more efficient to copy an existing object. Let
us consider a scenario where we need to create multiple
copies of a user profile template.
1. class UserProfile {
2. constructor(name, age, email) {
3. [Link] = name;
4. [Link] = age;
5. [Link] = email;
6. }
7.
8. clone() {
9. return new UserProfile([Link], [Link],
[Link]);
10. }
11. }
12.
13. export default UserProfile;
In the above code example, we defined a UserProfile class
with intrinsic properties such as name, age, and email.
Additionally, we implemented a clone method that creates
a new instance of this class, effectively duplicating the
existing object. Now, let’s see how to write a test case for
this:
1. import UserProfile from './UserProfile';
2.
3. test('UserProfile should be cloned correctly', () => {
4. const originalProfile = new UserProfile('John Doe',
30, '[Link]@[Link]');
5. const clonedProfile = [Link]();
6.
7. expect(clonedProfile).[Link](originalProfile); //
Ensure it's a new object
8. expect([Link]).toBe('John Doe');
9. expect([Link]).toBe(30);
10.
expect([Link]).toBe('[Link]@[Link]
m');
11. });
12.
Testing the Prototype Pattern involves ensuring that the
cloned object has the same properties as the original
object. In the above unit test, we first create an original
profile and then its clone. We then verify that the cloned
instance is a new instance, distinct from the original.
Finally, we confirm that the cloned instance has the same
properties as the original profile.

Mediator Pattern
The Mediator Pattern defines an object that encapsulates
how a set of objects interact. This pattern promotes loose
coupling by keeping objects from referring to each other
explicitly and allowing their interaction to be managed by
the mediator. Let us consider the example of a chat room
application where users send messages to each other
through a chat room mediator.
1. class ChatRoom {
2. showMessage(user, message) {
3. const time = new Date().toLocaleTimeString();
4. return `${time} [${user}]: ${message}`;
5. }
6. }
7.
8. class User {
9. constructor(name, chatRoom) {
10. [Link] = name;
11. [Link] = chatRoom;
12. }
13.
14. send(message) {
15. return [Link]([Link],
message);
16. }
17. }
18.
19. export { ChatRoom, User };
In the above code example, we defined a chat room using
the ChatRoom class, which contains a method to display a
message with the correct timestamp. Additionally, we
created a User class that defines the user’s name and
associates the user with the chat room. A send method is
used for sending messages. Now, let’s write unit test cases
for this:
1. import { ChatRoom, User } from './ChatRoom';
2.
3. test('ChatRoom should mediate messages between
users', () => {
4. const chatRoom = new ChatRoom();
5. const user1 = new User('Alice', chatRoom);
6. const user2 = new User('Bob', chatRoom);
7.
8. const message1 = [Link]('Hello, Bob!');
9. const message2 = [Link]('Hi, Alice!');
10.
11. expect(message1).toMatch(/^\d{1,2}:\d{2}:\d{2}
[APM]{2} \[Alice\]: Hello, Bob!$/);
12. expect(message2).toMatch(/^\d{1,2}:\d{2}:\d{2}
[APM]{2} \[Bob\]: Hi, Alice!$/);
13. });
Testing the Mediator Pattern involves ensuring that
messages are correctly mediated by the chat room. In the
above unit test case, we verify this by creating two users
and having each user send a different message. We then
check to ensure that the correct messages have been sent
and mediated properly.

Chain of Responsibility Pattern


The Chain of Responsibility Pattern allows an object to pass
a request along a chain of potential handlers until the
request is handled. This pattern is useful for decoupling the
sender of a request from its receivers, giving more than
one object a chance to handle the request. Let us consider
a scenario where a support system routes tickets based on
their severity levels.
1. class SupportHandler {
2. setNext(handler) {
3. [Link] = handler;
4. return handler;
5. }
6.
7. handle(request) {
8. if ([Link]) {
9. return [Link](request);
10. }
11. return null;
12. }
13. }
14.
15. class LowLevelSupport extends SupportHandler {
16. handle(request) {
17. if ([Link] === 'low') {
18. return `Low-level support handling ticket:
${[Link]}`;
19. }
20. return [Link](request);
21. }
22. }
23.
24. class MidLevelSupport extends SupportHandler {
25. handle(request) {
26. if ([Link] === 'mid') {
27. return `Mid-level support handling ticket:
${[Link]}`;
28. }
29. return [Link](request);
30. }
31. }
32.
33. class HighLevelSupport extends SupportHandler {
34. handle(request) {
35. if ([Link] === 'high') {
36. return `High-level support handling ticket:
${[Link]}`;
37. }
38. return [Link](request);
39. }
40. }
41.
42. export { LowLevelSupport, MidLevelSupport,
HighLevelSupport };
As we can see from the code above, we have installed a
level system for ticket support which can dynamically
choose and send the ticket to be handled at an appropriate
level. Now, let’s see how we can write a unit test case for
the same:
1. import { LowLevelSupport, MidLevelSupport,
HighLevelSupport } from './SupportHandlers';
2.
3. test('Low-level support should handle low-level
requests', () => {
4. const lowLevelSupport = new LowLevelSupport();
5. const midLevelSupport = new MidLevelSupport();
6. const highLevelSupport = new HighLevelSupport();
7.
8.
[Link](midLevelSupport).setNext(hig
hLevelSupport);
9.
10. const request = { level: 'low', message: 'Reset
password' };
11.
expect([Link](request)).toBe('Low-
level support handling ticket: Reset password');
12. });
13.
14. test('Mid-level support should handle mid-level
requests', () => {
15. const lowLevelSupport = new LowLevelSupport();
16. const midLevelSupport = new MidLevelSupport();
17. const highLevelSupport = new HighLevelSupport();
18.
19.
[Link](midLevelSupport).setNext(hig
hLevelSupport);
20.
21. const request = { level: 'mid', message: 'System
outage' };
22.
expect([Link](request)).toBe('Mid-
level support handling ticket: System outage');
23. });
24.
25. test('High-level support should handle high-level
requests', () => {
26. const lowLevelSupport = new LowLevelSupport();
27. const midLevelSupport = new MidLevelSupport();
28. const highLevelSupport = new HighLevelSupport();
29.
30.
[Link](midLevelSupport).setNext(hig
hLevelSupport);
31.
32. const request = { level: 'high', message: 'Security
breach' };
33.
expect([Link](request)).toBe('High-
level support handling ticket: Security breach');
34. });
Testing the Chain of Responsibility Pattern involves
ensuring that requests are handled by the appropriate
handler in the chain. In this code example, we try three
different levels of support – low, medium, and high, and
then we verify if the ticket is routed to the proper level of
support.

Summary
Based on what we have covered in this section so far, let us
summarize and compile a list of what needs to be tested for
each Design Pattern:

Singleton Pattern
• Uniqueness: Ensure only one instance is created.
• Instance identity: Confirm that repeated calls return
the same instance.
• State consistency: Check that the instance maintains
state across multiple accesses.

Factory pattern
• Object creation: Verify that the factory method
creates objects of the correct type.
• Exception handling: Ensure that the factory handles
invalid input appropriately.
• Scalability: Test that the factory can be extended to
create new types of objects without modifying existing
code.

Observer pattern
• Subscription: Test that observers can subscribe to the
subject.
• Unsubscription: Ensure observers can unsubscribe
from the subject.
• Notification: Verify that all subscribed observers are
notified of state changes.
• Order of notification: Check the order in which
observers are notified, if order matters.

Strategy Pattern
• Algorithm selection: Verify that the context correctly
selects and uses the provided strategy.
• Strategy interchangeability: Ensure different
strategies can be swapped and produce correct results.
• Extensibility: Test that new strategies can be added
without modifying existing strategies.

Decorator Pattern
• Dynamic behavior: Verify that decorators can
dynamically add behavior to the original object.
• Combinability: Ensure multiple decorators can be
combined and applied in sequence.
• State independence: Check that the decorated object
maintains state independently of other decorators.

Adapter Pattern
• Interface compatibility: Verify that the adapter
makes the adapted object compatible with the target
interface.
• Functionality: Ensure the adapter correctly maps
methods from the adaptee to the target interface.
• Transparency: Test that the client can use the adapted
object without being aware of the adapter’s presence.

Builder Pattern
• Stepwise construction: Verify that the builder
constructs objects in a step-by-step manner.
• Final product: Ensure the final object constructed by
the builder is correct.
• Fluent interface: Check that the builder’s interface
allows chaining of methods (if applicable).

Prototype Pattern
• Cloning: Test that objects can be cloned correctly
from the prototype.
• Identity: Ensure the cloned object is a distinct
instance but with identical properties to the prototype.
• Customization: Verify that the cloned object can be
customized after cloning.

Mediator Pattern
• Interaction management: Verify that the mediator
correctly manages interactions between colleagues.
• Decoupling: Ensure that colleagues are decoupled
and only communicate via the mediator.
• State management: Check that the mediator handles
state changes and notifies relevant colleagues.

Chain of Responsibility Pattern


• Request handling: Verify that requests are passed
along the chain until handled.
• Handler order: Ensure that handlers process requests
in the correct order.
• Fallback handling: Test that unhandled requests are
appropriately managed or passed to a default handler.

Planning unit tests for JavaScript Design


Patterns
As discussed in previous sections, unit tests are designed to
verify the correctness of individual units of code, typically
functions or methods. The goal is to ensure that each unit
performs as expected. Planning unit tests for JavaScript
Design Patterns involves several key steps to ensure that
our tests are thorough, maintainable, and effective. Let’s
understand the approach:

Understand the Design Pattern


Before we can plan effective unit tests, we need to have a
clear understanding of the Design Pattern we are using.
This includes knowing the pattern’s structure, its purpose,
and the typical interactions between its components. In the
previous section, we outlined the use cases for ten
commonly used Design Patterns in JavaScript, the types of
tests needed for each, and examples of how to write them.
We should keep this previous section in mind when writing
individual test cases for each Design Pattern.

Identify key components and responsibilities


For each Design Pattern, we need to identify the main
components and their responsibilities. Understanding what
each component is supposed to do can help us define what
exactly needs to be tested.

Define test cases


For each component and responsibility, we need to define
the test cases that cover:
• Expected behavior: What should happen under
normal conditions?
• Edge cases: How should the component behave under
extreme or unusual conditions?
• Error handling: How does the component handle
errors or invalid input?

Mock dependencies
We need to use mocking to isolate the unit under test.
Mocking allows us to simulate dependencies, which helps
ensure that our tests are focused on the component itself
rather than on its collaborators.

Automate tests
We can use a testing framework like Jest, Mocha, or
Jasmine to automate the execution of our test cases.
Automation helps ensure consistency and makes it easier to
run tests frequently.

Ensure coverage
We can use coverage tools like Istanbul to measure how
much of our code is exercised by our tests. While aiming for
high coverage is important, it’s equally crucial to write
meaningful tests that serve as a blueprint for how our
application is built, rather than just covering lines of code.

Conclusion
In this chapter, we have explored a critical part of the
software development cycle—testing—and how Design
Patterns in JavaScript can influence the way unit tests are
written. We began by understanding what testing is, the
different types of testing, and its importance in the
software development life cycle. Then, we examined ten of
the most commonly used JavaScript Design Patterns and
explored unit test cases for each. Finally, we discussed how
to plan unit test cases for these JavaScript Design Patterns.
This chapter has provided insight into how to write unit
test cases for JavaScript Design Patterns and how these
patterns can shape the process of writing effective tests.
In the next chapter, we will discuss the process to write
and create Design Patterns.

Points to remember
• Testing is a crucial part of the software development
cycle. In JavaScript, testing involves writing and
executing code to verify that applications function
correctly and meet specified requirements. This process
includes creating tests that simulate various scenarios
and inputs to ensure the application behaves as
expected under different conditions.
• Testing has a significant role in writing small to
enterprise level code. Testing helps identify and fix
bugs during the development process. Additionally,
testing ensures that even when the code needs to be
refactored, any regressions will be caught by the tests
thereby not impacting the user’s experience of the
product.
• A well-written test is easier to maintain and extend,
and thereby boosts confidence amongst developers.
Testing also helps in frequent and reliable code
deployments.
• The Singleton Pattern restricts the instantiation of a
class to a single object. To test Singleton Patterns, we
can check to ensure only one instance is created and
confirm that repeated calls return the same instance.
• The Factory pattern provides a way to create objects
without specifying the exact class of object that will be
created. To test code written using Factory Design
Pattern, we can verify that the factory method creates
objects of the correct type, and that the method can be
extended to create new types of objects without
modifying existing code.
• The Observer pattern allows an object, to maintain a
list of its observers, and notify them of state changes.
To test it, we can check if observers can subscribe and
unsubscribe the object and further verify that all
subscribed observers are notified of state changes.
• To write effective test cases for Design Patterns in
JavaScript, we first need to have a thorough
understanding of the Design Pattern and understand its
nuances to write a test case for it.
• For writing an effective test case for these Design
Patterns, we first need to identify the main components,
its responsibilities, and then define the test cases.
• While defining a test case for a Design Pattern, we
should test for its expected behaviour, edge-case
scenarios, and error handling.
• For API-driven code, it is necessary to mock certain
functions and applications and automate the code. Code
coverage often helps in understanding the health of our
application and how secure will it be when users start
using it in their daily grind.

Exercises
1. Which of the following things need to be tested
when writing a unit test case for a Decorator
Design Pattern?
a. Ensuring that the cloned object is a distinct instance
but with identical properties to the prototype.
b. Ensure that the final object constructed by the builder
is correct.
c. Verify that behaviour can be added dynamically to the
original object.
d. Verify that the context correctly selects and uses the
provided strategy.
2. Which of the following scenarios are NOT to be
considered when writing unit test cases for
Observer Design Pattern?
a. Verify that requests are passed along the chain until
handled.
b. Verify that all subscribed observers are notified of
state changes.
c. Confirm if the observer can unsubscribe from a
subject.
d. Checking the order in which observers are notified.
3. Which of the following tasks should not be
considered when planning a unit test cases for
JavaScript Design Patterns?
a. Ensuring coverage of the total written test cases
using a tool like Istanbul.
b. Ignore the error handling part of the code as it is
more important to test its working functionality.
c. Identifying key components and their responsibilities
so that we have a clear understanding of what we
need to test in a component.
d. Have a thorough understanding of the Design Pattern
for which the unit test cases need to be written.

Answers
1. c
2. a
3. b
CHAPTER 11
Writing and Creating
Design Patterns

Introduction
In software development, Design Patterns are vital for
transforming theoretical concepts into practical, reusable
solutions. This chapter focuses on crafting Design Patterns
for JavaScript, emphasizing their role in solving recurring
problems and enhancing project robustness. We will
explore the steps in authoring Design Patterns, from
analyzing problem scenarios to defining the problem and
context. Understanding the structure and elements of a
Design Pattern is essential for clarity and utility. Clear,
concise pattern descriptions and strategies for ensuring
reusability and flexibility will be discussed. Additionally,
the importance of review, refinement, and thorough
documentation will be highlighted.
Finally, we will explore the significance of publishing and
sharing Design Patterns, covering various platforms and
fostering collaborative development. This guide aims to
equip us with the knowledge and skills to create impactful
Design Patterns that enhance JavaScript development.

Structure
This chapter will cover the following topics:
• Design Pattern authoring process
• Creating effective Design Patterns
• Publishing and sharing Design Patterns

Objectives
The objectives of this chapter are to provide a
comprehensive understanding of the process involved in
writing and creating effective Design Patterns for
JavaScript. By the end of this chapter, readers will be able
to analyse problem scenarios and define the context
accurately. They will learn the essential components and
structure of Design Patterns, enabling them to write clear
and concise descriptions. The chapter also aims to teach
strategies for ensuring the reusability and flexibility of
Design Patterns, making them adaptable to various coding
environments. Additionally, readers will understand the
importance of thorough review, refinement, and
documentation. Finally, the chapter will guide readers on
how to publish and share their Design Patterns, fostering
collaborative development within the community.

Design Pattern authoring process


Understanding the Design Pattern authoring process is
essential for creating robust and reusable solutions in
JavaScript development. This section delves into the
systematic approach required to author effective Design
Patterns. It begins with identifying recurring problems and
analysing their context, followed by defining the problem
clearly. We will explore how to structure a Design Pattern,
detailing its key components and ensuring clarity and
comprehensibility. This section also covers best practices
for writing concise pattern descriptions and emphasizes the
importance of iterative refinement. By mastering this
process, we can create Design Patterns that significantly
enhance our development workflow and code quality.

Analysis of problem scenarios


In the realm of software development, identifying and
understanding problem scenarios is the first and arguably
most critical step in creating effective Design Patterns. A
well-analyzed problem scenario not only illuminates the
issues at hand but also provides the necessary context for
developing a robust and reusable solution.
The genesis of a Design Pattern often lies in the recognition
of recurring problems within the codebase or application
architecture. These are the issues that surface repeatedly
across different projects or within various modules of the
same project. To identify these problems, it is essential to
maintain a keen awareness of the common pain points
encountered during development. Regular code reviews,
team discussions, and retrospectives are invaluable
practices for uncovering these recurring issues. We need to
pay attention to areas where code complexity, redundancy,
or maintainability issues consistently arise. These patterns
of problems are prime candidates for the development of
Design Patterns. Once a recurring problem is identified,
the next step is to understand the context in which it
occurs which we will cover in the next section.

Understanding the context


Once a recurring problem is identified, the next step is to
understand the context in which it occurs. Context
encompasses the environment, conditions, and constraints
under which the problem manifests. This includes the
specific requirements of the application, the technologies in
use, the existing architecture, and any external
dependencies. A thorough contextual understanding
ensures that the Design Pattern we create will be relevant
and applicable. It is important to consider questions such
as:
• What triggers the problem?
• Under what conditions does the problem become more
pronounced?
• How does the problem impact the overall system?
• What are the specific constraints and requirements
related to this problem?
By answering these questions, we can gain a
comprehensive view of the problem’s context, which is
crucial for crafting a solution that fits seamlessly within the
given environment.

Deconstructing the problem


After identifying the problem and understanding its context
in the previous step, the next step is to deconstruct the
problem into its fundamental components. This involves
breaking down the problem into smaller, manageable parts
to understand its core issues better. We need to analyse the
symptoms of the problem and trace them back to their root
causes. This deconstruction helps in pinpointing the exact
nature of the problem and provides clarity on what the
Design Pattern needs to address. Key aspects to consider
during this phase include:
• The specific challenges posed by the problem.
• The existing solutions or workarounds that have been
attempted.
• The limitations and shortcomings of these solutions.
• The dependencies and interactions with other
components or systems.
A detailed deconstruction lays the groundwork for a
targeted and effective solution.

Tools and techniques for analysis


Several tools and techniques can facilitate the analysis of
problem scenarios. These include:
• Root cause analysis (RCA): A systematic process for
identifying the root causes of problems.
• Fishbone diagram: A visual tool for categorizing
potential causes of problems to identify root causes.
• SWOT analysis: A strategic planning tool used to
identify strengths, weaknesses, opportunities, and
threats related to the problem.
• Mind mapping: A technique for visually organizing
information to see relationships and hierarchies.
Utilizing these tools can enhance the thoroughness and
accuracy of our problem scenario analysis. Analyzing
problem scenarios is a critical step in the Design Pattern
authoring process. By identifying recurring problems,
understanding their context, deconstructing the issues, and
thoroughly documenting our findings, we can lay a solid
foundation for developing effective and reusable Design
Patterns.

Defining the problem and context


Defining the problem and its context is a pivotal step in the
Design Pattern authoring process. This step involves clearly
articulating the problem we intend to solve and
understanding the environment in which it occurs. A well-
defined problem, paired with a comprehensive
understanding of its context, ensures that the Design
Pattern we develop is both effective and relevant.

Articulating the problem and context


The first step in defining the problem is to articulate it
clearly and concisely. A well-articulated problem statement
serves as the foundation for the Design Pattern. It should
be specific enough to address the core issue but broad
enough to allow for a reusable solution. To articulate the
problem effectively, consider the following guidelines:
• Be specific: We need to clearly describe the problem,
avoiding vague or ambiguous language. We need to
specify the symptoms and manifestations of the
problem.
• Be objective: We should focus on the facts and avoid
subjective opinions. We should describe what is
happening rather than why we think it is happening.
• Be concise: We must keep the problem statement
brief and to the point. We should strictly avoid
unnecessary details that might obscure the main issue.
Example of a well-articulated problem statement: In a
JavaScript application with multiple asynchronous
operations, managing the state and ensuring data
consistency across various components is challenging,
leading to unpredictable behavior and bugs.
From the context gathered in the previous step, we need to
articulate it into a specific description. Example of a
context description: The problem occurs in a single-page
web application built with React. The application heavily
relies on asynchronous data fetching from multiple APIs.
The state management is currently handled by a global
store using Redux. The application must support real-time
updates and handle large volumes of data efficiently.
Analyzing the impact and RCA
Understanding the impact of the problem on the overall
system is another important aspect of defining the problem
and context. The impact analysis helps to prioritize the
problem and justify the need for a Design Pattern. Consider
the following when analysing the impact:
• System performance: We need to evaluate how the
problem affects the performance of the system. Does it
lead to slow response times, increased load times, or
inefficient resource usage?
• User experience: Next, we need to consider the
impact on the user experience. Does the problem cause
UI glitches, data inconsistencies, or crashes that
frustrate users?
• Maintainability: We should assess how the problem
affects the maintainability of the codebase. Does it lead
to complex, tangled code that is difficult to debug and
extend?
• Business goals: Finally, we must reflect on how the
problem impacts the achievement of business goals.
Does it hinder the delivery of key features, affect
customer satisfaction, or lead to increased costs?
Once we have gathered the following, we can articulate
this into an analysis description. Example of an impact
analysis: The unpredictable behavior caused by
inconsistent state management leads to a poor user
experience, with users frequently encountering stale or
incorrect data. This negatively impacts user engagement
and satisfaction, resulting in increased support costs and
potential loss of customers. Additionally, the complex state
management code makes it difficult for developers to add
new features or fix bugs, slowing down the development
process and increasing maintenance overhead.
Using RCA in the previous step, we should now have a well-
defined description denoting the root cause of the
recurring issue. An example would be:
• Why is the application showing stale data? — Because
the state is not updated consistently.
• Why is the state not updated consistently? — Because
asynchronous operations are not managed properly.
• Why are asynchronous operations not managed
properly? — Because there is no centralized mechanism
to handle them.
• Why is there no centralized mechanism to handle
them? — Because the current state management
approach does not support it.
• Why does the current state management approach not
support it? — Because it was designed for simpler use
cases without considering complex asynchronous
workflow.
Once we have achieved these four categories of significant
data, we can now create a detailed documentation. An
example for that would be:
• Problem statement: In a JavaScript application with
multiple asynchronous operations, managing the state
and ensuring data consistency across various
components is challenging, leading to unpredictable
behavior and bugs.
• Context description: The problem occurs in a single-
page web application built with React. The application
heavily relies on asynchronous data fetching from
multiple APIs. The state management is currently
handled by a global store using Redux. The application
must support real-time updates and handle large
volumes of data efficiently.
• Impact analysis: The unpredictable behavior caused
by inconsistent state management leads to a poor user
experience, with users frequently encountering stale or
incorrect data. This negatively impacts user
engagement and satisfaction, resulting in increased
support costs and potential loss of customers.
Additionally, the complex state management code
makes it difficult for developers to add new features or
fix bugs, slowing down the development process and
increasing maintenance overhead.
• Root cause analysis: Using the Five Whys technique,
it was determined that the root cause of the
inconsistent state management is the lack of a
centralized mechanism to handle asynchronous
operations, stemming from a state management
approach designed for simpler use cases.

Design Pattern structure and elements


A well-defined structure is essential for creating clear,
comprehensible, and reusable Design Patterns. The
structure provides a standardized format for documenting
Design Patterns, ensuring that they can be easily
understood and implemented by developers.

Key elements of a Design Pattern


A Design Pattern typically includes several key elements
that collectively provide a comprehensive description of the
problem and its solution. These elements include:
• Pattern name
• Intent
• Also known as
• Motivation
• Applicability
• Structure
• Participants
• Collaborations
• Consequences
• Implementation
• Sample code
• Known uses
• Related patterns
Each of these elements plays a crucial role in defining the
Design Pattern and ensuring its usability. Let us take a look
at them one by one:
Pattern name
The pattern name is the identifier used to refer to the
Design Pattern. It should be concise, descriptive, and
memorable. A well-chosen name facilitates communication
among developers and makes it easier to refer to the
pattern in discussions and documentation.
Example: Singleton, Observer, Factory Method
Intent
The intent describes the purpose of the Design Pattern. It
provides a high-level summary of what the pattern does
and why it is useful. The intent should be clear and to the
point, offering a quick understanding of the pattern’s
objective.
Example: Ensure a class has only one instance, and
provide a global point of access to it. (Singleton)
Also known as
Some Design Patterns are known by multiple names. This
section lists any alternative names for the pattern, helping
readers recognize it under different terminology.
Example: Also known as Publisher-Subscriber. (Observer)
Motivation
The motivation explains the problem the Design Pattern
addresses and why this solution is needed. It typically
includes a scenario or a real-world example that illustrates
the problem and demonstrates how the pattern provides a
solution. The motivation helps readers understand the
practical application and benefits of the pattern.
Example: In a graphics framework, an application needs to
be notified when the state of a graphical object changes.
The Observer Pattern allows the application to register
interest in an object’s state changes and receive updates
when the state changes. (Observer)
Applicability
The applicability section describes the contexts and
situations in which the Design Pattern is appropriate. It
outlines the conditions under which the pattern can be
applied effectively and highlights any prerequisites or
constraints.
Example: Use the Singleton Pattern when there must be
exactly one instance of a class, and it must be accessible
from a well-known access point. (Singleton)
Structure
The structure provides a visual representation of the
Design Pattern using class diagrams, sequence diagrams,
or other graphical tools. These diagrams illustrate the
relationships and interactions between the components
involved in the pattern, offering a clear overview of its
architecture.
Example:
Figure 11.1: A visual description of the Design Pattern
Participants
The participants section lists and describes the classes and
objects that participate in the Design Pattern. It provides a
brief overview of each participant’s role and
responsibilities within the pattern.
Example:
• Subject: Knows its observers. Provides methods to
attach and detach observers.
• Observer: Defines an updating interface for objects
that should be notified of changes in a subject.
Collaborations
Collaborations describe how the participants interact to
achieve the pattern’s intent. This section explains the flow
of communication and the sequence of actions among the
participants.
Example: Observers register with the subject to receive
updates. When the subject changes state, it notifies all
registered observers by calling their update() method.
Consequences
The consequences section outlines the results and trade-
offs of using the Design Pattern. It includes the benefits
and potential drawbacks, helping developers understand
the impact of implementing the pattern in their code.
Example: The Observer Pattern promotes loose coupling
between the subject and its observers. However, it can lead
to unexpected updates if not managed carefully.
Implementation
The implementation section provides guidance on how to
implement the Design Pattern. It includes tips, best
practices, and potential pitfalls to avoid. This section may
also address language-specific considerations and
variations.
Example: Ensure that the Singleton class’s constructor is
private to prevent direct instantiation. Provide a static
method for accessing the single instance.
Sample code
Sample code offers a concrete example of how to
implement the Design Pattern in code. It demonstrates the
pattern in action, providing a practical reference for
developers. The code should be clear, well-commented, and
relevant to the pattern’s intent.
Example:
1. // Subject
2. class Subject {
3. constructor() {
4. [Link] = [];
5. }
6.
7. // Method to attach observers
8. attach(observer) {
9. [Link](observer);
10. }
11.
12. // Method to detach observers
13. detach(observer) {
14. [Link] = [Link](obs => obs
!== observer);
15. }
16.
17. // Method to notify all observers
18. notify() {
19. [Link](observer =>
[Link]());
20. }
21.
22. // Example state change
23. someBusinessLogic() {
24. [Link]('Subject: Doing something
important...');
25. [Link]();
26. }
27. }
28.
29. // Observer
30. class Observer {
31. constructor(name) {
32. [Link] = name;
33. }
34.
35. // Update method to be called when the subject
changes state
36. update() {
37. [Link](`${[Link]} has been notified.`);
38. }
39. }
40.
41. // Usage
42. const subject = new Subject();
43.
44. const observer1 = new Observer('Observer 1');
45. const observer2 = new Observer('Observer 2');
46.
47. [Link](observer1);
48. [Link](observer2);
49.
50. [Link]();
51. // Output:
52. // Subject: Doing something important...
53. // Observer 1 has been notified.
54. // Observer 2 has been notified.
55.
56. [Link](observer1);
57.
58. [Link]();
59. // Output:
60. // Subject: Doing something important...
61. // Observer 2 has been notified.
Known uses
The known uses section lists real-world examples of the
Design Pattern in existing software systems. This helps
validate the pattern’s applicability and demonstrates its
utility in practical scenarios.
Example: The addEventListener and
removeEventListener methods in JavaScript follow the
Observer Pattern, allowing objects to listen for and react to
events.
Related patterns
Related patterns highlight connections and similarities with
other Design Patterns. This section helps developers
understand how the pattern fits into the broader ecosystem
of Design Patterns and when it might be used in
conjunction with others.
Example:
• Mediator: The Mediator Pattern centralizes complex
communications and control logic between related
objects, making it easier to manage relationships that
could otherwise be handled by multiple observers.
• Singleton: The Singleton Pattern is sometimes used
with the Observer Pattern to ensure that a subject has
only one instance, particularly when managing
application-wide events.
• MVC: The Observer Pattern is a fundamental part of
the MVC architecture, where the view observes the
model and updates itself whenever the model’s state
changes.
Defining a clear and comprehensive structure for Design
Patterns is essential for ensuring their clarity, usability,
and reusability. By including all the key elements and
organizing them in a consistent format, we can create
Design Patterns that are easy to understand and
implement.

Creating effective Design Patterns


Creating effective Design Patterns involves not only solving
specific problems but also ensuring that the solutions are
reusable, flexible, and easy to understand. This section will
delve into the strategies and practices for writing clear and
concise pattern descriptions, ensuring reusability and
flexibility, and conducting thorough review, refinement,
and documentation.

Writing clear and concise pattern descriptions


The effectiveness of a Design Pattern is significantly
influenced by how well it is described. Clear and concise
pattern descriptions make it easier for developers to
understand and apply the pattern correctly. Here are some
tips to write better descriptions for Design Patterns:

Use simple and precise language


Avoid overly complex language and technical jargon that
might confuse readers. Instead, use simple and precise
language that conveys the core concepts clearly. The goal
is to make the pattern accessible to developers of varying
skill levels.
Example: Instead of saying, The Observer Pattern
facilitates decoupling of the subject from its dependents,
say, The Observer Pattern allows an object to notify other
objects when its state changes.

Focus on the core idea


While it is important to provide enough detail, avoid
overwhelming readers with unnecessary information. Focus
on the core idea of the pattern, including the problem it
solves, the solution it provides, and the context in which it
is applicable.
Example: A concise description for the Singleton Pattern
might be: The Singleton Pattern ensures a class has only
one instance and provides a global point of access to it.
This is useful for managing shared resources like
configuration settings or database connections.

Use visual aids


Diagrams and code snippets can significantly enhance the
clarity of our pattern descriptions. Class diagrams,
sequence diagrams, and flowcharts can illustrate the
relationships and interactions within the pattern, while
code snippets provide concrete examples of
implementation.
Example: A class diagram for the Observer Pattern can
show how the Subject and Observer classes interact, while
a code snippet can demonstrate how to implement the
notify method in the Subject class.

Provide real-world examples


Including real-world examples helps readers understand
how the pattern can be applied in practice. These examples
should be relevant to common scenarios developers might
encounter.
Example: For the Observer Pattern, an example could be a
UI framework where UI components need to update in
response to changes in application state.

Ensuring reusability and flexibility


A good Design Pattern should be reusable in different
contexts and flexible enough to accommodate future
changes and extensions. Let us look at some methods that
can help us achieve that:

Generalize the solution


While the pattern should solve a specific problem, it should
also be generalized enough to apply to a variety of similar
problems. We should avoid tailoring the pattern too
narrowly to a specific use case.
Example: Instead of designing a Singleton Pattern
specifically for a logging utility, describe how it can be
used for any shared resource that should have only one
instance, such as a configuration manager or database
connection. However, it is important to recognize that the
Singleton Pattern may not always be the ideal solution in
certain scenarios. For example, in distributed systems,
where resources are often spread across multiple nodes or
services, enforcing a single instance may create
bottlenecks or scalability issues. In these cases,
alternatives such as dependency injection or service
locators might be more appropriate to manage shared
resources in a scalable and flexible way.

Promote loose coupling


We need to ensure that the components in the Design
Pattern are loosely coupled. Loose coupling enhances
reusability and flexibility by minimizing dependencies
between components, making it easier to adapt the pattern
to different contexts.
Example: In the Observer Pattern, the Subject should not
have knowledge of the concrete Observer implementations.
Instead, it should interact with them through an abstract
interface.

Facilitate extension
Design the pattern in a way that makes it easy to extend.
Consider the Open/Closed Principle, which states that
software entities should be open for extension but closed
for modification. This means we should be able to add new
functionality without altering existing code.
Example: In the Factory Method Pattern, the creator class
should be able to produce new types of products without
modifying the existing factory method. Instead, we can
extend the creator class and override the factory method.
Review, refinement, and documentation
The process of creating effective Design Patterns does not
end with the initial draft. Review, refinement, and thorough
documentation are crucial steps to ensure the pattern’s
quality and usability. Here are some of the ways we can do
this:

Peer review
Conducting peer reviews with other developers is one of
the first methods for getting feedback on the pattern. Peer
reviews help us identify ambiguities, inconsistencies, and
potential improvements. To make the peer review process
actionable, we should encourage reviewers to evaluate the
pattern based on specific criteria:
• Readability: Is the pattern easy to understand? Are
the names of functions, variables, and classes intuitive?
• Scalability: Can the pattern handle increased load or
complexity? Does it support future changes without
requiring significant modifications?
• Edge case handling: Does the pattern account for
edge cases and error scenarios? Are there any
situations where it might fail or produce unexpected
results? Reviewers should feel empowered to ask
questions and challenge assumptions to refine the
design further.

Iterative refinement
Based on feedback from peer reviews and practical
applications, refine the pattern iteratively. Each iteration
should aim to improve clarity, address edge cases, and
enhance the pattern’s applicability. This iterative process
helps ensure the pattern evolves to meet real-world needs.
Comprehension documentation
Thorough documentation is essential for the widespread
adoption of Design Patterns. We need to ensure that our
documentation covers all aspects of the pattern, including:
• Intent
• Motivation
• Structure
• Participants
• Collaborations
• Consequences
• Implementation
• Sample code
• Known uses
• Related patterns
By following these practices, we can develop Design
Patterns that are not only effective solutions to specific
problems but also valuable resources for the broader
developer community.

Publishing and sharing Design Patterns


Sharing Design Patterns with the developer community is a
crucial step in promoting best practices and facilitating the
adoption of proven solutions. In this section, we will
explore various platforms and repositories for publishing
Design Patterns, as well as strategies for collaborative
pattern development.

Publishing platforms and repositories


Choosing the right platforms and repositories to publish
our Design Patterns ensures they reach a wide audience
and are easily accessible to developers seeking solutions.
Let us take a look at several platforms and repositories that
can help us in this endeavor:

Online pattern repositories


Online repositories are dedicated platforms where
developers can publish and access Design Patterns. These
repositories often include features for searching,
categorizing, and discussing patterns.
Examples:
• Hillside Group Pattern Repository: A well-known
repository that hosts a wide variety of Design Patterns
across different domains.
• Portland Pattern Repository (WikiWikiWeb): One
of the oldest pattern repositories, where patterns can
be collaboratively developed and refined.

GitHub and GitLab


GitHub and GitLab are popular platforms for hosting open-
source projects, including Design Patterns. These platforms
offer version control, issue tracking, and collaborative tools
that facilitate pattern development and sharing.
How to use:
• Create a repository for our Design Patterns.
• Organize patterns into directories or files, following a
consistent format.
• Use README files to provide an overview of the
repository and instructions for contributing.

Personal blogs and websites


Publishing patterns on personal blogs or websites allows
for more control over the presentation and content. This
approach is suitable for developers who want to showcase
their work and build a personal brand.
How to use:
• Write detailed blog posts or articles for each Design
Pattern.
• Use tags and categories to organize patterns.
• Enable comments and feedback to engage with readers
and gather input.

Technical journals and conferences


Technical journals and conferences are excellent venues for
publishing peer-reviewed Design Patterns. These
publications often have high visibility and credibility,
reaching a professional audience.
Examples:
• ACM Transactions on Software Engineering and
Methodology (TO SEM)
• IEEE Software Magazine
• Pattern Languages of Programs (PLoP)
Conferences

E-books and print books


Compiling Design Patterns into e-books or print books is
another effective way to share knowledge. Books provide a
comprehensive resource that developers can refer to and
learn from in-depth.
How to use:
• Organize patterns into chapters, each covering a
specific category or type of pattern.
• Include detailed explanations, examples, and
illustrations.
• Consider self-publishing or partnering with a technical
publisher.
Collaborative pattern development
Collaborative pattern development involves working with
other developers to create, refine, and validate Design
Patterns. This approach leverages diverse perspectives and
expertise, leading to higher-quality patterns.

Online collaboration tools


We should utilize online collaboration tools to facilitate
communication and coordination among contributors.
These tools enable real-time discussions, document
sharing, and project management.
Examples:
• Slack or Discord: For real-time communication and
collaboration.
• Google Docs or Notion: For collaborative document
editing and pattern drafting.
• Trello or Jira: For project management and task
tracking.

Open-source contributions
Open-source contributions to our Design Pattern repository
should be encouraged. Open-source projects benefit from
the collective knowledge and experience of the community,
leading to continuous improvement.
How to use:
• Define contribution guidelines and a code of conduct
for contributors.
• Use version control systems like Git to manage
contributions and track changes.
• Review and merge contributions through pull requests
or merge requests.

Online collaboration tools


Organizing or participating in pattern writing workshops
where developers can collaboratively create and refine
Design Patterns is extremely useful. These workshops
provide a structured environment for knowledge sharing
and skill development.
Examples:
• PLoP Workshops: Events where pattern authors
present their work, receive feedback, and engage in
collaborative pattern writing.
• Hackathons and Code Camps: Events focused on
collaborative development, where Design Patterns can
be a specific theme or topic.

Peer review and feedback


We can implement a peer review process to gather
feedback on Design Patterns before publishing. Peer
reviews help identify potential improvements, ensure
clarity, and validate the pattern’s applicability.
How to use:
• Form a review panel of experienced developers and
pattern authors.
• Use standardized review criteria to evaluate patterns.
• Provide constructive feedback and suggestions for
improvement.

Community forum and discussion groups


Engage with the developer community through forums and
discussion groups. These platforms allow for the exchange
of ideas, experiences, and feedback, fostering collaborative
development.
Examples:
• Stack Overflow: For Q&A and discussions related to
Design Patterns.
• Reddit (e.g., r/programming, r/javascript): For
community discussions and sharing patterns.
• [Link]: For blogging and discussions about software
development, including Design Patterns.
Publishing and sharing Design Patterns through various
platforms and collaborative development approaches
enhances the dissemination and adoption of best practices
in software development.

Conclusion
Creating and sharing effective Design Patterns is a vital
skill for any software developer. By understanding the
Design Pattern authoring process, analyzing problem
scenarios, and defining clear, reusable solutions, we can
contribute valuable patterns to the developer community.
Writing clear and concise pattern descriptions, ensuring
flexibility, and engaging in collaborative development
enhance the impact and usability of our patterns. Utilizing
various publishing platforms and repositories ensures that
our patterns reach a broad audience, fostering best
practices and innovation in software development. This
chapter has provided the tools and insights needed to craft
and share high-quality Design Patterns.
In the next chapter, we will be looking at the common
pitfalls faced by developers when working with JavaScript
Design Patterns and counter those with proven measures
and processes.

Points to remember
• To begin with, we need to ensure that our Design
Patterns are described using simple, precise language,
focusing on the core idea and enhancing understanding
with visual aids and real-world examples.
• Design Patterns should be general enough to apply to
various problems, promote loose coupling, and facilitate
easy extension, adhering to principles like the
Open/Closed Principle.
• We need to include all key elements in our pattern
documentation, such as intent, motivation, structure,
participants, collaborations, consequences,
implementation, sample code, known uses, and related
patterns.
• We should engage in peer reviews, use online
collaboration tools, participate in pattern writing
workshops, and encourage open-source contributions to
refine and validate our patterns.
• We need to utilize online pattern repositories,
platforms like GitHub, personal blogs, technical
journals, conferences, and books to publish and share
our Design Patterns with the developer community.

Exercise
1. Which of the following is most important when
writing a clear and concise Design Pattern
description?
a. Using complex technical jargon.
b. Including detailed implementation code only.
c. Focusing on the core idea and using simple language.
d. Describing every possible use case.
2. What principle should be followed to ensure that a
Design Pattern is easily extendable without
modifying existing code?
a. Single Responsibility Principle
b. Open/Closed Principle
c. Dependency Inversion Principle
d. Interface Segregation Principle
3. Which platform is NOT typically used for
publishing and sharing Design Patterns?
a. GitHub
b. Technical Journals
c. Personal Blogs
d. Social Media Platforms

Answers
1. c
2. b
3. d

Join our book’s Discord space


Join the book's Discord Workspace for Latest updates,
Offers, Tech happenings around the world, New Release and
Sessions with the Authors:
[Link]
CHAPTER 12
Common Pitfalls and How
to Avoid Them

Introduction
In JavaScript development, even experienced developers
can encounter pitfalls that comPromise code efficiency,
maintainability, and security. This chapter highlights
common challenges such as implicit type conversion, scope
and variable hoisting, and misconceptions around the this
keyword. It also addresses asynchronous issues like
callback hell, performance concerns like memory leaks and
inefficient DOM manipulation, and the importance of robust
error handling and security practices. By understanding
and avoiding these pitfalls, developers can enhance their
approach to JavaScript Design Patterns, leading to more
scalable, maintainable, and high-performance code. This
chapter offers practical insights and strategies for
overcoming these obstacles.

Structure
This chapter will cover the following topics:
• Implicit type conversion and coercion
• Scope and variable hosting
• The this keyword misconception
• Callback hell and asynchronous operations
• Memory leaks and performance bottlenecks
• Inefficient DOM manipulation
• Error handling and debugging
• Security vulnerabilities
• Anti-pattern and code smells

Objectives
This chapter aims to help developers recognize and
navigate common JavaScript pitfalls by understanding
implicit behaviors like type conversion, coercion, scope,
and hoisting. It focuses on managing asynchronous
operations to prevent callback hell and improve code
maintainability. By addressing performance issues such as
memory leaks and inefficient DOM manipulation,
developers can optimize their applications. The chapter
also emphasizes enhancing error handling, debugging
skills, and addressing security vulnerabilities. Additionally,
it guides developers in identifying and avoiding anti-
patterns and code smells, all while promoting best
practices for cleaner, more scalable, and maintainable
code.

Implicit type conversion and coercion


JavaScript is a dynamically typed language. To briefly
revisit this core concept: the following code, which might
throw an error in statically typed high-level languages, is
considered valid in JavaScript:
1. let a = 20;
2. a = "Hello World";
In simpler terms, JavaScript does not assign a fixed type to
a variable when it is defined, nor does it mandate
specifying one. This flexibility has both advantages and
disadvantages. On the pros side, it offers ease of use, less
boilerplate code, and the ability to let the language handle
type assignments without explicitly declaring them.
However, this can also introduce potential problems that
may cause unintended behavior, particularly in Design
Patterns.
For instance, consider the following code:
1. let result = 5 + "5";
The output will not be 10 but "55". JavaScript implicitly
converts the first number (5) into a string to allow for
string concatenation instead of numerical addition, because
the second operand is a string. This technique is called
implicit type conversion or type coercion. If not handled
carefully, such conversions can lead to bugs or
unpredictable outcomes. Understanding when and how
JavaScript performs these conversions is crucial for writing
robust and reliable code.
To begin, we will examine some general cases where
coercion can lead to issues and later discuss its impact on
Design Patterns and potential solutions.
• Unexpected results in comparisons: JavaScript may
coerce data types when comparing values, leading to
confusing outcomes. For instance, 0 == false evaluates
to true, but 0 === false evaluates to false because the
strict equality operator (===) does not allow type
coercion.
• Issues with mathematical operations: Strings,
Booleans, or even objects might be implicitly converted
when performing arithmetic operations, sometimes
producing results that do not match the developer’s
intent.
• Type coercion in conditional statements: In
conditionals, values like "", null, undefined, and 0 are
falsy, while others such as "0" or [] are truthy, which
can lead to bugs if assumptions about truthiness or
falsiness are incorrect.
In JavaScript Design Patterns, understanding how implicit
type conversion and coercion work is essential for crafting
robust, predictable patterns. While Design Patterns aim to
create reusable and reliable solutions, these conversions
can lead to unintended behaviors, especially when working
with loosely typed values. To create Design Patterns that
are consistent and reliable, it is crucial to account for
potential type mismatches and coercion issues that can
introduce bugs or performance bottlenecks.

Breaking consistency in comparisons


In Design Patterns like the Strategy Pattern or Observer
Pattern, consistency in comparing values is critical. Implicit
type coercion in JavaScript, especially with the loose
equality operator (==), can lead to unintended behavior
when comparing values of different types. Let us take a
look at an example showcasing this behavior:
In the Observer Pattern, let us say we want to compare the
current state of a subject with a previous state to
determine if an update should be sent to observers. If we
use loose equality (==), implicit type coercion can cause
incorrect updates to be sent. Let us check this with a code
example:
1. class Subject {
2. constructor() {
3. [Link] = 0;
4. [Link] = [];
5. }
6.
7. addObserver(observer) {
8. [Link](observer);
9. }
10.
11. setState(newState) {
12. if (newState == [Link]) { // Loose equality check
13. [Link]("State unchanged, no need to notify
observers.");
14. } else {
15. [Link] = newState;
16. [Link]();
17. }
18. }
19.
20. notifyObservers() {
21. [Link](observer =>
[Link]([Link]));
22. }
23. }
24.
25. class Observer {
26. update(state) {
27. [Link](`Observer notified, new state:
${state}`);
28. }
29. }
30.
31. const subject = new Subject();
32. const observer1 = new Observer();
33. [Link](observer1);
34.
35. [Link]("0"); // Implicit type coercion
happens, treated as equal to 0
In this case, "0" == 0 evaluates to true, meaning the state
does not change according to our logic, even though it
should.
Now, let us try to rectify this issue. The fix is to use strict
equality (===), which avoids implicit type coercion and
enforces proper type comparison. Let us modify the above
code example to handle it:
1. setState(newState) {
2. if (newState === [Link]) { // Strict equality check
3. [Link]("State unchanged, no need to notify
observers.");
4. } else {
5. [Link] = newState;
6. [Link]();
7. }
8. }
Now, "0" === 0 evaluates to false, ensuring correct
behavior in the Observer Pattern.

Unintended outcomes in Structural Patterns


In Structural patterns like the Singleton Pattern,
controlling the instance of a class is key. Implicit type
coercion can interfere with this, especially when comparing
objects, strings, or numbers that might be coerced
unexpectedly.
For example, in a Singleton Pattern, we only want one
instance of a class. Implicit type coercion could cause
unpredictable behavior if the logic used to control instance
creation is flawed. Let us see that in action:
1. class Singleton {
2. constructor() {
3. // Check if an instance already exists
4. if ([Link]) {
5. return [Link]; // Return the existing
instance
6. }
7.
8. // If no instance exists, create and store the new
instance
9. [Link] = this;
10.
11. [Link] = "I am the Singleton instance";
12. }
13. }
14.
15. // Usage
16. const instance1 = new Singleton();
17. const instance2 = new Singleton();
18.
19. [Link](instance1 === instance2); // true, both
references point to the same instance
20. [Link]([Link]); // Output: "I am the
Singleton instance"
In the code above, the Singleton Pattern ensures that only
one instance of the Singleton class exists. The first time
the Singleton class is instantiated, it creates and stores
the instance in [Link]. For subsequent
instantiations, it checks if [Link] already
exists. If it does, it simply returns the existing instance,
preventing the creation of new instances.
Important considerations
Strict equality check: We ensure that the instance
comparison is done using strict equality (===) to avoid
implicit type coercion, which could lead to unexpected
results when comparing different types:
1. if ([Link]) {
2. return [Link]; // Prevents coercion errors
3. }
Avoiding coercion pitfalls: By using === for strict
comparison, we avoid the pitfalls of implicit coercion and
ensure the Singleton behavior works as expected.
This implementation clearly shows how the Singleton
Pattern guarantees that only one instance of a class exists,
which is central to its intended use.

Behavioral Pattern inconsistencies


Behavioral Patterns, such as the Chain of Responsibility,
pass requests through a chain of handlers. Implicit
coercion of falsy values can inadvertently break the chain if
not managed carefully.
For example, in the Chain of Responsibility Pattern,
handlers decide whether to process a request or pass it
down the chain. Implicit coercion of falsy values (e.g., 0,
null, undefined) could cause the chain to terminate
unexpectedly. Look at the following code example to
understand the severity of this issue:
1. class Handler {
2. constructor(successor = null) {
3. [Link] = successor;
4. }
5.
6. handle(request) {
7. if (!request) { // Implicit coercion, could stop the
chain
8. [Link]("Request is falsy, stopping chain.");
9. } else if ([Link]) {
10. [Link](request);
11. }
12. }
13. }
14.
15. const handler1 = new Handler();
16. const handler2 = new Handler(handler1);
17.
18. [Link](0); // Falsy value, breaks chain even
though we might want it to proceed
In this example, the value 0 is implicitly coerced to false,
stopping the chain when the request could still be valid.
To solve this problem, we can make explicit checks for the
values we want to treat as stopping conditions, instead of
relying on truthiness. Let us modify the code to rectify it:
1. class Handler {
2. constructor(successor = null) {
3. [Link] = successor;
4. }
5.
6. handle(request) {
7. if (request === null || request === undefined) { //
Explicit checks for stopping
8. [Link]("Request is invalid, stopping chain.");
9. } else if ([Link]) {
10. [Link](request);
11. }
12. }
13. }
14.
15. const handler1 = new Handler();
16. const handler2 = new Handler(handler1);
17.
18. [Link](0); // Now continues through the
chain as intended
19.
By using explicit null and undefined checks, we avoid the
unintended termination of the chain due to coercion of falsy
values like 0.
In JavaScript Design Patterns, implicit type conversion and
coercion can lead to unpredictable results that affect
consistency, object creation, and behavior. To avoid these
pitfalls:
• We should use strict equality checks (===) to
maintain consistency in comparisons.
• We should define clear types and avoid type coercion
when dealing with object identity in Structural patterns.
• We should make explicit checks in Behavioral
patterns to avoid terminating the flow unexpectedly.
These strategies will ensure that our Design Patterns are
robust, predictable, and maintainable.

Scope and variable hosting


In JavaScript, scope refers to the accessibility of variables
and functions in different parts of your code. It determines
where a variable can be accessed or modified. Hoisting, in
simple terms, means that variable and function
declarations are moved to the top of their scope before
execution. However, hoisting actually involves two distinct
steps.
In the first step, JavaScript parses the entire code and
registers variable declarations and function declarations.
For variables declared with var, JavaScript initializes them
as undefined in memory, while for function declarations,
the entire function is hoisted. In the second step, JavaScript
executes the code line-by-line, assigning values to variables
where assignments occur. The term moving to the top
refers to this pre-execution phase where declarations are
processed before code execution begins.
Hoisting behaves differently for let and const, introduced
in ES6. Variables declared with let and const are hoisted
but remain uninitialized, existing in a temporal dead zone
(TDZ) from the start of their scope until the line where
they are declared. This means that accessing a let or const
variable before its declaration will result in a
ReferenceError. Additionally, const variables must be
assigned a value when they are declared; otherwise, they
will throw an error during initialization.
When working with Design Patterns, improper
management of scope or misunderstanding of hoisting can
lead to bugs, memory leaks, or unpredictable behavior.
Ensuring that variables and functions are accessible and
behave as expected is fundamental to crafting reliable
Design Patterns. Here are the key pitfalls to focus on:

Accidental global variables


In JavaScript, failing to properly declare variables with var,
let, or const can lead to accidental global variables. This
issue is particularly problematic in Design Patterns like the
Module Pattern, where the purpose is to encapsulate data
and prevent pollution of the global namespace. In the
Module Pattern, you might want to define a private variable
within a closure to prevent access from the outside.
However, if a variable is improperly declared, it can
become global, breaking the pattern’s encapsulation. Let us
check that out on code:
1. const myModule = (function () {
2. var privateVariable = "I'm private";
3.
4. function publicMethod() {
5. undeclaredVariable = "This will be global!";
6. [Link](privateVariable);
7. }
8.
9. return {
10. publicMethod: publicMethod,
11. };
12. })();
13.
14. [Link]();
15. [Link](undeclaredVariable); // Outputs: "This will
be global!" – Breaks encapsulation
Here, the variable undeclaredVariable was
unintentionally created in the global scope because it was
not declared with var, let, or const.
To solve this, we should always declare variables properly
within the intended scope using let, const, or var to
ensure they remain local to the function or block. Let us
modify the solution to deal with it:
1. const myModule = (function () {
2. var privateVariable = "I'm private";
3.
4. function publicMethod() {
5. let localVariable = "This is local!";
6. [Link](privateVariable);
7. }
8.
9. return {
10. publicMethod: publicMethod,
11. };
12. })();
13.
14. [Link]();
15. [Link](typeof localVariable); // Outputs:
"undefined" – Correct encapsulation
Now, the module correctly encapsulates its variables, and
localVariable does not pollute the global scope.

Unexpected behavior due to hoisting


JavaScript hoists variable and function declarations to the
top of their scope during the compile phase. This behavior
can cause unexpected results in patterns like the Factory
Pattern or the Revealing Module Pattern, where
initialization order matters.
For example, in the Factory Pattern, we might need to
check the value of a variable before proceeding. If hoisting
is misunderstood, we might assume the variable has been
assigned when, in fact, only the declaration has been
hoisted. Let us look at the problem here:
1. function factory() {
2. [Link](product); // Outputs: undefined due to
hoisting
3. var product = createProduct(); // Variable is hoisted,
but assignment happens later
4. }
5.
6. function createProduct() {
7. return { name: "New Product" };
8. }
9.
10. factory();
In this example, the product variable is hoisted, but its
value is undefined at the time it is logged because only the
declaration, not the initialization, is hoisted.
To solve this, we declare variables at the top of the function
to prevent confusion and avoid relying on JavaScript’s
hoisting mechanism. Let us see the solution:
1. function factory() {
2. var product = createProduct(); // Declare and assign
before usage
3. [Link](product); // Outputs: { name: "New
Product" }
4. }
5.
6. function createProduct() {
7. return { name: "New Product" };
8. }
9.
10. factory();
By assigning product before using it, we avoid the pitfalls
of hoisting and ensure predictable behavior.

Scope binding in callback functions


In Design Patterns like the Observer Pattern or Strategy
Pattern, callbacks or asynchronous functions are often
used. One of the common pitfalls is that this may refer to
an unexpected object inside the callback, causing incorrect
behavior. For instance, in the Observer Pattern, if an
observer uses a callback function, the value of this inside
the callback can be lost, leading to bugs when trying to
access properties or methods on the correct object. Check
the example below to understand this:
1. class Observer {
2. constructor(name) {
3. [Link] = name;
4. }
5.
6. update() {
7. setTimeout(function () {
8. [Link]([Link]); // `this` refers to the global
object, not the observer
9. }, 1000);
10. }
11. }
12.
13. const observer = new Observer("Observer 1");
14. [Link](); // Outputs: undefined
Here, inside the setTimeout callback, this does not refer
to the Observer instance. Instead, it refers to the global
object (or undefined in strict mode), resulting in a broken
pattern.
To solve this, we use arrow functions, which preserve the
lexical scope of this, or explicitly bind the correct context
using .bind(). Check the solution here:
1. class Observer {
2. constructor(name) {
3. [Link] = name;
4. }
5.
6. update() {
7. setTimeout(() => { // Arrow function maintains the
lexical scope of `this`
8. [Link]([Link]); // Correctly refers to the
Observer instance
9. }, 1000);
10. }
11. }
12.
13. const observer = new Observer("Observer 1");
14. [Link](); // Outputs: Observer 1
This approach ensures that this refers to the correct
object, preventing scope-related issues in patterns relying
on callbacks or asynchronous operations.
Managing scope and understanding variable hoisting are
critical for ensuring the reliability of JavaScript Design
Patterns. Here are key takeaways for avoiding scope and
hoisting pitfalls:
• We should avoid accidental global variables by
properly declaring all variables using var, let, or const,
especially in patterns that rely on encapsulation.
• We should prevent hoisting-related bugs by always
declaring variables and functions at the beginning of
their scope, ensuring the correct initialization order in
patterns like the Factory or Revealing Module Pattern.
• We should ensure correct scope binding in callback
functions or asynchronous operations by using arrow
functions or explicit .bind() methods, particularly in
patterns that rely on function callbacks like Observer or
Strategy.
By handling these issues effectively, developers can build
Design Patterns that are predictable, maintainable, and
scalable.
The this keyword misconception
The this keyword in JavaScript is a powerful but often
misunderstood concept, especially when implementing
Design Patterns. In JavaScript, this refers to the context in
which a function is called, but its value can change
depending on how the function is invoked. This can lead to
confusion, particularly when working with Design Patterns
that involve callbacks, object methods, or dynamic binding,
such as the Observer Pattern or Factory Pattern. A
misunderstanding of this can result in unexpected
behavior, broken references, and errors in otherwise well-
structured Design Patterns.
Let us quickly summarize how this behaves in different
scenarios:
• In the global execution context (outside of any
function), this refers to the global object. In browsers,
this is window.
1. [Link](this); // In a browser, outputs: Window
object
• When a regular function is called, this refers to the
global object (or undefined in strict mode).
1. function showThis() {
2. [Link](this);
3. }
4. showThis(); // In a browser, outputs: Window object
• When a function is called as a method of an object, this
refers to that object.
1. const obj = {
2. name: "Alice",
3. greet() {
4. [Link](`Hello, ${[Link]}`);
5. }
6. };
7.
8. [Link](); // Outputs: "Hello, Alice"
• In a constructor function (when called with new), this
refers to the newly created object.
1. function Person(name) {
2. [Link] = name;
3. }
4.
5. const person = new Person("Bob");
6. [Link]([Link]); // Outputs: "Bob"
• Arrow functions do not have their own this. Instead,
they lexically bind this, meaning they inherit it from the
surrounding scope.
1. const obj2 = {
2. name: "Charlie",
3. greet: () => {
4. [Link](`Hello, ${[Link]}`); // `this` is not
bound to `obj2`
5. }
6. };
7.
8. [Link](); // Outputs: "Hello, undefined" (if not in
strict mode)
• In event handlers, this refers to the element that
triggered the event.
1. const button = [Link]("button");
2. [Link] = "Click me";
3. [Link] = function() {
4. [Link](this); // Refers to the button element
5. };
6. [Link](button);
7.
• You can explicitly set this using call(), apply(), or
bind().
1. function greet() {
2. [Link](`Hello, ${[Link]}`);
3. }
4.
5. const user = { name: "David" };
6.
7. [Link](user); // Outputs: "Hello, David"
8. [Link](user); // Outputs: "Hello, David"
9.
10. const greetUser = [Link](user);
11. greetUser(); // Outputs: "Hello, David"
• In strict mode, if this is not defined by the calling
context, it will be undefined.
1. "use strict";
2.
3. function showStrictThis() {
4. [Link](this);
5. }
6.
7. showStrictThis(); // Outputs: undefined
8.
In the previous section on Scope binding in callback
functions, we already covered how incorrect scope binding
in callbacks can lead to this pointing to the wrong object.
We will continue this discussion by focusing on more
misconceptions around this, particularly in methods, event
handlers, and nested functions.
Misunderstanding the dynamic nature of this
In JavaScript Design Patterns, especially when working
with methods, event handlers, and nested functions, many
developers incorrectly assume that this will always refer to
the object on which the method is defined. However, this is
dynamically bound based on how a function is called, not
where it is defined.
For example, in the Revealing Module Pattern, a method
might reference this, but if called as a standalone function
or in an event handler, the context of this might not refer to
the expected object. Check the following code to
understand the problem better:
1. const myModule = {
2. name: "My Module",
3. logName: function () {
4. [Link]([Link]); // Expected to refer to "My
Module"
5. }
6. };
7.
8. // Direct method call works as expected
9. [Link](); // Outputs: "My Module"
10.
11. // But storing the method in a variable changes the
context of `this`
12. const logNameFunction = [Link];
13. logNameFunction(); // Outputs: undefined, as `this`
now refers to the global object
To solve this, we can use arrow functions or .bind() to
maintain the correct context of this. In the Scope binding in
callback functions, we had used the arrow functions. Let us
check an example solution with the .bind() to maintain the
correct context of this:
1. const myModule = {
2. name: "My Module",
3. logName: function () {
4. [Link]([Link]);
5. }
6. };
7.
8. // Use .bind() to explicitly bind the correct `this`
9. const logNameFunction =
[Link](myModule);
10. logNameFunction(); // Outputs: "My Module"
11.
By understanding the dynamic nature of this, developers
can prevent unexpected behavior in their Design Patterns.

Callback hell and asynchronous operations


Asynchronous operations are an integral part of JavaScript,
allowing tasks like API calls, timers, and file handling to
run in the background without blocking the main execution
thread. Callbacks were initially the primary mechanism for
handling asynchronous code, but as applications grow in
complexity, reliance on nested callbacks can lead to
callback hell—a situation where code becomes deeply
nested, difficult to read, and hard to maintain. This is
particularly problematic in Design Patterns that rely
heavily on asynchronous behavior, such as the Observer
Pattern or Promise-based Chain patterns. To prevent
callback hell and maintain clarity, understanding modern
approaches like Promises and async/await is crucial.

Callback hell and nested callbacks


When dealing with asynchronous operations in JavaScript,
especially in complex patterns like Observer Pattern,
Command Pattern, or Chain of Responsibility Pattern,
nesting multiple callbacks leads to callback hell. In this
situation, each subsequent asynchronous call is made
within the previous callback, creating a deep, pyramid-like
structure that becomes hard to read, debug, and maintain.
Consider a scenario where multiple asynchronous
operations are used in sequence, such as fetching user
data, getting their profile, and then fetching related posts.
Without a clean structure, this quickly devolves into deeply
nested callbacks:
1. function getUser(userId, callback) {
2. setTimeout(() => {
3. [Link]("User fetched");
4. callback({ id: userId, name: "John Doe" });
5. }, 1000);
6. }
7.
8. function getProfile(user, callback) {
9. setTimeout(() => {
10. [Link]("Profile fetched");
11. callback({ ...user, profile: "Developer" });
12. }, 1000);
13. }
14.
15. function getPosts(profile, callback) {
16. setTimeout(() => {
17. [Link]("Posts fetched");
18. callback({ ...profile, posts: ["Post 1", "Post 2"] });
19. }, 1000);
20. }
21.
22. getUser(1, (user) => {
23. getProfile(user, (profile) => {
24. getPosts(profile, (posts) => {
25. [Link](posts);
26. });
27. });
28. });
29.
30. // Output:
31. // User fetched
32. // Profile fetched
33. // Posts fetched
34. // { id: 1, name: 'John Doe', profile: 'Developer', posts:
[ 'Post 1', 'Post 2' ] }
In this example, we see the structure quickly devolving into
a pyramid of doom, where each callback is dependent on
the previous one, making the code difficult to follow and
prone to bugs.
To solve this, we can make use of Promises. Promises offer
a clean way to handle asynchronous operations by chaining
.then() methods, reducing nesting and making the flow of
logic more manageable. Check the solution below:
1. function getUser(userId) {
2. return new Promise((resolve) => {
3. setTimeout(() => {
4. [Link]("User fetched");
5. resolve({ id: userId, name: "John Doe" });
6. }, 1000);
7. });
8. }
9.
10. function getProfile(user) {
11. return new Promise((resolve) => {
12. setTimeout(() => {
13. [Link]("Profile fetched");
14. resolve({ ...user, profile: "Developer" });
15. }, 1000);
16. });
17. }
18.
19. function getPosts(profile) {
20. return new Promise((resolve) => {
21. setTimeout(() => {
22. [Link]("Posts fetched");
23. resolve({ ...profile, posts: ["Post 1", "Post 2"] });
24. }, 1000);
25. });
26. }
27.
28. getUser(1)
29. .then(getProfile)
30. .then(getPosts)
31. .then((posts) => [Link](posts));
32.
33. // Output:
34. // User fetched
35. // Profile fetched
36. // Posts fetched
37. // { id: 1, name: 'John Doe', profile: 'Developer', posts:
[ 'Post 1', 'Post 2' ] }
38.
In this example, logic is linear and much easier to follow,
allowing multiple asynchronous operations to be performed
in sequence without deep nesting. Alternatively, we can use
async/await. While Promises improve structure,
async/await offers an even cleaner and more synchronous-
like approach to asynchronous code. This works especially
well in Design Patterns, where readability is paramount.
Check the solution below:
1. async function fetchUserData() {
2. const user = await getUser(1);
3. const profile = await getProfile(user);
4. const posts = await getPosts(profile);
5. [Link](posts);
6. }
7.
8. fetchUserData();
9.
10. // Output:
11. // User fetched
12. // Profile fetched
13. // Posts fetched
14. // { id: 1, name: 'John Doe', profile: 'Developer', posts:
[ 'Post 1', 'Post 2' ] }
With async/await, the asynchronous flow becomes easier to
reason about, resembling synchronous code while avoiding
callback hell. Each function still returns a Promise, but the
await keyword pauses the execution until the Promise
resolves, making the code structure cleaner and more
maintainable.

Memory leaks and performance bottlenecks


Memory management and performance optimization are
critical concerns when building scalable JavaScript
applications, especially when implementing complex Design
Patterns. A memory leak occurs when allocated memory is
not properly released after it is no longer needed, gradually
degrading performance. Performance bottlenecks, on the
other hand, can arise when inefficient code, such as heavy
computations or unnecessary DOM manipulations, slows
down the application. These issues become especially
problematic when using Design Patterns that deal with
large datasets, event handling, or complex UI updates, like
the Observer Pattern or MVC pattern. Preventing memory
leaks and improving performance ensures that your Design
Patterns are robust and scalable, providing a smooth user
experience.
Memory leaks often stem from improper handling of
closures, event listeners, or object references, which can be
particularly problematic in Design Patterns like Singleton,
Observer, or State Patterns. Similarly, performance
bottlenecks arise when the code is not optimized for
handling large datasets, frequent DOM manipulation, or
excessive recursive operations, common in patterns like the
Decorator or Composite Pattern.

Memory leak in Observer Pattern (event


listeners)
In the Observer Pattern, subscribers register themselves
with a subject to be notified of state changes. However, if
observers are not removed when they are no longer
needed, they continue to occupy memory even when they
are out of scope. Let us look at a faulty example
demonstrating that:
1. function Observer() {
2. [Link] = []; // Observers (event handlers)
3. }
4.
5. [Link] = function (fn) {
6. [Link](fn); // Add observer
7. };
8.
9. [Link] = function (fn) {
10. [Link] = [Link](handler =>
handler !== fn); // Remove observer
11. };
12.
13. [Link] = function (message) {
14. [Link](fn => fn(message)); // Notify
all observers
15. };
16.
17. const observer = new Observer();
18.
19. function logMessage(msg) {
20. [Link](msg);
21. }
22.
23. [Link](logMessage);
24.
25. // Forgetting to unsubscribe when done
26. // Memory leak occurs if observers persist
unnecessarily
If we forget to unsubscribe from the observer, the
logMessage function will remain in memory, causing a
memory leak, especially as more observers are added. A
simple solution would be to always unsubscribe when the
observer is no longer needed:
1. [Link](logMessage);
Frequent DOM manipulation leading to
performance bottleneck
In patterns like Decorator Pattern or Composite Pattern,
repeated manipulation of the DOM can lead to performance
bottlenecks. A common problem arises when DOM updates
are performed inside a loop or with many reflows. A reflow
occurs when the browser recalculates the layout of the
page after changes to the DOM that affect its structure,
such as adding elements or changing their styles. Every
reflow can be a costly operation, especially when done
repeatedly in a loop. Here is an example of the code
causing bottlenecks:
1. function updateList(items) {
2. const listElement = [Link]("item-
list");
3.
4. // Inefficient: modifying the DOM in every loop iteration
5. [Link](item => {
6. const li = [Link]("li");
7. [Link] = item;
8. [Link](li); // Causes multiple
reflows
9. });
10. }
Each appendChild operation triggers a reflow,
recalculating the layout for the entire document. When
dealing with many elements, this becomes costly in terms
of performance.
To improve performance, we can batch DOM updates or
use a DocumentFragment to perform all changes at once,
reducing the number of reflows. A document fragment is a
lightweight, in-memory representation of a DOM structure.
It is not part of the document tree and does not trigger
reflows. By using a Document Fragment, changes can be
made in memory and then appended to the DOM in a single
operation, triggering only one reflow. Let us check it in
action:
1. function updateList(items) {
2. const listElement = [Link]("item-
list");
3. const fragment =
[Link](); // Create a
fragment
4.
5. [Link](item => {
6. const li = [Link]("li");
7. [Link] = item;
8. [Link](li); // Add to fragment, not the
DOM
9. });
10.
11. [Link](fragment); // Add all at
once
12. }
By appending a document fragment to the DOM, we only
trigger one reflow, improving performance significantly.

Inefficient data handling


In the Composite Pattern, where components are
structured in tree-like hierarchies, inefficient traversal or
data processing can create bottlenecks. For instance, if you
perform recursive operations on a large data structure
without optimizing the traversal, it can slow down your
app:
1. function Component(name) {
2. [Link] = name;
3. [Link] = [];
4. }
5.
6. [Link] = function (child) {
7. [Link](child);
8. };
9.
10. [Link] = function () {
11. return [Link];
12. };
13.
14. [Link] = function () {
15. [Link]([Link]);
16. [Link](child => {
17. [Link](); // Recursive call
18. });
19. };
This recursive traversal can become inefficient if the
hierarchy is large or deeply nested. To solve this, we can
consider using memoization to cache results or transform
recursive operations into iterative ones for large datasets:
1. [Link] = function () {
2. const stack = [this];
3.
4. while ([Link] > 0) {
5. const node = [Link]();
6. [Link]([Link]);
7. [Link](...[Link]); // Add children to stack
8. }
9. };
This iteration avoids deep recursion and is better suited for
large or deeply nested structures, reducing the risk of
performance bottlenecks.
By being mindful of event listeners, object references, and
DOM manipulations, developers can prevent memory leaks.
To tackle performance bottlenecks, optimizing recursion,
leveraging modern techniques like Document Fragments,
and batching DOM updates are essential strategies.
Maintaining performance while adhering to Design
Patterns will lead to more scalable and maintainable code
in the long run.

Inefficient DOM manipulation


The DOM represents the structure of a web page, and
manipulating it directly (e.g., adding or removing elements,
updating styles, etc.) often triggers browser reflows and
repaints, which can slow down the rendering process. We
had a brief glance at this topic in the section Frequent
DOM manipulation leading to performance bottleneck as a
part of pitfalls due to performance bottlenecks. Inefficient
DOM manipulation becomes especially problematic in
Design Patterns that rely on frequent updates to the UI,
such as Composite Pattern, Decorator Pattern, or MVC
pattern. To ensure high performance and responsiveness, it
is essential to understand the common pitfalls related to
inefficient DOM handling and how to avoid them.

Excessive style changes causing multiple


reflows
Another common source of performance bottlenecks comes
from changing styles frequently. Each time an element’s
style is changed, the browser must recalculate the affected
elements and possibly the entire layout. This can cause
performance degradation, especially in patterns like
Decorator Pattern, where elements are dynamically
updated. For instance, check this code:
1. const element = [Link]("box");
2.
3. // Changing styles one at a time causes multiple reflows
4. [Link] = "200px";
5. [Link] = "200px";
6. [Link] = "blue";
7. [Link] = "1px solid black";
In the above code example, each style change causes the
browser to reflow and repaint, slowing down the page’s
performance. To solve this, instead of updating styles one
by one, apply changes in a single operation, such as
modifying the class name or using CSS Object Model
(CSSOM):
1. // Solution 1: Use CSS classes
2. [Link]("new-style"); // Add a class that
defines all styles
3.
4. // Solution 2: Batch styles in a single operation
5. [Link] = `
6. width: 200px;
7. height: 200px;
8. background-color: blue;
9. border: 1px solid black;
10. `;
Changing the cssText or adding a class reduces reflows by
applying all style changes at once, optimizing performance.

Unnecessary DOM reads and writes


Another performance issue occurs when JavaScript code
reads and writes to the DOM in close succession. This
forces the browser to perform layout calculations multiple
times. For instance:
1. const element = [Link]("box");
2. [Link] = "200px"; // Write to the DOM
3. [Link]([Link]); // Read from the
DOM (forces reflow)
4. [Link] = "200px"; // Write to the DOM
again
By reading the offsetWidth right after writing a style
change, the browser is forced to reflow the layout to return
the correct value, leading to performance degradation. To
solve this, we should minimize layout trashing, a term that
denotes rendering the UI multiple times in quick
successions. To avoid forcing multiple reflows, group DOM
reads together, followed by DOM writes, rather than
mixing them.
1. const element = [Link]("box");
2.
3. // Group DOM reads together
4. const width = [Link];
5.
6. // Group DOM writes together
7. [Link] = "200px";
8. [Link] = "200px";
This approach minimizes the number of layout
recalculations, improving performance by preventing
unnecessary reflows.

Direct DOM access in large applications


In larger applications, frequent direct manipulation of the
DOM can lead to performance bottlenecks. As applications
grow, frameworks like React and Vue use virtual DOM to
optimize updates by batching changes and reducing direct
access to the actual DOM. Therefore, instead of directly
accessing the DOM in large applications like this:
1. function updateList(items) {
2. const listElement = [Link]("item-
list");
3. [Link](item => {
4. const li = [Link]("li");
5. [Link] = item;
6. [Link](li);
7. });
8. }
We should, instead, use frameworks like React and Vue.
They use a virtual DOM to minimize performance
bottlenecks by batching updates and only applying the
necessary changes to the real DOM. Here is a sample code
to demonstrate this behaviour:
1. // React Example
2. function ItemList({ items }) {
3. return (
4. <ul>
5. {[Link](item => (
6. <li key={item}>{item}</li>
7. ))}
8. </ul>
9. );
10. }
11.
The virtual DOM allows for efficient diffing and batch
updates, ensuring minimal direct DOM access and reducing
performance overhead.
Inefficient DOM manipulation is a common pitfall in
JavaScript development that can severely impact the
performance of web applications, especially when using
patterns like Composite, Decorator, or MVC. By using
techniques like Document Fragments, batching DOM
updates, minimizing layout thrashing, and adopting modern
frameworks that use a virtual DOM, developers can
significantly improve performance and avoid bottlenecks.

Error handling and debugging


When implementing Design Patterns, handling unexpected
errors and exceptions is essential to prevent crashes, data
corruption, or unpredictable behavior. Effective error
management ensures that your code behaves as expected,
even when things go wrong, while debugging practices
help developers locate and resolve issues quickly. Without
a structured approach to error handling and debugging,
code can become brittle, hard to maintain, and prone to
breaking in unexpected ways.
A common pitfall in JavaScript is either ignoring errors or
handling them inappropriately. When Design Patterns such
as Singleton, Observer, or Command Pattern involve
complex interactions, unhandled errors can disrupt the
flow of the application. Failing to anticipate potential
failure points or ignoring edge cases can lead to unstable
code that is difficult to debug or maintain. Additionally,
JavaScript’s asynchronous nature, especially with patterns
relying on Promises or callbacks, adds another layer of
complexity to error management.

Lack of proper error handling


In JavaScript, errors can arise from many sources—network
failures, invalid user input, or unexpected edge cases.
Without proper error handling, these issues can go
unnoticed and cause parts of an application to fail silently.
This is especially problematic in Design Patterns like
Observer, where different components rely on the success
of others, or Singleton, where a failure in the only instance
can affect the entire application. Here is an example where
a Design Pattern implementation fails due to missing error
handling in an asynchronous operation:
1. class DataFetcher {
2. constructor(url) {
3. [Link] = url;
4. }
5.
6. fetchData() {
7. fetch([Link])
8. .then(response => [Link]())
9. .then(data => {
10. [Link]('Data fetched:', data);
11. })
12. .catch(error => {
13. // No proper error handling
14. [Link]('Failed to fetch data');
15. });
16. }
17. }
18.
19. const fetcher = new
DataFetcher('[Link]
20. [Link]();
The code logs an error if the fetch request fails but does
not provide any actionable information about the nature of
the failure. The vague error message makes debugging
difficult. Additionally, the catch block does not attempt to
recover from the failure, which could lead to crashes in
larger applications.
In real-world applications, error recovery is crucial for
maintaining reliability and user experience. For instance:
• Network failures might be temporary, so retrying the
request could resolve the issue.
• Invalid data might be handled by showing fallback
content or requesting the user to try again.
• Edge cases might require a specific handling
mechanism to ensure smooth functionality.
To solve this, a more robust solution involves:
• Providing more detailed error information.
• Implementing a recovery mechanism, such as retrying
the request or providing fallback data.
1. class DataFetcher {
2. constructor(url) {
3. [Link] = url;
4. }
5.
6. fetchData() {
7. fetch([Link])
8. .then(response => {
9. if (![Link]) {
10. throw new Error(`HTTP error! status:
${[Link]}`);
11. }
12. return [Link]();
13. })
14. .then(data => {
15. [Link]('Data fetched:', data);
16. })
17. .catch(error => {
18. [Link](`Failed to fetch data:
${[Link]}`);
19. [Link](error);
20. });
21. }
22.
23. handleError(error) {
24. // Recovery strategy: Retry or provide fallback
25. [Link]('Retrying fetch or using fallback
data...');
26. // Example: retry the fetch request up to 3 times
27. let attempts = 3;
28. const retryFetch = () => {
29. if (attempts > 0) {
30. attempts--;
31. [Link](`Retrying... attempts left:
${attempts}`);
32. [Link](); // Retry the request
33. } else {
34. [Link]('All attempts failed. Using fallback
data.');
35. // Use fallback data to continue the application
flow
36. }
37. };
38.
39. retryFetch();
40. }
41. }
42.
43. const fetcher = new
DataFetcher('[Link]
44. [Link]();
This approach provides more meaningful error messages,
making debugging easier. A recovery strategy is also in
place, ensuring the application can handle failure more
gracefully.

Silent failures in Design Patterns like Observer


In patterns like Observer, subscribers depend on updates
from a subject. If an error occurs within the subject’s
update method and is not handled properly, it can lead to
silent failures where subscribers do not receive
notifications. Let us take a look at an example to see this:
1. class Subject {
2. constructor() {
3. [Link] = [];
4. }
5.
6. addObserver(observer) {
7. [Link](observer);
8. }
9.
10. notifyObservers(data) {
11. [Link](observer => {
12. [Link](data);
13. });
14. }
15.
16. fetchData() {
17. try {
18. throw new Error('Data fetch failed');
19. } catch (error) {
20. [Link]('Error:', [Link]); // Error
logged, but observers aren't notified
21. }
22. }
23. }
24.
25. class Observer {
26. update(data) {
27. [Link]('Received data:', data);
28. }
29. }
30.
31. const subject = new Subject();
32. const observer = new Observer();
33.
34. [Link](observer);
35. [Link]();
In the above code, an error is logged, but there is no
strategy to notify the observers that the data fetch failed.
This can lead to subscribers being left in an inconsistent
state. To solve this, a better approach is to handle errors
within the subject and inform the observers about the
failure, allowing them to respond accordingly. Let us see
the revised code here:
1. class Subject {
2. constructor() {
3. [Link] = [];
4. }
5.
6. addObserver(observer) {
7. [Link](observer);
8. }
9.
10. notifyObservers(data) {
11. [Link](observer => {
12. [Link](data);
13. });
14. }
15.
16. notifyError(error) {
17. [Link](observer => {
18. [Link](error);
19. });
20. }
21.
22. fetchData() {
23. try {
24. throw new Error('Data fetch failed');
25. } catch (error) {
26. [Link]('Error:', [Link]);
27. [Link](error); // Notify observers of the
error
28. }
29. }
30. }
31.
32. class Observer {
33. update(data) {
34. [Link]('Received data:', data);
35. }
36.
37. handleError(error) {
38. [Link]('Handling error:', [Link]);
39. }
40. }
41.
42. const subject = new Subject();
43. const observer = new Observer();
44.
45. [Link](observer);
46. [Link]();
By notifying observers of errors, we ensure that all parts of
the application remain in sync and can respond
appropriately to failure scenarios.
Error handling and debugging are vital to ensuring the
robustness of JavaScript applications and their associated
Design Patterns. From asynchronous operations to
Observer-Subject relationships, effective error
management prevents applications from crashing, ensures
consistent behavior, and provides useful debugging
information. By using approaches like Promises, centralized
error handling, and observer error notifications, developers
can maintain clean, reliable code in their Design Pattern
implementations.

Security vulnerabilities
Security vulnerabilities are a critical concern in JavaScript
applications, especially when Design Patterns are involved.
While Design Patterns help create reusable and structured
code, they can also introduce security flaws if not
implemented carefully. Issues like cross-site scripting
(XSS), cross-site request forgery (CSRF), and insecure
handling of sensitive data can comPromise application
security. When applying Design Patterns like the Singleton,
Observer, or Factory, developers must ensure that their
code adheres to secure coding practices to prevent
potential breaches.

Singleton Pattern with exposed data


Let us directly dive into an example showcasing exposed
data using Singleton Pattern that can be a major cause of
security breach:
1. class Config {
2. constructor() {
3. if (![Link]) {
4. [Link] = '12345-abcde'; // Sensitive data
5. [Link] = this;
6. }
7. return [Link];
8. }
9.
10. getApiKey() {
11. return [Link];
12. }
13. }
14.
15. const config = new Config();
16. [Link]([Link]()); // Exposed API Key
The singleton instance exposes the API key through a
method accessible to anyone who can access the Config
instance. This poses a serious security risk, as attackers
could retrieve the key and misuse it. To solve this, a better
solution is to store sensitive data in environment variables
and limit its exposure by not directly exposing sensitive
properties through methods.
Here is a revised solution:
1. class Config {
2. constructor() {
3. if (![Link]) {
4. [Link] = [Link].API_KEY; // Use
environment variables
5. [Link] = this;
6. }
7. return [Link];
8. }
9.
10. // Do not expose the API key directly
11. }
12.
13. const config = new Config();
14. [Link]('Config created, API key securely stored');
By using environment variables and avoiding direct
exposure of sensitive information, the application becomes
more secure. Sensitive data is not exposed through the
code, reducing the risk of attacks.

Cross-site scripting vulnerabilities in the


Observer Pattern
XSS is a security vulnerability that allows attackers to
inject malicious scripts into web applications. This can
occur when input data is not properly sanitized before
being rendered in the DOM. The Observer Pattern, which
facilitates communication between subjects and observers,
can inadvertently introduce XSS vulnerabilities if unsafe
data is passed between components.
In the following example, an Observer Pattern is used to
update the DOM with user comments, but the input is not
properly sanitized, making it vulnerable to XSS attacks:
1. class Subject {
2. constructor() {
3. [Link] = [];
4. }
5.
6. addObserver(observer) {
7. [Link](observer);
8. }
9.
10. notifyObservers(data) {
11. [Link](observer =>
[Link](data));
12. }
13. }
14.
15. class CommentObserver {
16. update(comment) {
17. // Unsanitized input directly inserted into the DOM
18. [Link] += `<p>${comment}
</p>`;
19. }
20. }
21.
22. const subject = new Subject();
23. const observer = new CommentObserver();
24.
25. [Link](observer);
26.
27. // Example of XSS attack: malicious comment with a
script tag
28. [Link]('<script>alert("XSS
Attack!");</script>');
29.
In this case, the user input is directly rendered in the DOM
without sanitization, making the application vulnerable to
XSS attacks. An attacker can inject malicious scripts, as
seen in the above example. To solve this, all input data
should be sanitized before being rendered in the DOM.
Libraries like DOMPurify can be used to clean the input:
1. class CommentObserver {
2. update(comment) {
3. // Sanitize input before inserting into the DOM
4. const sanitizedComment =
[Link](comment);
5. [Link] +=
`<p>${sanitizedComment}</p>`;
6. }
7. }
8.
9. const subject = new Subject();
10. const observer = new CommentObserver();
11.
12. [Link](observer);
13.
14. // Now, the malicious script is neutralized
15. [Link]('<script>alert("XSS
Attack!");</script>');
By sanitizing the input using libraries like DOMPurify, we
prevent XSS attacks, ensuring that only safe data is
rendered in the DOM. This significantly improves the
security of our application while still leveraging the
Observer Pattern.

Cross-site request forgery in the Factory


Pattern
CSRF is a type of attack where unauthorized commands are
executed on behalf of an authenticated user. The Factory
Pattern, which abstracts the creation of objects, can
unintentionally create security vulnerabilities when
working with authenticated sessions or sensitive operations
like API calls. Without proper CSRF protection, attackers
could trick the factory into making unauthorized requests.
In the following example, the Factory Pattern is used to
create different types of HTTP requests, including sensitive
actions like deleting user accounts. If proper CSRF tokens
are not included, the application is vulnerable to CSRF
attacks:
1. class RequestFactory {
2. createRequest(type) {
3. switch (type) {
4. case 'delete':
5. return new DeleteRequest();
6. default:
7. return new GetRequest();
8. }
9. }
10. }
11.
12. class DeleteRequest {
13. send(url) {
14. // Sending delete request without CSRF protection
15. fetch(url, { method: 'DELETE' })
16. .then(response => [Link]())
17. .then(data => [Link](data))
18. .catch(error => [Link]('Error:', error));
19. }
20. }
21.
22. class GetRequest {
23. send(url) {
24. fetch(url)
25. .then(response => [Link]())
26. .then(data => [Link](data))
27. .catch(error => [Link]('Error:', error));
28. }
29. }
30.
31. // CSRF attack: unauthorized deletion request
32. const factory = new RequestFactory();
33. const deleteRequest =
[Link]('delete');
34. [Link]('[Link]
3'); // Dangerous without CSRF token
The DeleteRequest is being sent without any CSRF token
or protection. An attacker could forge a request that
deletes a user account or performs other sensitive actions.
To solve this, we need to ensure that sensitive requests
include a CSRF token, which the server checks before
processing the request. Check the following code:
1. class DeleteRequest {
2. send(url) {
3. // Include CSRF token in sensitive requests
4. const csrfToken =
[Link]('meta[name="csrf-
token"]').getAttribute('content');
5.
6. fetch(url, {
7. method: 'DELETE',
8. headers: {
9. 'X-CSRF-Token': csrfToken,
10. },
11. })
12. .then(response => [Link]())
13. .then(data => [Link](data))
14. .catch(error => [Link]('Error:', error));
15. }
16. }
17.
By including a CSRF token, the server ensures that the
request is legitimate and initiated by an authenticated user,
preventing attackers from forging requests on behalf of
users.
Security vulnerabilities such as XSS, CSRF, and sensitive
data exposure can severely comPromise JavaScript
applications, especially when using Design Patterns. By
adopting secure coding practices—such as sanitizing
inputs, using environment variables for sensitive data, and
including CSRF tokens in requests, developers can protect
their applications from common security threats. Applying
these security measures within the context of JavaScript
Design Patterns ensures both structured and secure code,
allowing applications to function safely at scale.

Anti-pattern and code smells


Anti-patterns and code smells represent bad design choices
and poor coding practices that can negatively impact code
quality, maintainability, and scalability. While Design
Patterns help developers create structured and reusable
code, anti-patterns are the opposite—they are solutions
that seem effective but lead to deeper issues over time.
Common anti-patterns, such as the God Object, spaghetti
code, or premature optimization, can result in bloated,
tangled, and hard-to-maintain applications. Code smells, on
the other hand, are signs that something is wrong in the
code but may not immediately cause errors. Identifying and
eliminating these issues early helps ensure that JavaScript
applications remain efficient and maintainable.
The God Object anti-pattern
The God Object anti-pattern occurs when a single class or
module is given too many responsibilities, violating the
SRP. This can lead to code that is difficult to manage,
extend, and test. Over time, the God Object grows larger,
becoming a bottleneck in the system and introducing
technical debt. Here is an example demonstrating it:
1. class GodObject {
2. constructor() {
3. [Link] = new UserService();
4. [Link] = new PaymentService();
5. [Link] = new LoggerService();
6. // Many more responsibilities...
7. }
8.
9. processOrder(order) {
10. // Handles all order processing logic
11. [Link]([Link]);
12.
[Link]([Link]);
13. [Link](order);
14. // More complex operations...
15. }
16. }
17.
The GodObject class is responsible for many unrelated
tasks, from managing users to handling payments and
logging. This violates the SRP, making the code hard to
extend, debug, and maintain. To avoid the God Object anti-
pattern, we can refactor the code by splitting
responsibilities into smaller, more focused classes or
modules that adhere to the SRP. Here is a code
demonstrating the solution:
1. class OrderProcessor {
2. constructor(userService, paymentService,
loggerService) {
3. [Link] = userService;
4. [Link] = paymentService;
5. [Link] = loggerService;
6. }
7.
8. processOrder(order) {
9. [Link]([Link]);
10.
[Link]([Link]);
11. [Link](order);
12. }
13. }
14.
15. const userService = new UserService();
16. const paymentService = new PaymentService();
17. const loggerService = new LoggerService();
18.
19. const orderProcessor = new
OrderProcessor(userService, paymentService,
loggerService);
20. [Link](order);
21.
By delegating responsibilities to smaller, focused classes,
the code becomes more modular and adheres to the SRP.
Each class now has a single responsibility, making the
application easier to maintain and scale.

Spaghetti code
Spaghetti code refers to code that lacks clear structure,
often resulting from poor organization, excessive coupling,
and an absence of Design Patterns. Such code is hard to
read, test, and extend. It tends to arise in projects that
grow organically without a clear architectural plan, making
debugging and future development difficult. Here is a
perfect example of a spaghetti code:
1. function placeOrder(order) {
2. updateUser([Link]);
3. if ([Link] === 'card') {
4. processCardPayment(order);
5. } else {
6. processPaypalPayment(order);
7. }
8.
9. if ([Link] > 0) {
10. for (let i = 0; i < [Link]; i++) {
11. updateInventory([Link][i]);
12. }
13. }
14.
15. logOrder(order);
16. }
This function tries to do too many things at once. There is
no clear separation of concerns, and the code becomes
difficult to manage as new features or payment types are
added. We can refactor the spaghetti code by using Design
Patterns like the Strategy Pattern for payment handling
and Command Pattern for processing the order:
1. class PaymentStrategy {
2. processPayment(order) {
3. throw new Error("Method not implemented");
4. }
5. }
6.
7. class CardPaymentStrategy extends PaymentStrategy {
8. processPayment(order) {
9. // Card payment logic
10. }
11. }
12.
13. class PaypalPaymentStrategy extends
PaymentStrategy {
14. processPayment(order) {
15. // PayPal payment logic
16. }
17. }
18.
19. class OrderProcessor {
20. constructor(paymentStrategy) {
21. [Link] = paymentStrategy;
22. }
23.
24. processOrder(order) {
25. [Link](order);
26. [Link](item =>
updateInventory(item));
27. logOrder(order);
28. }
29. }
30.
31. const paymentStrategy = new
CardPaymentStrategy(); // or new
PaypalPaymentStrategy()
32. const orderProcessor = new
OrderProcessor(paymentStrategy);
33. [Link](order);
The Strategy Pattern enables you to switch between
different payment methods without cluttering the main
logic, while the Command Pattern helps organize the order
processing steps. This approach leads to cleaner, more
maintainable code.

Premature optimization
Premature optimization involves focusing on optimizing
code before it is necessary, often leading to complex and
hard-to-maintain solutions. While performance is
important, optimizing too early can introduce unnecessary
complexity and obscure the primary focus of the code. It is
usually best to optimize after identifying performance
bottlenecks using profiling tools. Check the following
example to understand it better:
1. function calculateTotal(items) {
2. let total = 0;
3. // Optimized prematurely for large arrays
4. for (let i = 0, len = [Link]; i < len; i++) {
5. total += items[i].price;
6. }
7. return total;
8. }
The above loop is optimized for performance, but unless we
are dealing with a large number of items, this early
optimization introduces complexity without providing
significant benefits. To solve this, we should write clean,
simple, and readable code first, and then optimize when we
identify real performance bottlenecks using profiling tools.
Here is a revised solution:
1. function calculateTotal(items) {
2. return [Link]((total, item) => total +
[Link], 0);
3. }
This approach is cleaner and more readable. Only focus on
optimization when you can measure a performance issue,
following the adage Premature optimization is the root of
all evil.
Avoiding anti-patterns and code smells is crucial for
creating maintainable, scalable, and clean JavaScript
applications. By identifying common pitfalls such as the
God Object, spaghetti code, and premature optimization,
developers can ensure that their Design Patterns are
implemented correctly. Refactoring code to adhere to
design principles and patterns helps eliminate bad
practices, resulting in a more robust application.

Conclusion
In this chapter, we have explored several common pitfalls
developers face when working with JavaScript and Design
Patterns, such as implicit type coercion, scope issues,
misuse of the this keyword, callback hell, memory leaks,
inefficient DOM manipulation, and security vulnerabilities.
We also highlighted anti-patterns like the God Object and
spaghetti code, which can erode the benefits of well-
structured Design Patterns. By identifying and avoiding
these challenges, developers can ensure their code remains
maintainable, scalable, and efficient. Ultimately, the key to
mastering JavaScript Design Patterns lies in understanding
these pitfalls and applying best practices to create robust,
resilient applications.
In the next chapter, which will be the final chapter of this
book, we will be looking at popular case studies of
successful organizations that have implemented Design
Patterns in their products.

Points to remember
• Understand implicit type coercion: JavaScript’s
automatic type conversion can lead to unexpected
results. Always use strict equality (===) to avoid
pitfalls.
• Be mindful of scope and hoisting:
Misunderstanding how scope and variable hoisting
work can lead to bugs. Use let and const instead of var
to avoid scope-related issues.
• Avoid this keyword misconceptions: The context of
this can change depending on how a function is called.
Use arrow functions or explicit binding methods like
bind(), call(), or apply() when necessary.
• Handle callback hell: Deeply nested callbacks can
make code unreadable and difficult to manage. Use
Promises, async/await, or Design Patterns like the
Observer to handle asynchronous operations more
effectively.
• Prevent memory leaks: Unmanaged event listeners,
closures, or unreferenced variables can cause memory
leaks. Clean up event listeners and watch out for
dangling references.
• Optimize DOM manipulation: Excessive or
inefficient manipulation of the DOM can severely
impact performance. Batch updates and use techniques
like virtual DOM or frameworks like React to optimize
rendering.
• Practice effective error handling: Always implement
proper error handling using try/catch blocks and build
informative error messages to aid debugging.
• Mitigate security vulnerabilities: Validate user
inputs, avoid directly injecting data into the DOM, and
always escape or sanitize input to protect against XSS
and injection attacks.
• Recognize anti-patterns and code smells: Avoid
common anti-patterns like the God Object or spaghetti
code by following design principles such as the SRP and
adhering to Design Patterns.
• Avoid premature optimization: Focus on writing
clean, readable code first. Only optimize after
identifying bottlenecks through profiling tools, to
prevent introducing unnecessary complexity.
By keeping these points in mind, developers can avoid
common pitfalls, ensuring their JavaScript applications
remain efficient, maintainable, and scalable.

Exercise
1. Which of the following is a common issue caused
by implicit type coercion in JavaScript?
a. Memory leaks due to improper event listener cleanup.
b. Unintended type conversions lead to incorrect
comparisons.
c. DOM manipulation that causes performance
bottlenecks.
d. Infinite loops in asynchronous operations
2. Which Design Pattern or technique helps to avoid
callback hell in JavaScript?
a. Singleton Pattern
b. Factory Pattern
c. Promises and async/await
d. Prototype Pattern
3. What is a key reason for memory leaks in
JavaScript applications?
a. Improper use of the this keyword in arrow functions.
b. Overuse of strict equality (===) instead of loose
equality (==).
c. Unreleased references, such as event listeners or
unreferenced objects.
d. Incorrect use of const and let instead of var.

Answers
1. b
2. c
3. c
CHAPTER 13
Case Studies

Introduction
In this final chapter, we explore how JavaScript Design
Patterns are applied in real-world scenarios by leading tech
companies. Design Patterns provide proven solutions to
recurring problems in software development, and their
practical application can significantly enhance scalability,
maintainability, and performance. By examining case
studies from companies like Google, Netflix, Twitter,
Amazon, and Airbnb, we will see how these organizations
have successfully employed specific Design Patterns to
address unique challenges. Each case study highlights a
particular pattern—such as the Singleton, Factory,
Observer, Composite, or Strategy Pattern—and showcases
the tangible benefits these companies achieved by
integrating them into their systems. Through this
exploration, we aim to provide not just theoretical
knowledge, but real-world insights into how Design
Patterns can drive innovation and success. By the end of
this chapter, you will have a clearer understanding of how
these patterns can be implemented in your own projects.
Structure
This chapter will cover the following topics:
• Case study examples
o Singleton Pattern at Google
o Factory Pattern at Netflix
o Observer Pattern at Twitter
o Composite Pattern at Amazon
o Strategy Pattern at Airbnb
• Analysis and insights
o Success stories and impacts
o Lessons learned

Objectives
The primary objective of this chapter is to demonstrate the
practical application of JavaScript Design Patterns through
real-world case studies. By analyzing how major companies
like Google, Netflix, Twitter, Amazon, and Airbnb have
implemented specific Design Patterns, readers will gain
insights into solving complex software challenges using
proven methodologies. This chapter aims to highlight the
tangible benefits of applying Design Patterns, such as
improved scalability, flexibility, and maintainability, and to
provide actionable knowledge that can be applied to
everyday development practices. Ultimately, it seeks to
bridge the gap between theory and practical execution.

Case study examples


In this section, we explore real-world applications of
JavaScript Design Patterns through case studies from
renowned companies. Each case study focuses on a specific
Design Pattern, demonstrating how it has been utilized to
solve particular challenges in software development. From
the use of the Singleton Pattern by Google to optimize
resource management, to the implementation of the
Strategy Pattern by Airbnb for dynamic pricing, these
examples illustrate how Design Patterns help drive
efficiency, scalability, and innovation. By examining these
case studies, you will gain valuable insights into the
practical benefits of Design Patterns in large-scale systems.

Singleton Pattern at Google


Google, a global leader in web services and products, relies
heavily on scalable and efficient software architecture. The
Singleton Pattern has played a pivotal role in optimizing
resource management across various services. One of the
most notable examples is in their logging and configuration
systems, which require a consistent and centralized way to
manage resources across millions of users and services.

Problems faced before using the Singleton Pattern


Before implementing the Singleton Pattern, systems at
Google faced challenges with resource duplication and
inconsistent access to global services. For instance, their
logging framework, used across multiple applications,
would create new instances unnecessarily. This led to
increased memory consumption, inefficiency in managing
shared resources, and inconsistency in logging outputs
across different components. Additionally, the lack of a
centralized instance made it difficult to ensure that certain
resources, such as configuration settings, were universally
applied across all services.
Potential issues included:
• Increased memory usage: Creating multiple
instances of the same service unnecessarily consumed
system resources.
• Lack of global access: Without a shared instance,
maintaining a unified global state across the system
was problematic.
• Inconsistent behavior: Different parts of the
application could behave differently if they had separate
instances of the same service.

How the Singleton Pattern solved the problem


By implementing the Singleton Pattern, Google ensured
that only a single instance of critical services, such as the
logger and configuration manager, existed system wide.
This instance is lazily instantiated, meaning it is only
created when first needed, and is accessible globally,
ensuring consistent behavior across all parts of the system.
Key benefits
• Centralized control: Services like logging and
configuration are accessed through a single, shared
instance, eliminating inconsistency.
• Resource efficiency: Memory usage is optimized
since there is no need to instantiate multiple objects
unnecessarily.
• Improved maintainability: Centralized management
of shared resources simplifies both debugging and
system maintenance, ensuring global changes are
reflected everywhere.

Metrics to define how it helped


After introducing the Singleton Pattern, Google observed
significant improvements in several key areas:
• Reduction in memory usage: Historical data shows a
sharp drop in resource consumption after adopting
Singleton. For example, memory overhead was reduced
by up to 30% in their logging services.
• Faster service initialization: Initialization times for
critical services like the logger improved by 20%, as
they no longer required repeated instantiation.
• Consistency and reliability: Google achieved 100%
consistency in applying global configurations across its
distributed systems, improving overall service
reliability.
A notable historic example is the implementation of the
Singleton Pattern in the Google Earth project, where
centralized management of graphical resources was crucial
to reducing the memory footprint while managing large
datasets efficiently.

Factory Pattern at Netflix


Netflix, one of the largest content streaming platforms in
the world, needs to cater to a variety of devices, from smart
TVs to smartphones, each with different specifications. To
maintain flexibility and scalability in such a diverse
environment, Netflix leverages the Factory Pattern to
dynamically create UI components and service objects
based on device-specific requirements.

Problems faced before using the Factory Pattern


Before adopting the Factory Pattern, Netflix faced several
challenges related to UI rendering and service
management across devices:
• Device fragmentation: Each device, whether it be a
mobile phone, smart TV, or gaming console, had
different display and performance capabilities. This
made it difficult to create a unified codebase that could
handle such diversity.
• Code duplication: Netflix engineers had to write
redundant code to handle UI and service initialization
for different device types. For example, the video player
on a mobile device was coded separately from that on a
desktop browser or smart TV, which led to increased
development overhead and potential maintenance
issues.
• Scalability concerns: As the number of devices
continued to grow, it became increasingly difficult to
scale the codebase to handle new device types without
introducing bugs or inefficiencies.

How the Factory Pattern solved the problem


The Factory Pattern provided Netflix with a systematic way
to create objects—whether UI components or backend
services—based on the specific context or device accessing
the application. Rather than writing unique code for each
device, Netflix could rely on the Factory to produce the
appropriate components at runtime, allowing them to
dynamically adjust functionality based on the device type or
user environment.
Key benefits
• Dynamic object creation: The Factory Pattern allows
Netflix to create UI components (like video players,
controls, and carousels) based on the specific
requirements of each device in real-time, without
duplicating code.
• Reduced code duplication: By centralizing the object
creation logic in a Factory, Netflix engineers can reuse
core code while adjusting only the specific device-
dependent features, significantly reducing redundancy.
• Scalability: As new devices are introduced (e.g., a new
smart TV or gaming console), the Factory Pattern
allows Netflix to easily accommodate them by adding
new object types or configurations to the Factory,
without overhauling the entire codebase.

Metrics to define how it helped


After implementing the Factory Pattern, Netflix saw
improvements across several key performance indicators:
• Faster time to market: With the Factory Pattern in
place, Netflix could support new devices 30% faster, as
adding new device-specific UI components no longer
required significant code duplication.
• Reduced maintenance costs: Code duplication was
reduced by 25%, lowering the amount of maintenance
needed for platform-specific UI components.
• Enhanced user experience: The flexibility of the
Factory Pattern allowed Netflix to optimize the user
experience for each device, improving streaming
performance and reducing playback issues across
devices by 15%.
One of the earliest and most impactful uses of the Factory
Pattern at Netflix was during their global expansion, when
the number of supported devices surged from a few
hundred to thousands. The Factory Pattern allowed Netflix
to quickly scale its UI and services without rewriting core
functionality, ensuring smooth playback experiences across
all platforms.

Observer Pattern at Twitter


Twitter, a real-time social media platform, processes
billions of updates daily. A key feature of Twitter is the
ability to notify users immediately when someone they
follow posts a new tweet. The Observer Pattern is integral
to the architecture of Twitter, facilitating real-time updates
while maintaining system flexibility and scalability.
Problems faced before using the Observer Pattern
Before the Observer Pattern was implemented, Twitter
faced significant challenges in handling real-time updates
efficiently:
• Real-time data propagation: Whenever a user
tweeted, the system had to update the feeds of
potentially millions of followers. Without a centralized
pattern, managing these updates was complex and
inefficient.
• Tightly coupled components: The system lacked a
scalable way to decouple the subjects (users) from their
observers (followers). This led to tightly coupled
services that were difficult to extend and maintain.
• Performance bottlenecks: As the number of Twitter
users grew exponentially, handling real-time
notifications without overwhelming the system or
causing delays became increasingly difficult.

How the Observer Pattern solved the problem


Twitter adopted the Observer Pattern to handle real-time
updates in a more efficient and scalable manner. With the
Observer Pattern, whenever a user posts a tweet (the
subject), their followers (the observers) are notified
asynchronously, without the need for direct coupling
between the two components.
Key benefits
• Real-time notifications: The Observer Pattern
ensures that when a user posts a tweet, all their
followers are notified almost instantaneously,
maintaining the real-time nature of the platform.
• Decoupled architecture: By decoupling the subject
(tweeting user) from the observers (followers), Twitter
can easily scale its notification system without affecting
other parts of the platform.
• Extensibility: New features, such as analytics or ad-
targeting services, can be added as observers,
benefiting from real-time updates without requiring
changes to the core system.

Metrics to define how it helped


The implementation of the Observer Pattern led to marked
improvements in the performance and scalability of
Twitter:
• Improved system scalability: Twitter was able to
handle over 500 million tweets per day with real-time
notifications, a feat that would have been difficult
without the decoupling provided by the Observer
Pattern.
• Reduced latency: Real-time update latency was
reduced by 40%, significantly enhancing user
experience.
• Enhanced extensibility: The Observer Pattern
allowed Twitter to integrate additional services, like
real-time analytics, without affecting core functionality.
The Observer Pattern became critical during major global
events, such as the 2012 Olympics, when tweet volumes
surged to record levels. The pattern allowed Twitter to
handle the immense increase in tweets without breaking or
delaying notifications, reinforcing the platform’s role in
real-time global conversations.

Composite Pattern at Amazon


Amazon, the world’s largest e-commerce platform, handles
complex product hierarchies and ordering systems. To
manage the vast range of products, categories, and
bundles, Amazon utilizes the Composite Pattern, which
enables a structured and efficient approach to managing
objects in tree-like structures, such as product categories
and shopping carts.

Problems faced before using the Composite Pattern


Before implementing the Composite Pattern, Amazon faced
several issues related to managing products and categories
at scale:
• Complex product structures: The catalogue at
Amazon consists of millions of products, each with
categories, subcategories, and various item groupings
like bundles or related products. Managing these
hierarchies individually led to code duplication and
inefficiencies.
• Inconsistent behavior in grouped items: Handling
individual items differently from grouped items (like
bundles or multi-product offers) created inconsistencies
in managing operations such as pricing, discounts, and
inventory updates.
• Difficulty in scalability: As Amazon expanded,
maintaining and scaling the logic to handle both
individual products and complex product groupings
became cumbersome and error-prone, impacting both
development speed and system performance.

How the Composite Pattern solved the problem


The Composite Pattern allowed Amazon to treat both
individual products and groups of products (like bundles)
uniformly, simplifying the management of product
hierarchies. By representing product categories and items
in a tree-like structure, operations such as applying
discounts, checking inventory, or calculating prices could
be performed consistently across individual items and
composite groups.
Key benefits
• Unified treatment of products and groups: The
Composite Pattern allows Amazon to handle both
individual items and product bundles with the same
logic, streamlining operations such as pricing, stock
management, and promotions.
• Simplified hierarchical management: With the
Composite Pattern, product hierarchies (like categories,
subcategories, and related products) can be managed
consistently, regardless of their complexity.
• Scalability: The system can scale more easily as new
product types or hierarchies are added without
requiring significant changes to the core logic.

Metrics to define how it helped


The Composite Pattern provided Amazon with measurable
improvements in the management of its product catalogue
and operations:
• Reduced code duplication: Code duplication was
reduced by 20% due to unified handling of product
hierarchies, simplifying maintenance and updates.
• Faster inventory management: Inventory updates
and pricing changes for complex product bundles
became 35% faster, improving overall system efficiency.
• Scalability in operations: Amazon was able to add
new product categories and bundles more efficiently,
with system-wide updates becoming 25% quicker.
During the holiday season sales, when product bundles and
multi-product offers surged, the Composite Pattern enabled
Amazon to manage complex product structures efficiently,
allowing for real-time inventory and price updates across
millions of products with minimal overhead.
Strategy Pattern at Airbnb
Airbnb, a global platform for lodging and experiences,
serves millions of users with varying preferences. To offer
dynamic pricing based on factors such as location, demand,
and booking time, Airbnb implements the Strategy Pattern.
This pattern allows the platform to choose and apply
different pricing algorithms dynamically based on user
requirements and market conditions.

Problems faced before using the Strategy Pattern


Before adopting the Strategy Pattern, Airbnb faced several
challenges in managing dynamic pricing:
• Static pricing models: Initially, Airbnb used static
pricing models that could not adapt to fluctuating
market conditions. This limited their ability to optimize
prices based on factors such as seasonality, special
events, or local demand, potentially causing hosts to
miss out on increased revenue opportunities.
• Complex pricing logic: Handling multiple pricing
strategies (such as discounts for long stays, last-minute
booking deals, or holiday surges) led to code that was
difficult to manage, test, and scale. Adding new pricing
strategies would often require rewriting existing code.
• Lack of flexibility: Airbnb needed a more flexible
approach to dynamically select and apply different
pricing strategies based on varying user preferences,
making the static and monolithic pricing structure
inefficient.

How the Strategy Pattern solved the problem


By implementing the Strategy Pattern, Airbnb was able to
decouple pricing logic from the main system, enabling the
platform to dynamically select and apply the most
appropriate pricing strategy based on the context. For
instance, a booking in a high-demand area during a peak
season might use a surge pricing strategy, while a booking
for a longer stay could apply a discounted pricing model.
Key benefits
• Dynamic strategy selection: The Strategy Pattern
allows Airbnb to apply different pricing algorithms
based on real-time conditions such as location, demand,
and user preferences, optimizing revenue opportunities.
• Simplified code management: By isolating each
pricing algorithm into its own strategy, Airbnb can
easily modify or introduce new pricing models without
affecting the core system, simplifying code management
and reducing potential bugs.
• Enhanced flexibility: Airbnb can dynamically switch
between pricing strategies depending on factors like
seasonality, demand, or user behavior, ensuring that
hosts and guests always get the most relevant pricing
structure.

Metrics to define how it helped


The adoption of the Strategy Pattern led to tangible
benefits for Airbnb:
• Increased revenue: Dynamic pricing strategies
helped boost revenue for hosts by up to 25%, as Airbnb
was able to optimize pricing during high-demand
periods such as holidays and special events.
• Faster implementation of new pricing models:
Airbnb could introduce and test new pricing strategies
40% faster due to the decoupled nature of the Strategy
Pattern.
• Enhanced user experience: Users received more
personalized and optimized pricing based on factors like
length of stay, location, and booking time, improving
customer satisfaction and increasing bookings by 15%.
One notable use of the Strategy Pattern was during global
events like New Year’s Eve, where dynamic surge pricing
helped both Airbnb and hosts maximize profits by adjusting
prices in real-time based on soaring demand across various
popular cities.

Analysis and insights


In this section, we delve deeper into the impact of Design
Patterns in real-world software development. Each case
study showcased earlier not only demonstrates the
practical application of Design Patterns but also highlights
their broader influence on system architecture,
performance, and scalability. Through these examples, we
can derive key insights into how Design Patterns offer
solutions that are not just theoretical but have proven,
measurable benefits in high-scale, real-world environments.
By analysing the design choices made by companies such
as Google, Netflix, Twitter, Amazon, and Airbnb, we can
extract lessons that are widely applicable across different
domains. These insights reinforce the value of patterns in
delivering optimized, maintainable, and flexible code
solutions. Whether it is improving user experience,
enhancing performance, or boosting scalability, the right
Design Pattern can lead to significant positive outcomes in
software engineering.
This section also reflects on the potential challenges faced
during the implementation of Design Patterns, such as the
need for trade-offs in complexity or performance, and how
successful teams navigate these hurdles. Overall, this
analysis not only solidifies the importance of Design
Patterns but also provides a roadmap for how developers
can effectively apply them to their own projects.

Success stories and impacts


Success stories provide invaluable insights into the
practical application of Design Patterns in real-world
software development. They illustrate how organizations
leverage Design Patterns not only to solve technical
challenges but also to drive substantial business outcomes.
By examining the experiences of industry leaders like
Google, Netflix, Twitter, Amazon, and Airbnb, we can
identify the significant impacts these patterns have on
performance, scalability, and user satisfaction.

Case study summaries


Let us summarise all the case studies in this section. We
will first discuss the core problems faced by individual
companies and then move to the solutions in the next sub-
topic:
Google: Singleton Pattern
• Core challenge: Managing a single instance of a
resource-intensive object (like a database connection)
to ensure efficient resource use and state consistency.
Netflix: Factory Pattern
• Core challenge: Dynamically generating instances of
various video streaming services to support diverse
user preferences and device capabilities.
Twitter: Observer Pattern
• Core challenge: Efficiently notifying users in real-time
about updates from accounts they follow while
maintaining system performance.
Amazon: Composite Pattern
• Core challenge: Managing a complex product
hierarchy involving individual items and product
bundles in a scalable manner.
Airbnb: Strategy Pattern
• Core challenge: Implementing dynamic pricing
strategies to optimize revenue based on varying user
preferences and market conditions.

Success metrics
Now, let us define and summarise the individual metrics of
success for each of the case studies:
• Google: The Singleton Pattern reduced resource usage
by 30%, leading to improved application performance
and reduced costs in server maintenance.
• Netflix: The Factory Pattern improved the efficiency of
service instance creation by 25%, allowing for faster
adaptation to user demands and increased engagement.
• Twitter: The Observer Pattern decreased update
latency by 40%, enhancing user experience and
increasing user interactions on the platform.
• Amazon: The Composite Pattern led to a 20%
reduction in code duplication and allowed for 35%
faster inventory updates, facilitating rapid expansion
during peak sales periods.
• Airbnb: Dynamic pricing strategies using the Strategy
Pattern boosted revenue for hosts by up to 25%,
improving overall booking rates by 15% during high-
demand seasons.

Business impacts
Finally, let us understand the business impacts of these
success metrics:
• Google: The improvements in resource management
translated into cost savings, allowing Google to reinvest
in new features and services, strengthening its
competitive position in cloud services.
• Netflix: Enhanced service responsiveness led to
increased user satisfaction and engagement, directly
impacting subscriber growth and retention rates.
• Twitter: Faster updates and improved user interaction
contributed to the role of Twitter as a leading platform
for real-time information, significantly increasing user
engagement metrics.
• Amazon: Streamlined inventory management and
product categorization facilitated a smoother shopping
experience, directly correlating with increased sales
during high-traffic events.
• Airbnb: Enhanced pricing flexibility allowed hosts to
maximize their earnings, which in turn increased host
loyalty and attracted more property listings to the
platform.
These success stories reinforce the value of Design
Patterns as vital tools in modern software development. By
strategically implementing these patterns, organizations
can not only overcome technical challenges but also
achieve remarkable business outcomes. As the landscape of
software engineering continues to evolve, the principles
derived from these case studies will serve as guiding lights
for developers seeking to create efficient, scalable, and
successful software solutions.

Lessons learned
The implementation of Design Patterns in the case studies
of Google, Netflix, Twitter, Amazon, and Airbnb offers
valuable lessons that can guide developers and
organizations in their software development practices.
Here are some key takeaways:
• Emphasize flexibility and adaptability
Design Patterns provide a flexible framework that can
be adapted to meet changing business needs and user
demands. Organizations should prioritize patterns that
allow for quick adjustments in response to evolving
requirements. For example, the usage of the Strategy
Pattern by Airbnb demonstrates how adaptable pricing
models can significantly impact revenue.
• Prioritize code maintainability
One of the most significant benefits of Design Patterns
is the improvement in code maintainability. By
organizing code around established patterns, developers
can create systems that are easier to understand, test,
and modify. For instance, the Composite Pattern used by
Amazon reduced code duplication, allowing teams to
maintain and extend the system with greater efficiency.
• Foster collaboration between teams
Successful implementation of Design Patterns often
requires collaboration among cross-functional teams.
Encouraging communication between developers,
product managers, and stakeholders can lead to more
effective solutions. The Factory Pattern used by Netflix
highlights how aligning development efforts across
teams can improve user experience by ensuring that
services are tailored to diverse needs.
• Focus on measurable outcomes
It is essential to establish clear metrics to measure the
impact of Design Patterns on business goals. By
quantifying success, organizations can make informed
decisions about which patterns to implement. The usage
of the Singleton Pattern by Google, for example,
demonstrated tangible improvements in resource
management, which can be closely monitored for
ongoing effectiveness.
• Continuous learning and iteration
The technology landscape is ever-evolving, and so are
the challenges faced by software teams. Organizations
should foster a culture of continuous learning and
iteration, encouraging teams to revisit and refine their
use of Design Patterns. The experiences of Twitter with
the Observer Pattern exemplify the importance of
adapting design approaches to optimize system
performance over time.
• Understand the trade-offs
While Design Patterns provide numerous benefits, they
also come with potential trade-offs, such as increased
complexity in certain cases. Developers must carefully
weigh the advantages against the complexities
introduced by each pattern. Understanding the specific
context in which a pattern is applied is crucial to
maximizing its effectiveness while minimizing
drawbacks.
• Document and share knowledge
Proper documentation of Design Patterns and their
implementations fosters knowledge sharing within the
organization. By creating a repository of Design
Patterns, teams can build on past successes and avoid
repeating mistakes. This practice is vital for onboarding
new team members and ensuring consistency in
software development practices.
The lessons learned from these case studies underscore the
importance of Design Patterns in creating robust, scalable,
and maintainable software solutions. By applying these
lessons, developers and organizations can not only improve
their technical capabilities but also enhance their overall
business outcomes. As the software development landscape
continues to evolve, leveraging Design Patterns will remain
a critical strategy for success.

Conclusion
This chapter has explored the practical implementation of
Design Patterns through case studies from leading
organizations such as Google, Netflix, Twitter, Amazon, and
Airbnb. Each case highlights how Design Patterns,
including Singleton, Factory, Observer, Composite, and
Strategy, have provided innovative solutions to complex
challenges, leading to significant improvements in
performance, scalability, and user satisfaction. The success
stories shared here not only demonstrate the technical
benefits of Design Patterns but also reveal their broader
business impacts, from enhanced resource management to
increased revenue and customer loyalty. Moreover, the
lessons learned emphasize the importance of flexibility,
maintainability, collaboration, and continuous learning in
the software development process. By recognizing the
trade-offs associated with Design Patterns and committing
to thorough documentation and knowledge sharing,
organizations can harness the full potential of these
patterns. As the software development landscape continues
to evolve, the insights gained from these case studies will
serve as valuable guides for developers seeking to
implement effective solutions in their own projects.
Ultimately, Design Patterns remain essential tools that
empower teams to create efficient, scalable, and
sustainable software systems that meet the demands of
today’s fast-paced digital world.

Points to remember
• Understanding Design Patterns: Design Patterns
are structured solutions to common software design
challenges, enhancing code maintainability and
readability.
• Real-world applications: Companies like Google,
Netflix, Twitter, Amazon, and Airbnb showcase how
Design Patterns address specific challenges and lead to
notable outcomes.
• Selection of patterns: Choosing the appropriate
Design Pattern is crucial; each has unique strengths
and weaknesses that should align with the problem
being solved.
• Flexibility and scalability: Design Patterns facilitate
adaptability, allowing software systems to evolve in
response to changing requirements and user needs.
• Measurable impact: Establishing clear metrics is
essential for evaluating the effectiveness of Design
Patterns and their contributions to business goals.
• Collaboration and communication: Successful
implementation relies on collaboration among cross-
functional teams to ensure alignment and effective
development processes.
• Continuous improvement: Foster a culture of
learning and iteration, regularly refining Design
Patterns to remain relevant in an evolving tech
landscape.

Questions
1. Which Design Pattern is primarily used by Google
to manage a single instance of a resource-
intensive object?
a. Factory Pattern
b. Singleton Pattern
c. Observer Pattern
d. Composite Pattern
2. What was the main benefit of the Factory Pattern
as implemented by Netflix?
a. Improved real-time notifications.
b. Streamlined inventory management.
c. Dynamic generation of service instances.
d. Enhanced revenue through dynamic pricing.
3. Which lesson learned emphasizes the importance
of collaboration among teams in the
implementation of Design Patterns?
a. Flexibility and scalability
b. Continuous improvement
c. Real-world applications
d. Collaboration and communication

Answers
1. b
2. c
3. d
Index
A
Abstract Factory Pattern 44
Abstract Factory Pattern, advantages 51
Abstract Factory Pattern, configuring 44
Abstract Factory Pattern, guide
Abstract Factory, defining 47
Abstract Products, defining 46
Client Code 49
Concrete Code, implementing 48
Concrete Products, implementing 47
Abstract Factory Pattern, instances
Document Process Libraries 50
Localization/Internationalization 50
UI Component Libraries 50
Abstract Factory Pattern, steps
Abstract Factory 45
Abstract Products, defining 45
Client Code 45
Concrete Factories, implementing 45
Concrete Products, implementing 45
Adapter Design Pattern 72
Adapter Design Pattern, advantages
Open/Close Principle 79
Single Responsibility Principle (SRP) 79
Adapter Design Pattern, applications
Internationalization 79
Legacy Code, integrating 78
Microservices, integrating 79
Third-Party API, integrating 78
Version, upgrading 79
Adapter Design Pattern, guide
Adapter 76, 77
Client Code 77
Existing Class 75
Target Interface 75
Usage 77
Adapter Design Pattern, scenario 72-74
Adapter Design Pattern, steps
Adapter, implementing 74
Client Code 74
Existing Class 74
Target Interface 74
Usage 74
Anti-Pattern 418
Anti-Pattern, points
God Object 419, 420
Premature Optimization 422
Spaghetti Code 420, 421
Async/Await 182, 183
Async/Await, keywords
async 183
await 183
Asynchronous JavaScript 172
Asynchronous JavaScript, sections
Async/Await 182
Callback Functions 175, 176
Callback Hell 177, 178
Generator/Yield 185, 186
Promises 179
Synchronous JavaScript 172
Web APIs 186

B
Batch Processing 208
Batch Processing, advantages 213
Batch Processing, applications
API Requests 212
Database Operations 212
Data Migration 213
Image Processing 212
Job Queues 213
Report Generation 213
Batch Processing, architecture 208, 209
Batch Processing, concepts
Async Task Function, implementing 210
Complex Logic, executing 211
Function, defining 209, 210
Input Data, defining 211
Batch Processing, disadvantages 213, 214
Behavioral Design Patterns 120
Behavioral Design Patterns, architecture 123
Behavioral Design Patterns, configuring 120-122
Behavioral Design Patterns, issues
Dynamic Behaviour 123
Flexibility/Extensibility 123
Loose Coupling 122
Responsibility Chain 123
Bridge Design Pattern 79
Bridge Design Pattern, advantages 86
Bridge Design Pattern, applications
Communication Channels 86
Cross-Platform Applications 85
Database Abstraction Layer 85
Network Communication Framework 86
Bridge Design Pattern, concepts
Abstraction 83
Client Code 84
Concrete Implementors 82
Implementor Interface 82
Refined Abstraction 84
Bridge Design Pattern, scenarios 80
Bridge Design Pattern, steps
Abstraction 81
Client Code 81
Concrete Implementors, reating 81
Implementor Interface, defining 81
Refined Abstractions 81
Builder Pattern 51
Builder Pattern, advantages
Flexibility 60
Reusability 60
SRP 61
Builder Pattern, applications
Composite Objects, assembling 60
Database Query 60
Documents/Reports, building 60
Object Construction 60
Product Variants 60
Builder Pattern, approaches 52
Builder Pattern, architecture 51
Builder Pattern, concepts
Builder Interface 55
Client Code 57
Concrete Builders 56
Director 56
Product 54
Builder Pattern, observations 59
Builder Pattern, steps
Builder Interface, defining 53
Client Code 54
Concrete Builders, creating 53
Director, creating 54
Product, defining 53

C
Callback Functions 175, 176
Callback Hell 177, 178, 398
Callback Hell, architecture 398-401
Callback Hell, reasons
Anonymous Functions 179
Complex Control Flow 179
Error Handling 179
Sequential Asynchronous Operation 179
Chain of Responsibility (CoR) Pattern 123
Class-Based/JavaScript OOP, comparing 29
Collaborative Pattern Development 378
Collaborative Pattern Development, approach 378, 379
Combining Design Patterns 299
Combining Design Patterns, advantages 299, 300
Combining Design Patterns, section
Factory Design With Singleton 300-302
Mediator Design With Observer 302-304
Strategy Design With Decorator 304-306
Command Pattern 131
Command Pattern, applications
GUI Applications 136
Logging/Auditing 136
Smart Home Automation 136
Undo/Redo Functionality 136
Workflow Management 136
Command Pattern, architecture 131
Command Pattern, benefits 137
Command Pattern, concepts
Client Code 135
Command Interface 132
Concrete Command Classes 133
Invoker Classes 134
Receiver Classes 134
Command Pattern, steps
Client Code 132
Command Interface 131
Concrete Command Classes, implementing 132
Invoker Classes, implementing 132
Receiver Classes, implementing 132
Composite Design Pattern 86
Composite Design Pattern, applications
Document Object Model (DOM) 91
E-Commerce Shopping 92
File System Operations 92
GUI Components 91
Menu Systems 92
Tree Structures 91
Composite Design Pattern, concepts
Client Code 90, 91
Component Interface 88
Composite Class, implementing 89
Leaf Class, implementing 88
Composite Design Pattern, pros 92
Composite Design Pattern, scenario 86, 87
Composite Design Pattern, steps
Client Code 87
Component Interface 87
Composite Class, implementing 87
Leaf Class, implementing 87
Computed Properties 272, 273
Computed Properties, advantages 274
Concurrency 206
Concurrency, approaches
Async/Await 208
Callbacks 207
Event Loop 207
Promises 207
Concurrency/Parallelism, comparing 206, 207
Container/Component Pattern 238
Container/Component Pattern, advantages 242
Container/Component Pattern, term
Container 239-241
Presentation 241
Context API Pattern 245, 246
Context API Pattern, advantages 249
Context API Pattern, implementing 246-248
CoR Pattern, advantages 130
CoR Pattern, applications
Chatbot Response, processing 130
[Link] Middleware 130
Form Validation 130
Middleware Processing 130
Workflow Engines 130
CoR Pattern, concepts
Chain, setup 128
Client Code 129
Concrete Handlers 126
Handle Method, implementing 127, 128
Handler Interface 125
CoR Pattern, scenarios 123, 124
CoR Pattern, steps
Chain, setup 125
Client Code 125
Concrete Handlers, creating 125
Handle Method, implementing 125
Handler Interface, defining 125
Creational Design Patterns 30

D
Debouncing 195
Debouncing, advantages 199
Debouncing, architecture 196-198
Debouncing, disadvantages 199
Debouncing, scenarios 198, 199
Decorator Design Pattern 92
Decorator Design Pattern, advantages 98
Decorator Design Pattern, application
Authentication 97
Data Validation 97
Dynamic Configuration 97
Event Handling 97
User Interface 97
Decorator Design Pattern, concepts
Component Interface 94
Concrete Components 94
Concrete Decorators 95
Decorator Class 95
Usage 96
Decorator Design Pattern, disadvantages 98
Decorator Design Pattern, scenarios 92, 93
Decorator Design Pattern, steps
Component Interface 93
Concrete Component 93
Concrete Decorators 93
Decorator Class, creating 93
Usage 94
Decorator Pattern 258, 259
Designing Systems 312
Designing Systems, roles 312, 313
Design Pattern Authoring, process
Problem/Context, defining 366
Problem Scenarios 364
Structure/Elements 368
Design Pattern Authoring Process 364
Design Patterns 6, 7
Design Patterns, advantages
Abstraction/Encapsulation 10
Common Vocabulary 10
Facilitates Evolution/Adaptation 11
Faster Development 11
Maintainability 10
Performance, improving 10
Proven Solutions 10
Reusability 9
Scalability 10
Design Patterns, architecture 8
Design Patterns, aspects
Decorator Pattern 319
Factory Pattern 318
Module Pattern 314, 315
Observer Pattern 317
Proxy Pattern 321
Revealing Module Pattern 315
Singleton Pattern 316
Strategy Pattern 320
Design Patterns Business, impacts 435
Design Patterns, case studies 434
Design Patterns, categories
Behavioural Design 9
Creational Design 9
Structural Design 9
Design Patterns, fundamentals
Four Book Gang 8
Pattern Languages 7, 8
Design Patterns, key takeaways 436, 437
Design Patterns, lists
Decorator Pattern 325
Factory Pattern 327
Observer Pattern 324
Proxy Pattern 326
Singleton Pattern 323
Design Patterns, points
Decorator Pattern 332
Factory Pattern 332
Module Pattern 330
Observer Pattern 331
Proxy Pattern 333
Singleton Pattern 330
DOM Manipulation 405
DOM Manipulation, pitfalls
Direct DOM, accessing 407
Excessive Style 405
Unnecessary DOM 406
Dynamic Load Components 276
Dynamic Load Components, ways 276, 277

E
Effective Design Patterns 373
Effective Design Patterns, strategies
Concise Pattern Descriptions 373
Refinement/Documentation, reviewing 375
Reusability/Flexibility, ensuring 374, 375
Error Handling 408
Error Handling, terms
Lock of Proper 408, 409
Silent Failures 411-413
Event Handling 187
Event Handling, aspects
Event Loop 190
Event Propagation 189
Events 187, 188
Event Loop 190
Event Loop, configuring 190, 191
Event Propagation 189
Event Propagation, phase
Bubbling 189
Capturing 189

F
Façade Design Pattern 98
Façade Design Pattern, applications
APIs, optimizing 103
Browser Compatibility 103
Legacy Code Integration 103
Resource Management 103
Third-Party Library, integrating 103
Façade Design Pattern, concepts
Client Code, uses 102
Encapsulate 101
Interface, simplifying 101
Methods, expose 102
Subsystem Classes 100
Façade Design Pattern, pros 103
Façade Design Pattern, scenario 98
Façade Design Pattern, steps
Client Code 99
Facade Classes, creating 99
Simplified Methods 99
Subsystem Classes, creating 99
Subsystem Logic, encapsulate 99
Factory Method 37
Factory Method, advantages 43
Factory Method, applications
Data Access Layers 43
Game Development 43
Internationalization 43
UI Component Libraries 43
Factory Method, architecture 37
Factory Method, disadvantages 43
Factory Method, implementing 38
Factory Method, steps
Client Code 42
Concrete Creator, implementing 41
Concrete Products, implementing 39
Creator Interface, defining 40
Product Interface 39
Factory Pattern 253
Factory Pattern, optimizing 253-255
Flyweight Design Pattern 104
Flyweight Design Pattern, applications
Collaborative Tools 109
Document Object Model (DOM) 109
Engine Rendering 108
UI Components 108
Flyweight Design Pattern, process
Client Code 105
Concrete Class, implementing 105
Extrinsic States, identifying 104
Flyweight Factory 105
Flyweight Interface, creating 105
Flyweight Design Pattern, pros 109
Flyweight Design Pattern, scenarios 104
Flyweight Design Pattern, steps
Class, implmenting 106
Client Code 108
Extrinsic States 106
Factory, implementing 107
Interface, creating 106

H
Higher-Order Components (HOCs) 236
HOCs, configuring 236, 237
HOCs, patterns
Adapter 238
Composite 238
Decorator 237
Factory 237
Proxy 238
Strategy 237
Hooks 230
Hooks, components
useCallback 235
useMemo 234
Hooks, patterns
Adapter 235
Factory 235
Observer 235
Proxy 235
Strategy 235
Hooks, types
Context 231
Effect 233
Performance 234
Ref 232
State 231

I
Iterator Pattern 137
Iterator Pattern, advantages 143
Iterator Pattern, applications
Database Results, navigating 143
Object Properties, traversing 143
Over Arrays, iterating 142
Parsing Streams 143
Iterator Pattern, architecture 137-139
Iterator Pattern, concept
Aggregate Object 141
Client Code 141
Concrete Iterator 140
Iterator Interface 140
Iterator Pattern, disadvantages 143
Iterator Pattern, steps
Aggregate Object, creating 139
Client Code 139
Concrete iterator, implementing 139
Iterator Interface 139

J
JavaScript 2
JavaScript Conversion, types
Behavioral Pattern 388
Breaking Consistency 385, 386
Structural Pattern 387
JavaScript Design Patterns, approach
Automate Tests 360
Coverage Ensuring 360
Design Pattern 360
Mock Dependencies 360
Test Cases 360
JavaScript Design Patterns, case study
Composite Pattern 430
Factory Pattern 428
Observer Pattern 429
Singleton Pattern 426
Strategy Pattern 432
JavaScript, fundamentals
Inception 2
Revival 3
War Browsers 2, 3
JavaScript, pitfalls
Problem Statement 1 3, 4
Problem Statement 2 4
Problem Statement 3 4, 5
Problem Statement 4 5
Problem Statement 5 6
JavaScript, solutions
Problem Statement 1 11, 12
Problem Statement 2 12, 13
Problem Statement 3 13, 14
Problem Statement 4 14, 15
Problem Statement 5 15, 16

M
Mediator Pattern 144
Mediator Pattern, advantages 149
Mediator Pattern, applications
Chat Applications 148
Game Development 148
Microservices Communication 149
Real-Time Collaborative 149
Mediator Pattern, architecture 144
Mediator Pattern, concepts
Communication mediator 148
Entity Objects 146
Mediator Object 145, 146
Register Entities 147
Mediator Pattern, steps
Communication Mediator 145
Entity Objects, creating 145
Mediator Object, defining 145
Register Entities 145
Memento Pattern 150
Memento Pattern, advantages 155
Memento Pattern, application
Drawing Application 155
Form State Management 154
Game State Management 155
Text Editors/Code Editors 154
Memento Pattern, architecture 150
Memento Pattern, concepts
Caretaker Class 152
Client Code 153
Memento Class 152
Originator Class 151
Memento Pattern, disadvantages 155
Memento Pattern, steps
Caretaker Class 151
Client Code 151
Memento Class 150
Originator Class, creating 150
Memory Management 401
Memory Management, term
Data Handling 404, 405
DOM Manipulation 402, 403
Memory Leak 401, 402
Module Pattern 250, 282
Module Pattern, advantages 285
Module Pattern, disadvantages 285
Module Pattern, implementing 283, 284
Module Pattern, principles
Closures 283
Encapsulation 283
Module Pattern, steps 250-253
Module Pattern, utilizing 250
MVC Pattern 286
MVC Pattern, advantages 290
MVC Pattern, configuring 286-290
MVC Pattern, disadvantages 290
MVMM Pattern 295
MVMM Pattern, advantages 298, 299
MVMM Pattern, components 295
MVMM Pattern, disadvantages 299
MVMM Pattern, implementing 296-298
MV* Patterns 285
MV* Patterns, components
Controller/ViewModel/Presenter 286
Model 286
View 286
MV* Patterns, section
MVC Pattern 286
MVMM Pattern 295
MVP Pattern 290
MVP/MVC Pattern, comparing 291-294
MVP Pattern 290, 291
MVP Pattern, advantages 294
MVP Pattern, disadvantages 295

O
Object Creation 30
Object Creation, fundamentals
Code Organization 30
Encapsulation 30
Reusability 30
Scalability 30
Object Creation, terms
Circular Dependencies 31, 32
Memory Leak 31
Uninitialized Variables 32
Object-Oriented Programming (OOP) 20
Observer Pattern 155, 255
Observer Pattern, applications
Cross-Component Communication 162
Event Handling 162
Model-View-Controller (MVC) 162
UI Components/State Management 162
Observer Pattern, architecture 156
Observer Pattern, concepts
Notification Mechanism, implementing 159, 160
Observer Classes, defining 158
Subscribe/Trigger 160, 161
Subject Class 157, 158
Subscription Mechanism, implementing 158, 159
Observer Pattern, implementing 156, 157
Observer Pattern, pros 162
OOP, concepts
Constructors 24-26
Inheritance 26, 27
Object Prototype 23, 24
Polymorphism 28, 29
OOP, principles
Abstraction 22
Encapsulation 22
Inheritance 22
Polymorphism 22
OOP, section
Classes 21, 22
Objects 21
OOP, types
Classical 20
JavaScript 22

P
Parallelism 200
Parallelism, approaches
Parallel Array 200
Parallel Processing 200
Web Workers 200
Promises 179, 180
Promises, approaches
Chaining 215
Error Handling 216
Memory Management 217
Nested Avoiding 214, 215
Parallel 216
Performance Considerations 218
Reuse 217
Promises, method
catch() 181
then() 180
Promises, states
Fulfilled 180
Pending 180
Rejected 180
Prototype Pattern 61
Prototype Pattern, advantages 64
Prototype Pattern, applications
Dynamic Object, configuring 64
Object Initialization 64
Object States, managing 64
Role-Based Permissions 64
Prototype Pattern, approaches 61
Prototype Pattern, architecture 61
Prototype Pattern, guide
Customizing 63
Instances, creating 63
Instances, testing 63
Object Prototype 62
Prototype Pattern, steps
Instance, creating 62
Instance, customizing 62
Instances, testing 62
Object Prototype, creating 62
Proxy Design Pattern 109
Proxy Design Pattern, advantages 114
Proxy Design Pattern, application
Access Control 114
Caching 114
Lazy Loading 114
Remote Proxy 114
Validation 114
Proxy Design Pattern, architecture 109
Proxy Design Pattern, disadvantages 114
Proxy Design Pattern, implementing 111-113
Proxy Design Pattern, process
Client Code 110
Proxy, creating 110
Proxy, implementing 110
Proxy Interface, defining 110
Real Object, implementing 110
Subject, identifying 110

R
React 222
React, architecture 223, 224
React, challenges
Code Reusability 223
DOM Manipulation 222
Scalability 223
React, patterns
Container/Component Pattern 238
Context API Pattern 245
Render Prop Pattern 242
React, terms
Component-Based Architecture 224
Higher-Order Components (HOCs) 236
Hooks 230
React, ways
Decorator Pattern 258
Factory Pattern 253
Module Pattern 250
Observer Pattern 255
Render Prop Pattern 242
Render Prop Pattern, advantages
Composability 245
Encapsulation 245
Flexibility 245
Reusability 245
Render Prop Pattern, implementing 242-245

S
Scoped Styles 275
Scoped Styles, advantages 276
Scoped Styles, configuring 275
Security Vulnerabilities 413
Security Vulnerabilities, points
Cross-Site Scripting (XSS) 415
CSRF 416-418
Singleton Pattern 413, 414
Sharing Design Patterns 376
Sharing Design Patterns, repositories
E-Books/Print Books 378
GitHub/GitLab 377
Online Pattern 377
Personal Blog/Websites 377
Singleton Pattern 32
Singleton Pattern, configuring 33
Singleton Pattern, cons 37
Singleton Pattern, implementing 34, 35
Singleton Pattern, pros 37
Singleton Pattern, scenarios
Application State, maintaining 37
Caching 36
Database Connection, managing 36
Global Configuration 36
Singleton Pattern, steps 34
Strategy Pattern 163
Strategy Pattern, advantages 168
Strategy Pattern, applications
Authentication Strategies 167
Form Validation 167
Payment Processing 168
Routing Strategies 168
Search Algorithms 168
Sorting Algorithms 167
Strategy Pattern, architecture 163
Strategy Pattern, disadvantages 168
Strategy Pattern, implementing 163, 164
Strategy Pattern, scenarios 164-167
Structural Design Patterns 68
Structural Design Patterns, attributes
Classes Organization 71
Code Reusability 71
Design Problems 72
Flexibility/Adaptability 72
Object Composition 71
Structural Design Patterns, sections
Adapter Pattern 115
Bridge Pattern 115
Composite Pattern 115
Decorator Pattern 116
Facade Pattern 116
Flyweight Pattern 116
Proxy Pattern 116
Synchronous JavaScript 172-174
Synchronous JavaScript, challenges
Blocking Nature 174
Browser Compatibility 175
Concurrency/Stability 175
Network Operations 175
User Experience 175

T
Testing 338
Testing, role
Boost Confidence 339
CI/CD 339
Code Quality, ensuring 339
Collaboration, improving 339
Facilitates Refactoring 339
Maintainability, enhancing 339
Testing, types
End-to-End 339
Integration 339
Unit 338
this keyword 394
this keyword, scenarios 394-396
Throttling 191
Throttling, advantages 195
Throttling, disadvantages 195
Throttling, resources
Applications 194
Implementation 192, 193
Input Events 194
Network Requests 194
Polling 194
Problem Statement 191
Resize Events 194
Scrolling Events 194
UI Updates 194

U
Unit Testing 339, 340
Unit Testing, pattern
Adapter 349, 350
Builder 350
CoR 355-357
Decorator 347, 348
Factory 341, 342
Mediator 353, 354
Observer 343, 344
Prototype 352
Singleton 340, 341
Strategy 345-347
Unit Testing, section
Adapter Pattern 358
Builder 359
CoR 359
Decorator 358
Factory 358
Mediator 359
Observer 358
Prototype 359
Singleton 357
Strategy 358

V
Variable Hosting 390
Variable Hosting, pitfalls
Accidental Global Variables 390, 391
Scope Binding 393
Unexpected Behavior 392
Vue 262
Vue, architecture 263
Vue, features
Custom Directives/Mixins 269, 270
SFCs 264
State Management 267, 268
Vue, issues
Code Reusability 263
DOM Manipulation 262
Scalability 263
Vue, pattern
Computed Properties 272, 273
Dynamic Load Components 276
Scoped Styles 275
Vue, ways
Factory Pattern 278
Observer Pattern 278
Singleton Pattern 277

W
Web APIs 186
Web APIs, fundamentals
Canvas API 187
DOM 186
Fetch API 186
Geolocation API 186
Web Audio API 187
Web Storage 186
XMLHttpRequest (XHR) 186
Web Architect 311
Web Architect, role
Integration/Communication 312
Maintainability/Futureproofing 312
Security Consideration 312
Technology Selection 312
Web Architecture Blueprint 312
Web Architecture 310
Web Architecture, elements
Components 310
Scalability Performance 311
Security 311
Technologies 311
Tiers 311
Web Workers 201
Web Workers, advantages 205, 206
Web Workers, configuring 201-205
Web Workers, disadvantages 206
Web Workers, scenarios
Intensive Calculations 205
Large Data Processing 205
Real-Time Data, visualizing 205
Web-Based Games 205

You might also like