Java Design Patterns
Run each complete program separately. Its required helper classes and imports are included in the same code block. Short syntax fragments explain individual operations.
A design pattern names a useful way to arrange responsibilities when a design problem keeps recurring. For example, a caller may need to swap a behavior without editing its own control flow. Patterns help discuss such choices, but each choice still needs a reason in the actual application.
A design pattern is not a ready-made piece of code. Instead, it provides a proven approach for structuring classes and objects to solve a particular type of problem.
Design patterns help developers create software that is easier to:
- Understand
- Maintain
- Extend
- Test
- Reuse
What is a Design Pattern?
A design pattern is a general design solution that can be applied to a recurring problem in software development.
For example, suppose an application should have only one instance of a particular class.
Instead of designing the solution from scratch, we can use the Singleton pattern.
Similarly, if an object should be created without exposing complicated creation logic to the client, patterns such as Factory or Builder can be useful.
Why Do We Need Design Patterns?
As applications become larger, simply writing classes and methods is not enough.
We need to decide:
- Which class should create an object?
- Which class should be responsible for a particular operation?
- How should objects communicate?
- How can we avoid tightly coupled code?
- How can we make the code easier to extend?
Design patterns provide commonly recognized approaches to these problems.
Simple Example
Suppose we have:
class EmailService {
public void sendEmail() {
System.out.println("Sending email");
}
}Another class directly creates it:
class NotificationService {
private EmailService emailService =
new EmailService();
public void notifyUser() {
emailService.sendEmail();
}
}This creates a strong dependency between NotificationService and EmailService.
A better design may introduce an abstraction:
interface Notification {
void send();
}Now different implementations can be created:
class EmailNotification implements Notification {
public void send() {
System.out.println("Sending email");
}
}The exact pattern used depends on the problem we are trying to solve.
Categories of Design Patterns
Design patterns are commonly divided into three major categories:
Design Patterns
|
+------------+------------+
| | |
Creational Structural Behavioral1. Creational Patterns
Deal with object creation.
Examples:
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
2. Structural Patterns
Deal with how classes and objects are organized and combined.
Examples:
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
3. Behavioral Patterns
Deal with communication and responsibilities between objects.
Examples:
- Chain of Responsibility
- Command
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
1. Singleton Pattern
The Singleton pattern ensures that a class has only one instance and provides a way to access that instance.
Example
class DatabaseConnection {
private static DatabaseConnection instance;
private DatabaseConnection() {
}
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
}Usage:
class DatabaseConnection {
private static DatabaseConnection instance;
private DatabaseConnection() {
}
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
}
public class HelloWorld {
public static void main(String[] args) {
DatabaseConnection obj1 =
DatabaseConnection.getInstance();
DatabaseConnection obj2 =
DatabaseConnection.getInstance();
System.out.println(obj1 == obj2);
}
}Output
trueBoth variables refer to the same object.
Important Point
Singleton implementations require careful consideration in multithreaded applications. A simple lazy implementation like the above is not thread-safe.
2. Factory Method Pattern
The Factory Method pattern puts an object-creation method in a parent type and lets subclasses choose the concrete product. The static selection example below is a Simple Factory, a related creation technique rather than the subclass-based Factory Method pattern.
Example
interface Vehicle {
void drive();
}Implementations:
class Car implements Vehicle {
public void drive() {
System.out.println("Driving a car");
}
}class Bike implements Vehicle {
public void drive() {
System.out.println("Riding a bike");
}
}Factory:
class VehicleFactory {
public static Vehicle createVehicle(String type) {
if (type.equalsIgnoreCase("car")) {
return new Car();
}
if (type.equalsIgnoreCase("bike")) {
return new Bike();
}
throw new IllegalArgumentException(
"Unknown vehicle type"
);
}
}Usage:
class Bike implements Vehicle {
public void drive() {
System.out.println("Riding a bike");
}
}
class Car implements Vehicle {
public void drive() {
System.out.println("Driving a car");
}
}
class VehicleFactory {
public static Vehicle createVehicle(String type) {
if (type.equalsIgnoreCase("car")) {
return new Car();
}
if (type.equalsIgnoreCase("bike")) {
return new Bike();
}
throw new IllegalArgumentException(
"Unknown vehicle type"
);
}
}
interface Vehicle {
void drive();
}
public class HelloWorld {
public static void main(String[] args) {
Vehicle vehicle =
VehicleFactory.createVehicle("car");
vehicle.drive();
}
}Output
Driving a carThe client does not directly create the Car object.
Factory Method Through a Subclass
Here ReportCreator defines the workflow and calls createReport(). DailyReportCreator chooses the report implementation by overriding that method. Each block is a separate example; it does not replace the Simple Factory shown above.
interface Report {
String title();
}
class DailyReport implements Report {
public String title() {
return "Daily reading summary";
}
}
abstract class ReportCreator {
abstract Report createReport();
void printTitle() {
System.out.println(createReport().title());
}
}
class DailyReportCreator extends ReportCreator {
@Override
Report createReport() {
return new DailyReport();
}
}
public class HelloWorld {
public static void main(String[] args) {
ReportCreator creator = new DailyReportCreator();
creator.printTitle();
}
}Output:
Daily reading summary3. Abstract Factory Pattern
The Abstract Factory pattern provides an interface for creating families of related objects without specifying their concrete classes.
For example, suppose an application supports different UI themes:
Dark Theme
├── DarkButton
└── DarkCheckbox
Light Theme
├── LightButton
└── LightCheckboxThe factory can create a matching set of objects.
This pattern is useful when objects need to work together as a family.
4. Builder Pattern
The Builder pattern is useful for creating complex objects step by step.
Suppose a Student object has many optional properties.
class Student {
private String name;
private int age;
private String email;
private Student(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.email = builder.email;
}
static class Builder {
private String name;
private int age;
private String email;
Builder setName(String name) {
this.name = name;
return this;
}
Builder setAge(int age) {
this.age = age;
return this;
}
Builder setEmail(String email) {
this.email = email;
return this;
}
Student build() {
return new Student(this);
}
}
}Usage:
Student student =
new Student.Builder()
.setName("Rahul")
.setAge(20)
.setEmail("rahul@example.com")
.build();The builder makes object construction easier to read.
5. Prototype Pattern
The Prototype pattern creates new objects by copying an existing object.
Conceptually:
Existing Object
|
| copy
↓
New ObjectA common Java approach is to provide a copy constructor or another explicit copying mechanism.
Example
class Student {
private String name;
Student(String name) {
this.name = name;
}
Student(Student other) {
this.name = other.name;
}
}Usage:
Student original =
new Student("Rahul");
Student copy =
new Student(original);Structural Design Patterns
Structural patterns focus on relationships between classes and objects.
6. Adapter Pattern
The Adapter pattern allows incompatible interfaces to work together.
For example:
Client
↓
Adapter
↓
Existing ClassExample
interface PaymentProcessor {
void pay();
}Existing class:
class OldPaymentSystem {
public void makePayment() {
System.out.println(
"Payment completed"
);
}
}Adapter:
class PaymentAdapter
implements PaymentProcessor {
private OldPaymentSystem oldSystem;
PaymentAdapter(OldPaymentSystem oldSystem) {
this.oldSystem = oldSystem;
}
public void pay() {
oldSystem.makePayment();
}
}Usage:
PaymentProcessor processor =
new PaymentAdapter(
new OldPaymentSystem()
);
processor.pay();Output
Payment completedThe adapter allows the old system to work with the new interface.
7. Decorator Pattern
The Decorator pattern adds additional behavior to an object without modifying its original class.
Example
Basic Coffee
↓
Coffee + Milk
↓
Coffee + Milk + SugarJava example:
interface Coffee {
String getDescription();
}Basic implementation:
class BasicCoffee implements Coffee {
public String getDescription() {
return "Coffee";
}
}Decorator:
class MilkDecorator implements Coffee {
private Coffee coffee;
MilkDecorator(Coffee coffee) {
this.coffee = coffee;
}
public String getDescription() {
return coffee.getDescription()
+ " + Milk";
}
}Usage:
Coffee coffee =
new MilkDecorator(
new BasicCoffee()
);
System.out.println(
coffee.getDescription()
);Output
Coffee + Milk8. Facade Pattern
The Facade pattern provides a simple interface over a complex subsystem.
Suppose an order process requires:
Inventory
Payment
Shipping
NotificationInstead of the client interacting with all these systems, we can create a facade.
class OrderFacade {
public void placeOrder() {
System.out.println("Checking inventory");
System.out.println("Processing payment");
System.out.println("Creating shipment");
System.out.println("Sending notification");
}
}Client:
OrderFacade facade =
new OrderFacade();
facade.placeOrder();Output
Checking inventory
Processing payment
Creating shipment
Sending notificationThe facade hides the subsystem complexity from the client.
9. Proxy Pattern
The Proxy pattern provides a substitute or representative object that controls access to another object.
Client
↓
Proxy
↓
Real ObjectA proxy can be used for:
- Access control
- Lazy initialization
- Logging
- Caching
- Remote communication
Example
interface Image {
void display();
}Real object:
class RealImage implements Image {
public RealImage() {
System.out.println("Loading image");
}
public void display() {
System.out.println("Displaying image");
}
}Proxy:
class ImageProxy implements Image {
private RealImage image;
public void display() {
if (image == null) {
image = new RealImage();
}
image.display();
}
}The proxy delays creation of the real object until it is actually needed.
Behavioral Design Patterns
Behavioral patterns focus on communication and responsibilities between objects.
10. Strategy Pattern
The Strategy pattern allows different algorithms or behaviors to be selected at runtime.
For example, a payment system may support:
Credit Card
UPI
Bank TransferDefine an interface:
interface PaymentStrategy {
void pay(double amount);
}Implementation:
class CardPayment implements PaymentStrategy {
public void pay(double amount) {
System.out.println(
"Paid using card: " + amount
);
}
}Another implementation:
class UpiPayment implements PaymentStrategy {
public void pay(double amount) {
System.out.println(
"Paid using UPI: " + amount
);
}
}Context:
class PaymentService {
private PaymentStrategy strategy;
PaymentService(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void makePayment(double amount) {
strategy.pay(amount);
}
}Usage:
PaymentService service =
new PaymentService(
new UpiPayment()
);
service.makePayment(500);Output
Paid using UPI: 500.0The payment behavior can be changed without changing PaymentService.
11. Observer Pattern
The Observer pattern establishes a relationship where multiple objects are notified when the state of another object changes.
Example
Subject
|
+---- Observer 1
|
+---- Observer 2
|
+---- Observer 3A common real-world concept is notification.
For example, when an event occurs, multiple subscribers may need to receive an update.
12. Command Pattern
The Command pattern encapsulates an operation as an object.
Instead of directly performing an operation:
light.turnOn();we can represent the action as a command.
Conceptually:
Command
↓
execute()
↓
ReceiverThis can be useful for:
- Undo/redo
- Queuing operations
- Logging actions
- Scheduling operations
13. Iterator Pattern
The Iterator pattern provides a standard way to traverse elements of a collection without exposing its internal structure.
Java collections already provide iterator support.
Example
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
List<String> names =
Arrays.asList(
"Rahul",
"Priya",
"Amit"
);
Iterator<String> iterator =
names.iterator();
while (iterator.hasNext()) {
System.out.println(
iterator.next()
);
}
}
}Output
Rahul
Priya
Amit14. Template Method Pattern
The Template Method pattern defines the overall structure of an algorithm while allowing subclasses to customize particular steps.
Example
abstract class DataProcessor {
public final void process() {
readData();
processData();
saveData();
}
abstract void readData();
abstract void processData();
void saveData() {
System.out.println("Saving data");
}
}The algorithm remains:
readData()
↓
processData()
↓
saveData()Subclasses can provide their own implementations of the required steps.
15. Chain of Responsibility Pattern
The Chain of Responsibility pattern passes a request through a sequence of handlers.
Request
↓
Handler 1
↓
Handler 2
↓
Handler 3Each handler can:
- Process the request
- Pass it to the next handler
This pattern is useful when multiple objects may be capable of handling a request.
16. State Pattern
The State pattern allows an object to change its behavior when its internal state changes.
For example, an order might have states:
NEW
↓
CONFIRMED
↓
SHIPPED
↓
DELIVEREDThe behavior of the order can depend on its current state.
Design Pattern Summary
| Category | Pattern | Main Purpose |
|---|---|---|
| Creational | Singleton | One shared instance |
| Creational | Factory Method | Encapsulate object creation |
| Creational | Abstract Factory | Create related object families |
| Creational | Builder | Build complex objects step by step |
| Creational | Prototype | Create objects by copying |
| Structural | Adapter | Make incompatible interfaces work together |
| Structural | Bridge | Separate abstraction from implementation |
| Structural | Composite | Treat individual and group objects uniformly |
| Structural | Decorator | Add behavior dynamically |
| Structural | Facade | Simplify a complex subsystem |
| Structural | Flyweight | Share reusable object state |
| Structural | Proxy | Control access to another object |
| Behavioral | Chain of Responsibility | Pass requests through handlers |
| Behavioral | Command | Encapsulate an operation |
| Behavioral | Iterator | Traverse a collection |
| Behavioral | Mediator | Centralize object communication |
| Behavioral | Memento | Save and restore object state |
| Behavioral | Observer | Notify dependent objects |
| Behavioral | State | Change behavior based on state |
| Behavioral | Strategy | Select an algorithm/behavior |
| Behavioral | Template Method | Define an algorithm skeleton |
| Behavioral | Visitor | Add operations without changing object structures |
Design Patterns and SOLID Principles
Design patterns and SOLID principles are related, but they are not the same thing.
SOLID
SOLID provides general principles for designing maintainable object-oriented software.
Examples:
S → Single Responsibility Principle
O → Open/Closed Principle
L → Liskov Substitution Principle
I → Interface Segregation Principle
D → Dependency Inversion PrincipleDesign Patterns
Design patterns provide reusable approaches to particular recurring design problems.
For example:
SOLID Principles
↓
Good Object-Oriented Design
↓
Design Patterns
↓
Reusable Design SolutionsDesign Patterns and Interfaces
Many Java design patterns make heavy use of:
- Interfaces
- Abstract classes
- Inheritance
- Composition
- Polymorphism
- Encapsulation
For example, the Strategy pattern uses an interface:
interface PaymentStrategy {
void pay(double amount);
}Different classes can implement the interface:
class CardPayment implements PaymentStrategy {
// ...
}
class UpiPayment implements PaymentStrategy {
// ...
}This allows the application to work with the abstraction rather than a specific implementation.
Composition Over Inheritance
Many design patterns favor composition over unnecessary inheritance.
For example:
class PaymentService {
private PaymentStrategy strategy;
PaymentService(PaymentStrategy strategy) {
this.strategy = strategy;
}
}Here, PaymentService contains a PaymentStrategy rather than inheriting from it.
This allows the strategy to be replaced easily.
Advantages of Design Patterns
1. Reusable Solutions
Patterns provide established approaches to recurring design problems.
2. Better Maintainability
Well-structured code can be easier to understand and modify.
3. Loose Coupling
Many patterns help reduce unnecessary dependencies between classes.
4. Better Communication
Developers can communicate using common terms.
For example:
"Use a Strategy here."
A developer familiar with the pattern immediately understands the general design idea.
5. Extensibility
Some patterns make it easier to add new behavior without modifying existing code extensively.
Disadvantages of Design Patterns
Design patterns are useful, but they should not be applied unnecessarily.
1. Increased Complexity
A simple problem can become unnecessarily complicated if too many abstractions are introduced.
2. More Classes
Some patterns require additional interfaces, classes, or objects.
3. Overengineering
Using a pattern simply because it is available can make code harder to understand.
4. Learning Curve
Developers need to understand the problem and the pattern before applying it effectively.
Common Mistakes
1. Using a Pattern Everywhere
Not every problem requires a design pattern.
First understand the problem, then determine whether a pattern provides a useful solution.
2. Confusing Design Pattern with Framework
A design pattern is a reusable design approach.
A framework is a software platform that provides reusable infrastructure and structure for application development.
They are different concepts.
3. Memorizing Patterns Without Understanding the Problem
Knowing the names of patterns is less useful than understanding:
Problem
↓
Why does it occur?
↓
What design can solve it?
↓
Which pattern fits?4. Treating Patterns as Fixed Code
A pattern is not a code template that must always be copied exactly.
The implementation can vary depending on the application's requirements.
How to Learn Java Design Patterns
A good learning approach is:
OOP Concepts
↓
SOLID Principles
↓
Design Principles
↓
Creational Patterns
↓
Structural Patterns
↓
Behavioral Patterns
↓
Real-World ApplicationsBefore learning design patterns, it is useful to understand:
- Classes and objects
- Encapsulation
- Inheritance
- Polymorphism
- Interfaces
- Abstract classes
- Composition
- Association
- Aggregation
- Exception handling
- Generics
Important Design Patterns to Learn First
For Java developers, commonly encountered patterns include:
Singleton
Factory
Builder
Adapter
Decorator
Facade
Proxy
Strategy
Observer
Command
Template Method
Chain of ResponsibilityThe order in which you learn them is less important than understanding the problem each pattern addresses.
Quick Revision
Design Pattern
↓
Reusable design approach
↓
Recurring software design problemThree major categories:
Creational
↓
Object creation
Structural
↓
Class/Object structure
Behavioral
↓
Object communication and behaviorExamples:
Creational
→ Singleton
→ Factory
→ Builder
→ Prototype
Structural
→ Adapter
→ Decorator
→ Facade
→ Proxy
Behavioral
→ Strategy
→ Observer
→ Command
→ Iterator
→ State
→ Template MethodKey Points
- A design pattern is a reusable approach to a recurring software design problem.
- A design pattern is not a ready-made piece of code.
- Design patterns are commonly categorized as Creational, Structural, and Behavioral.
- Creational patterns focus on object creation.
- Structural patterns focus on relationships and object/class composition.
- Behavioral patterns focus on communication and responsibilities.
- Singleton deals with controlled single-instance creation.
- Factory patterns encapsulate object creation.
- Builder helps construct complex objects.
- Adapter connects incompatible interfaces.
- Decorator adds behavior without modifying the original class.
- Facade simplifies access to complex subsystems.
- Proxy controls access to another object.
- Strategy allows behavior or algorithms to be changed.
- Observer supports notification between objects.
- Command represents an operation as an object.
- Iterator provides a way to traverse collections.
- Design patterns should solve real design problems rather than being added unnecessarily.
Conclusion
Java design patterns provide commonly used approaches for solving recurring software design problems.
The three major categories are:
Creational → Object Creation
Structural → Object/Class Structure
Behavioral → Object CommunicationThe most important lesson is not to memorize every pattern. Instead, understand the problem a pattern solves, how it works, and when it is appropriate to use it.
Good Java design combines OOP concepts, SOLID principles, composition, interfaces, and appropriate design patterns to create software that is easier to maintain and extend.
See you in next page....🙂