Design Patterns in Java
Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
Why Patterns Matter
From project skeleton to project design
In Project Setup and Version Control, you created a clean baseline: a runnable Java project, a Git working loop, and repo hygiene. Now we begin shaping code so it can survive real project change.
Design patterns are not magic templates. They are shared names for proven object-oriented solutions.
In real-time Java projects, patterns help you answer questions like:
- Where should object creation live?
- How do we swap behavior without rewriting business logic?
- How do modules communicate without becoming tightly coupled?
- How do we keep classes small enough to test and evolve?
We will focus on patterns you will reuse in later projects, especially before we build services, persistence layers, chat features, and e-commerce flows.

Pattern Thinking
Patterns solve recurring pressure
A design pattern usually appears when code starts feeling one of these pressures:
Pressure Typical symptom Helpful pattern family Too manyif/else branches
Logic changes often
Behavioral patterns
Object creation is scattered
Constructors everywhere
Creational patterns
Classes know too much
Hard to replace pieces
Structural patterns
Modules need notifications
Direct calls multiply
Behavioral patterns
Three useful families
- Creational: how objects are created. Example: Factory Method.
- Structural: how classes and objects are composed. Example: Adapter, Facade.
- Behavioral: how objects communicate and delegate behavior. Example: Strategy, Observer.
Real-project rule
Use a pattern when it makes code simpler under change. Do not add a pattern just to sound architectural.

Strategy Pattern
Swap behavior without rewriting the caller
The Strategy pattern lets you define a family of algorithms, place each one in its own class, and make them interchangeable.
This is useful when a project has behavior that varies:
- discount calculation
- shipping cost calculation
- authentication method
- ranking or scoring logic
- notification channel selection
Instead of writing one large method full of conditional branches, the caller depends on a small interface.
Mental model
CheckoutService ---> DiscountStrategy
/ | \
NoDiscount Festival Member
The service does not care which discount is used. It only calls apply.
```java
public class Main {
interface DiscountStrategy {
double apply(double subtotal);
}
static class NoDiscount implements DiscountStrategy {
public double apply(double subtotal) {
return subtotal;
}
}
static class FestivalDiscount implements DiscountStrategy {
public double apply(double subtotal) {
return subtotal * 0.80;
}
}
static class CheckoutService {
private final DiscountStrategy discountStrategy;
CheckoutService(DiscountStrategy discountStrategy) {
this.discountStrategy = discountStrategy;
}
double finalAmount(double subtotal) {
return discountStrategy.apply(subtotal);
}
}
public static void main(String[] args) {
CheckoutService normalCheckout = new CheckoutService(new NoDiscount());
CheckoutService festivalCheckout = new CheckoutService(new FestivalDiscount());
System.out.println("Normal: " + normalCheckout.finalAmount(1000));
System.out.println("Festival: " + festivalCheckout.finalAmount(1000));
}
}
```
Choose The Pattern
A checkout module needs to support multiple discount algorithms. The selected algorithm may change depending on campaign, user type, or region.
Which pattern best fits this need?
Factory Method
Move creation decisions out of business logic
The Factory Method pattern centralizes object creation behind a method or class.
This helps when code knows what kind of object it needs, but should not know all construction details.
Common real-project examples:
- create a report exporter based on
pdf,csv, orjson - create a notification sender based on channel
- create a parser based on file type
- create a payment handler based on provider
The goal is not to hide every constructor. The goal is to prevent object creation rules from leaking across the project.
```java
public class Main {
interface ReportExporter {
String export(String reportTitle);
}
static class PdfExporter implements ReportExporter {
public String export(String reportTitle) {
return "PDF export created for: " + reportTitle;
}
}
static class CsvExporter implements ReportExporter {
public String export(String reportTitle) {
return "CSV export created for: " + reportTitle;
}
}
static class ExporterFactory {
static ReportExporter create(String type) {
if ("pdf".equalsIgnoreCase(type)) {
return new PdfExporter();
}
if ("csv".equalsIgnoreCase(type)) {
return new CsvExporter();
}
throw new IllegalArgumentException("Unsupported export type: " + type);
}
}
public static void main(String[] args) {
ReportExporter exporter = ExporterFactory.create("pdf");
System.out.println(exporter.export("Monthly Sales"));
ReportExporter csvExporter = ExporterFactory.create("csv");
System.out.println(csvExporter.export("Inventory"));
}
}
```
Factory Check
True or false: A factory is most useful when object creation rules are starting to spread across many unrelated classes.
Singleton Caution
One instance is not always good design
The Singleton pattern ensures a class has only one instance and provides a global access point.
It can be useful for truly shared, process-wide resources, such as:
- application configuration loaded once
- a registry of immutable metadata
- a shared clock abstraction in a small app
But in real Java projects, Singleton is often overused.
Why be careful?
Singletons can introduce hidden dependencies:
Class A ---> GlobalConfig.getInstance()
Class B ---> GlobalConfig.getInstance()
Class C ---> GlobalConfig.getInstance()
This makes testing harder because classes silently reach into global state.
Better project habit
Prefer passing dependencies explicitly through constructors. This keeps classes easier to test, replace, and reason about. Later, frameworks can automate dependency wiring, but the design principle is the same: dependencies should be visible.

