Design+Patterns+Notes+-+Complete
Design+Patterns+Notes+-+Complete
Design, Design
Patterns & SOLID
Principles
Prateek Narang
PDF Notes
Course Link
Prateek Narang
Pre-requisites
● Basic knowledge of programming languages & OOPS concepts preferably Java
● Basic problem solving skills & logical thinking
● Familiaity with Java IDEs (IntelliJ IDEA, Eclipse etc)
Course Logistics
● Github Repository
○ https://github.com/prateek27/design-patterns-java
● Slides/PDF Notes
● Coding Exercises
● Quizzes
● Q/A & Discussion Section
Course Structure
1. OOP Recap
2. SOLID Principles
3. Design Patterns
○ Behavioural
○ Creational
○ Structural
4. Project
Module 1
Object Oriented
Programming
Recap
Prateek Narang
Keys Concepts in OOPS
1. Encapsulation
2. Abstraction
3. Inheritance
4. Polymorphism
Encapsulation
● Binding data (variables) and methods (functions) together into a single unit, usually a class.
● Control access to data via access modifiers (e.g., private, public).
● Example: Getters and setters in a class to access and update private data members.
Abstraction
● Hiding unnecessary details and showing only the essential information.
● Focus on what an object does, not how it does it.
● Example: Defining a method draw() in a Shape class without revealing the
drawing logic (implemented by subclasses like Circle, Rectangle).
Inheritance
● Mechanism for creating new classes based on existing classes.
● Helps in code reuse and establishing relationships between objects.
● Example: Car class inherits from Vehicle class and adds specific behavior
like startEngine().
Polymorphism
● Allows one interface to be used for a general class of actions.
● Achieved through method overriding (runtime polymorphism) or method
overloading (compile-time polymorphism).
● Example: A single method area() can be used for different objects like
Circle, Rectangle, with different implementations.
Design a Payment Service
For this example, we can assume payment service should be responsible for storing
details of Payments Methods (Credit Card, Debit Card etc) that belong to a user and also
have a makePayment() Method.
UML - Unified Modeling Language
UML Diagrams
● Unified Modeling Language to model systems
Example: A User class should only handle user-related logic, while database-related
operations should be handled by a separate UserRepository class.
O – Open/Close Principle
Why It Matters: Repeated code increases the risk of bugs, makes maintenance
difficult, and increases the chance of inconsistencies.
Example:
Instead of writing the same database connection logic in multiple places, use a
DatabaseConnection class that handles this logic, ensuring code reuse.
What are Design Patterns?
Design Patterns
● A design pattern is a general, reusable solution to a common problem within a
specific context in software design.
● Patterns are blueprints that can be applied in multiple situations, but they are
not direct pieces of code.
● Importance:
○ Promoted modular, maintainable, and scalable code.
○ Encouraged reusability of solutions across different projects.
Creational Patterns: Deal with object creation mechanisms, trying to create objects
in a manner suitable to the situation.
Structural Patterns: Deal with the composition of objects or classes to form larger
structures.
Prateek Narang
Agenda
● Overview of Behavioral Design Patterns
● Observer Pattern
● Strategy Pattern
● Command Pattern
● Template Method Pattern
● Iterator Pattern
● State Pattern
● Mediator Pattern
● Memento Pattern
Behavioral Patterns
Behavioral patterns focus on how objects communicate and interact, managing the
flow of information between entities.
They simplify complex control flow by defining clear communication and behavior
among objects.
State Restoration: Used in scenarios where you need to periodically save system
states (e.g., games, data recovery) and allow users to return to previous states.
Use Cases:
If we need multiple devices (e.g., mobile, tablet) to display the temperature, we need
to modify the WeatherStation class, leading to poor scalability and flexibility.
Observer Pattern
Problem: There is a need to notify multiple objects about a change in state without
tightly coupling them.
● Scalability: New observers (e.g., new display devices) can easily be added
without changing the subject.
With the Strategy Pattern, the logic for each payment type is encapsulated in separate strategy
classes, and the PaymentService (context class) delegates the task of payment processing to one
of these strategies at runtime.
Strategy Pattern
Problem: Hardcoded algorithms in classes lead to:
○ Code duplication.
○ Increased maintenance complexity when switching between algorithms.
○ Violation of Open/Closed Principle: Modifications are required every
time a new algorithm is introduced.
Solution: The Strategy Pattern decouples the algorithm implementation from the
client, allowing easy switching of algorithms without altering the client code.
Strategy Pattern Structure
Context: The client class that uses a strategy to perform an operation.
Strategy Interface: Defines the operations that all concrete strategies must
implement.
Without the Command Pattern, the buttons directly interact with the TextEditor
class, and you’d end up hardcoding behavior into the UI classes, making them tightly
coupled.
Code
Problems in Code
● Each button class is tightly coupled with the TextEditor. If the action
changes, all button classes need to be modified.
Task Scheduling:
Undo/Redo Functionality:
● Commands can be stored and rolled back to provide undo and redo
capabilities, especially in applications like IDEs, word processors, or graphics
software.
Macro Recording:
● Overhead: Each operation becomes an object, which may add memory and
performance overhead in systems with large numbers of commands.
Template Method Pattern
Motivation Problem
Consider a scenario where you have different data parsers (e.g., CSV, XML,
and JSON). Each parser follows the same steps: open file, parse data, and
close file.
Without the Template Method Pattern, you might end up duplicating the
common steps in each parser class.
Problems in our code
● Code duplication: The openFile() and closeFile() methods are
duplicated in both parsers.
● Any changes to the common logic would require changes in every parser,
violating the DRY (Don’t Repeat Yourself) principle.
Template Method Pattern
Problem: Different parts of an algorithm may need to vary in subclasses, but the
overall structure should remain consistent.
Structure:
Document Processing: A framework might define the skeleton for reading, processing, and
saving documents, while specific formats (e.g., Word, PDF) provide their own processing logic.
Game Development: A game loop (initialize, update, render) can be defined in a base class, with
specific games implementing their own logic for updating and rendering.
The Template Method Pattern is ideal for situations where a common algorithm exists, but some
steps may need to be redefined by subclasses. It helps enforce structure and promotes
reusability, while allowing flexibility where needed.
●
Iterator Pattern
Iterator Pattern Motivation
Suppose you have a collection, such as an array or list, and you need to
provide a mechanism for accessing its elements. Without the iterator pattern,
the client code needs to understand how the collection is structured, and
different collections would require different methods to traverse them.
Code Demo
Problems in our Code
Problems:
● The client needs to know the internal structure of the collection (array in this
case).
● If we change the collection type (e.g., from an array to a linked list), we would
need to modify the client code.
● It’s harder to implement different traversal strategies.
Iterator Pattern
Problem: How to access elements in a collection without exposing its internal
representation.
Structure:
1. Additional Complexity: Implementing the iterator pattern can add extra layers of abstraction,
especially for small or simple collections where direct traversal is sufficient.
2. Increased Overhead: For small collections or when the structure is unlikely to change, the
overhead of creating iterators may not be justified.
State Pattern
State Pattern
You are tasked with building a DirectionService class for a navigation app. This class calculates the
estimated time of arrival (ETA) and provides directions between two points. The ETA and direction differ
based on the mode of transportation, which can be one of the following:
● Walking
● Cycling
● Car
● Train
Code Demo
Problems with our Code
Tight Coupling and Complex Conditional Logic:
● Adding new transportation modes (e.g., Airplane, Boat) requires modifying the existing
DirectionService class, which goes against the Open/Closed Principle (classes should be
open for extension but closed for modification).
Problems with our Code
Code Duplication and Reduced Maintainability:
● Similar code blocks for different transportation modes may lead to code duplication, making the
system less maintainable and more error-prone.
Scalability Issues:
● As more features or transportation modes are added, the class becomes bulky, impacting scalability
and readability.
State Pattern: Structure
Structure:
Example:
● As more users are added, each user needs to manage direct communication
with all others, leading to high coupling.
Solution: The Mediator Pattern introduces a mediator object that handles all
communication between objects, reducing direct dependencies and coupling.
In our chat app, by introducing a Mediator object, we will decouple the users from knowing
about each other directly. The Mediator handles all communication, and the users
(colleagues) only interact with the Mediator. This simplifies the interaction and reduces
dependencies.
Mediator Pattern Code
1. ChatMediator Interface: Declares the sendMessage method, which all mediators must
implement.
2. Concrete Mediator:
○ The ChatRoom class implements the ChatMediator interface. It holds a list of users and
handles message broadcasting.
3. User Class:
○ Each User object represents a participant in the chat. When a user sends a message, the
sendMessage method in the ChatRoom mediator is called, which distributes the message
to all users except the sender.
4. Communication:
○ Users interact only with the ChatRoom (mediator), which facilitates communication between
them, removing direct dependencies between individual users.
Mediator Pattern Benefits
Airplanes communicate through a central control tower (mediator) instead of coordinating directly
with each other.
In GUI applications, multiple UI components may need to interact. For example, when a dropdown
changes, it may trigger updates to text fields, buttons, etc. A mediator can handle this interaction
logic instead of having the components know about each other directly.
Workflow Systems:
In a business process management system, a mediator can coordinate various activities across
multiple systems or departments.
UML Diagram
Summary
Summary
This module covers key Behavioral Design Patterns that help define how objects
interact and manage state transitions within a system. From observer-based event
notification to decoupling algorithms with strategies, each pattern offers a
solution to common communication challenges in software design.
Module
Creational Design
Patterns
Prateek Narang
Agenda
● Overview of Creational Design Patterns
● Singleton Pattern
● Builder Pattern
● Prototype Pattern
● Factory Pattern
● Abstract Factory Pattern
Overview
Purpose: Creational patterns are focused on object creation mechanisms, aiming
to optimize the creation process while ensuring flexibility.
Goal: They abstract the instantiation process to make systems more flexible and
reusable.
Problem They Solve: Prevent tight coupling between code and object creation logic,
simplifying the management of new object creation, especially in complex systems.
Example:
● In a system where different types of documents (PDF, Word, Excel) are created,
instead of using new everywhere, a Factory Pattern can centralize object
creation.
Overview
Application: They are applied in scenarios where:
● Inconsistent state: If multiple instances represent the same concept, they may
hold different data.
● Resource conflicts: If multiple instances of a resource-heavy class are
created, it can lead to performance degradation.
Singleton Design Pattern
The Singleton pattern is used when exactly one instance of a class is required to
coordinate actions across the system.
When to Use
Solutions:
For example, constructing an object with multiple optional parameters without the
Builder pattern can look like this:
Code Demo
Builder Design Pattern
Problem: When a class constructor has too many parameters, the Builder Pattern
allows step-by-step construction of complex objects.
We will use the prototype pattern allows us to make a copy of the current board,
including all its pieces and their states, without the need for deeply recreating each
part of the board.
Code Demo
Solution - Prototype Pattern
● The Prototype Pattern can be extremely useful in a board game when you
want to save the current state of the game (including the board layout and the
position of pieces) for undo/redo functionality, checkpoints, or simply making a
copy of the board for a new player.
● Each piece or game element can provide its own clone method, allowing the
entire board to be easily cloned with all its current state.
By using the Prototype pattern, we decouple the complexity of cloning the board from
the client, ensuring that each object knows how to clone itself, making the system
flexible and easier to maintain.
Shallow vs Deep Copy
Shallow Copy: Creates a new object but does not clone the objects that the original object refers
to.
Deep Copy: Clones the original object and all the objects it refers to (nested objects)
Example:
● Shallow Copy: Cloning an object with references to other objects (only the outer object is copied).
● Deep Copy: Cloning the entire object graph, including any objects the original refers to.
Benefits
● Simplifies Object Creation: Instead of manually copying each object, the clone method
in each object simplifies the creation of copies.
● Avoids Subclassing: The pattern relies on delegation to the clone method, allowing the
class itself to handle object creation, avoiding the need for subclassing.
● Shallow or Deep Copy: Depending on the use case, you can either implement a shallow
copy (copying references) or a deep copy (cloning objects) based on the specific
requirements.
● Efficient Creation: When creating objects with a complex structure or when performance
is a concern, the Prototype pattern allows you to efficiently replicate objects.
● Consistency: Ensures that all properties of the object are consistently copied, avoiding
errors associated with manual copying.
Use Cases
● Use Cases:
○ Cloning large objects where construction is costly (e.g., deep configuration settings).
○ When system resources (time, memory) are limited and re-creating objects is expensive.
Factory Design Pattern
Motivation
Consider an example of a transportation service app where users can
request different types of transport vehicles (e.g., Car, Bike, Bus). You
might initially create separate classes for each type, and create instances
like this:
But as the system evolves, managing object creation directly like this can
become complex, especially when adding new types of vehicles.
Code
Problems:
The Factory Design Pattern is a fundamental tool to reduce coupling and centralize
object creation logic, especially in systems that need to support multiple types of
objects.
Abstract Factory Pattern
Motivation
Consider an application that needs to support multiple UI themes (e.g., Windows,
macOS). Each theme has its own set of UI components such as buttons, scrollbars,
and windows. The challenge is to create an architecture that allows switching
between these themes without changing the client code that uses the UI
components.
Without the Abstract Factory Pattern, the client code would be tightly coupled with
the concrete implementations of buttons, scrollbars, etc., and switching between
themes would require modifying the client code.
Problems in Code
1. Tight coupling: The client code is tightly coupled to specific implementations of UI
components (WindowsButton, WindowsScrollBar).
Structure:
Using the Abstract Factory Pattern, you create interfaces for each product (e.g., Button, ScrollBar, Window)
and provide a family of concrete implementations for each theme (e.g., WindowsButton, MacOSButton, etc.).
The Abstract Factory provides a way to create a suite of related objects without knowing the exact type of
objects that will be created.
Factory vs Abstract Factory
Factory Method: Defines a method in the base class but lets subclasses override it
to specify the type of objects that will be created.
Prateek Narang
Agenda
● Overview of Structural Design Patterns
● Adapter Pattern
● Decorator Pattern
● Proxy Pattern
● Composite Pattern
● Facade Pattern
● Bridge Pattern
● Flyweight Pattern
Overview
Structural patterns are design patterns that deal with how objects and classes are
composed to form larger structures, making the system more flexible and easy to
understand.
Let's see how the Adapter Pattern can solve this problem.
Adapter Pattern
● Problem: When two systems or components have incompatible interfaces, they
cannot work together directly.
● Solution: The Adapter Pattern bridges the gap by converting the interface of
one class into another that the client expects.
Real-world analogy:
● A power adapter that allows a device with a US power plug to fit and work with
a European power socket
Code Demo
Adapter Pattern Examples
● Adapters in Software Frameworks: In GUI frameworks, adapters are used to
convert legacy code into newer formats.
● Adapter in Java I/O: In Java, InputStreamReader works as an adapter to
convert InputStream (byte-based) to Reader (character-based).
● Adapter in APIs: When integrating external libraries, you often need adapters
to convert data formats or APIs to match your system's requirements.
Let's use the Decorator Pattern using a Pizza making example where different
toppings (like cheese, olives, tomatoes, etc.) can be dynamically added to the base
pizza.
Decorator Pattern
Problem: In a system where you need to dynamically extend or add behavior to
objects, inheritance can lead to inflexible and tightly coupled code.
Solution: The Decorator Pattern allows you to add new functionality to objects at
runtime by wrapping them with decorator classes, providing flexibility
Real-world analogy:
● A pizza order where you start with a basic pizza and add toppings dynamically
(e.g., cheese, pepperoni) without modifying the original pizza class.
Code Demo
Decorator Pattern
Flexible and Scalable: You can add as many toppings as needed by simply creating a
decorator for each topping. No need for subclassing every combination.
Single Responsibility Principle: Each decorator class has one responsibility — to add a
specific topping.
Open/Closed Principle: The BasicPizza class remains unchanged, and new features
(toppings) can be added by creating new decorators.
Combinatorial Freedom: You can mix and match toppings in any order, making the system
more flexible and reusable.
Decorator vs Inheritance
Example
1. Lazy Initialization: Objects are loaded only when necessary, saving memory
and CPU cycles.
2. Access Control: You can control access to the real object (e.g., based on user
permissions).
3. Additional Behavior: Proxies can add additional functionalities like logging,
access control, or caching without modifying the real object.
4. Separation of Concerns: The real object only deals with its core
responsibilities, while the proxy handles ancillary operations like initialization or
security.
Composite Pattern
Composite Pattern
Problem: When building systems like a file directory, which consist of both
individual items (files) and groups of items (directories), managing these with
standard object hierarchies can become complex.
Solution: The Composite Pattern allows you to treat individual objects and
compositions of objects uniformly by representing part-whole hierarchies.
Composite Pattern
In a microservices architecture, each microservice can have its own API for specific business logic, such as
user management, order processing, and inventory. If the client needs to interact with these microservices, it
would need to directly communicate with all the individual services. This would increase the complexity of the
client code, tightly couple the client to all the microservices, and expose the inner workings of the system.
Motivation
Solution Using Facade (API Gateway):
The API Gateway acts as a facade, providing a unified interface to the client while handling communication
with the underlying microservices. It simplifies client interactions, reduces network calls, and abstracts away
the complexity of dealing with multiple services.
Facade Pattern
Example: API Gateway as a Facade
Solution: By sharing the common state among multiple objects (the flyweights), the
pattern reduces the memory footprint.
Example Problem:
Purpose: To optimize resource usage in applications that need to handle a large number of
similar objects by reusing common parts of their data.
When to Use:
● When you need to create a large number of objects, and they share a lot of common
data.
● Ideal for memory-constrained applications where the object creation cost is high
Flyweight Pattern Structure
● Flyweight: The shared object that stores intrinsic state (common data).
● Concrete Flyweight: Implements the flyweight interface and shares the intrinsic state.
● Flyweight Factory: Manages and creates flyweight objects, ensuring that objects with
the same intrinsic state are reused.
● Client: Holds and manages the extrinsic state (unique information) and delegates
behavior to the flyweight.
Flyweight Pattern Example
Scenario: A program that needs to render a large number of trees in a forest.
○ Intrinsic State: Tree type, shape, texture, and color (shared across trees).
○ Extrinsic State: Tree position and size (unique per tree).
Without Flyweight: Each tree object would store all properties, leading to high memory
usage.
With Flyweight: The tree objects share common properties, and only the unique properties
(position and size) are stored separately.
Final Project
Ride
Sharing
App
Prateek Narang
Design a Ride Sharing App!
Ride Sharing App
You are tasked with designing and implementing a ride-sharing application
where passengers can request rides, and drivers can be matched to them
based on proximity. The application should handle different types of vehicles
(such as cars, bikes, luxury cars) and support multiple fare calculation
strategies. The system must notify both passengers and drivers about ride
statuses and calculate the fare based on the type of ride and distance traveled.
Let’s Refactor our Code
Coding Ride Sharing App with SOLID Principles
Requirements-I
Ride Request:
● Passengers can request a ride by providing their location and the desired destination.
● The system should calculate the distance between the passenger’s location and the driver’s location.
● The system must assign the nearest available driver to the passenger.
Vehicle Types:
● The system should support different vehicle types (e.g., car, bike, luxury car).
● Each vehicle type should have a different base fare per kilometer.
Fare Calculation:
● The system should use different fare strategies (e.g., standard fare, shared fare, luxury fare).
Requirements-II
Ride Status Notifications:
● Both the passenger and the driver should be notified of ride statuses (e.g., ride started, ride
completed).
● Use the Observer Pattern to notify users about ride status updates.
Ride Matching: