8 Essential Design Patterns Every Java Developer Should Master
This article introduces eight core design patterns—Singleton, Factory, Strategy, Proxy, Observer, Decorator, Template Method, and Builder—explaining their concepts, providing Java code examples, and showing typical JDK and Spring framework usages to help developers write cleaner, more maintainable code.
Introduction
Design patterns are ubiquitous in software development, helping write more extensible and readable code.
This article presents eight commonly used design patterns with real‑world Java source examples and their typical usage in the JDK and Spring framework.
1. Singleton Pattern
The Singleton pattern ensures a class has only one instance, often used for shared resources such as configuration, cache, or thread pools.
Code implementation: double‑checked locking
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}JDK usage:
java.lang.Runtime.getRuntime() java.util.logging.LoggerSpring usage: Spring beans are singleton by default; use @Scope("prototype") to change scope.
2. Factory Pattern
The Factory pattern encapsulates object‑creation logic, reducing coupling when instantiation is complex.
Code implementation: simple factory for payment systems
public class PaymentFactory {
public static Payment createPayment(String type) {
switch (type) {
case "AliPay": return new AliPay();
case "WeChatPay": return new WeChatPay();
default: throw new IllegalArgumentException("Unknown payment type");
}
}
}JDK usage:
java.util.Calendar.getInstance() javax.xml.parsers.DocumentBuilderFactory.newInstance()Spring usage: BeanFactory and ApplicationContext are examples of the factory pattern.
3. Strategy Pattern
The Strategy pattern encapsulates algorithms in separate classes and selects them at runtime.
Code implementation: promotion strategies
public interface PromotionStrategy {
void applyPromotion();
}
public class DiscountStrategy implements PromotionStrategy {
@Override
public void applyPromotion() {
System.out.println("Applying discount...");
}
}
public class PromotionContext {
private PromotionStrategy strategy;
public PromotionContext(PromotionStrategy strategy) {
this.strategy = strategy;
}
public void executePromotion() {
strategy.applyPromotion();
}
}JDK usage: java.util.Comparator is a typical strategy.
Spring usage: Transaction management via TransactionManager uses strategy concepts.
4. Proxy Pattern
The Proxy pattern controls access to a target object, useful for permission checks, logging, etc.
Code implementation: static proxy
public interface Service {
void execute();
}
public class RealService implements Service {
@Override
public void execute() {
System.out.println("Executing real service...");
}
}
public class ServiceProxy implements Service {
private RealService realService;
@Override
public void execute() {
System.out.println("Checking permissions...");
if (realService == null) {
realService = new RealService();
}
realService.execute();
}
}JDK usage:
Dynamic proxy: java.lang.reflect.Proxy RMI (remote method invocation)
Spring usage: AOP extensively uses the proxy pattern.
5. Observer Pattern
The Observer pattern defines a one‑to‑many dependency so that when one object changes, all its dependents are notified.
Code implementation: event notification
public interface Observer {
void update(String message);
}
public class User implements Observer {
private String name;
public User(String name) { this.name = name; }
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
public class Weibo {
private List<Observer> observers = new ArrayList<>();
public void follow(Observer observer) { observers.add(observer); }
public void post(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}JDK usage: java.util.Observer and
java.util.Observable javax.swing.event.ChangeListenerSpring usage: ApplicationEvent and ApplicationListener implement the observer pattern.
6. Decorator Pattern
The Decorator pattern adds responsibilities to objects dynamically without modifying their structure.
Code implementation: coffee with add‑ons
public interface Coffee {
String getDescription();
double getCost();
}
public class SimpleCoffee implements Coffee {
@Override
public String getDescription() { return "Simple Coffee"; }
@Override
public double getCost() { return 5.0; }
}
public class MilkDecorator implements Coffee {
private Coffee coffee;
public MilkDecorator(Coffee coffee) { this.coffee = coffee; }
@Override
public String getDescription() { return coffee.getDescription() + ", Milk"; }
@Override
public double getCost() { return coffee.getCost() + 1.5; }
}JDK usage: java.io.BufferedInputStream and java.io.BufferedOutputStream act as decorators.
Spring usage: BeanPostProcessor can modify bean behavior at runtime.
7. Template Method Pattern
The Template Method defines the skeleton of an algorithm, leaving concrete steps to subclasses.
Code implementation: task execution template
public abstract class Task {
public final void execute() {
init();
doWork();
cleanup();
}
protected abstract void init();
protected abstract void doWork();
protected void cleanup() {
System.out.println("Default cleanup...");
}
}
public class DataProcessingTask extends Task {
@Override
protected void init() {
System.out.println("Initializing data...");
}
@Override
protected void doWork() {
System.out.println("Processing data...");
}
}JDK usage: java.util.AbstractList and java.util.AbstractMap follow this pattern.
Spring usage: JdbcTemplate and RestTemplate provide template implementations.
8. Builder Pattern
The Builder pattern constructs complex objects step by step, especially when many optional parameters exist.
Code implementation: HTTP request builder
public class HttpRequest {
private String method;
private String url;
private String body;
private HttpRequest(Builder builder) {
this.method = builder.method;
this.url = builder.url;
this.body = builder.body;
}
public static class Builder {
private String method;
private String url;
private String body;
public Builder method(String method) { this.method = method; return this; }
public Builder url(String url) { this.url = url; return this; }
public Builder body(String body) { this.body = body; return this; }
public HttpRequest build() { return new HttpRequest(this); }
}
}JDK usage: StringBuilder and Stream.Builder.
Spring usage: UriComponentsBuilder builds URIs.
Conclusion
These design patterns are widely applied in everyday development and are deeply reflected in both the JDK and Spring framework.
Understanding their essence and usage scenarios enables you to write more elegant and robust code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