Name The Pattern
A payment module chooses between CreditCardPayment, WalletPayment, and BankTransferPayment at runtime. Each option implements the same payment behavior interface.
The best pattern name is: _____
Observer Pattern
Notify interested modules without hardwiring them
The Observer pattern lets one object publish an event while other objects subscribe to react.
This is useful when one action should trigger multiple side effects:
- order placed → send email, update inventory, create audit log
- user registered → send welcome message, create profile, notify analytics
- file uploaded → scan file, generate thumbnail, log activity
The publisher should not need to know every concrete class that reacts. It only notifies listeners through an interface.
This style will become very useful when we later build real-time and service-oriented features.
```java
import java.util.ArrayList;
import java.util.List;
public class Main {
static class Order {
final String id;
final double total;
Order(String id, double total) {
this.id = id;
this.total = total;
}
}
interface OrderListener {
void onOrderPlaced(Order order);
}
static class EmailNotifier implements OrderListener {
public void onOrderPlaced(Order order) {
System.out.println("Email sent for order " + order.id);
}
}
static class InventoryUpdater implements OrderListener {
public void onOrderPlaced(Order order) {
System.out.println("Inventory reserved for order " + order.id);
}
}
static class OrderService {
private final List<OrderListener> listeners = new ArrayList<>();
void addListener(OrderListener listener) {
listeners.add(listener);
}
void placeOrder(Order order) {
System.out.println("Order placed: " + order.id + ", total=" + order.total);
for (OrderListener listener : listeners) {
listener.onOrderPlaced(order);
}
}
}
public static void main(String[] args) {
OrderService orderService = new OrderService();
orderService.addListener(new EmailNotifier());
orderService.addListener(new InventoryUpdater());
orderService.placeOrder(new Order("ORD-101", 2499.0));
}
}
```
Project Pattern Map
How to use patterns in your course projects
As you continue through this Java project course, treat patterns as design tools, not decorations.
Practical selection guide
If you notice... Consider... many interchangeable algorithms Strategy object creation logic repeated Factory Method global instance temptation explicit dependencies before Singleton one action causing many reactions ObserverA real-project workflow
- Start with simple code.
- Notice repeated change pressure.
- Extract an interface around the variation.
- Move implementations into focused classes.
- Keep the caller dependent on abstractions.
- Commit the refactor cleanly using the Git habits from Project Setup and Version Control.
Next, when we build a RESTful web service, these patterns will help keep controllers, services, and supporting classes from collapsing into one large procedural block.
