Fundamentals 15 min read

Master the 7 Most Used Java Design Patterns with Real-World Code

This article explores the seven most commonly used design patterns in Java development—Singleton, Factory, Strategy, Observer, Decorator, Adapter, and Template Method—detailing their definitions, typical use cases, and providing fully commented code implementations that illustrate how each pattern can be applied in real projects.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Master the 7 Most Used Java Design Patterns with Real-World Code

Java's Seven Most Common Design Patterns: Scenarios and Code

Design patterns are proven best‑practice solutions that improve code maintainability, extensibility, and readability. The following sections introduce the seven most frequently used patterns in Java with real‑world scenarios and annotated code examples.

1. Singleton Pattern

Definition: Ensures a class has only one instance and provides a global access point.

Applicable Scenarios:

Global unique resources such as thread pools, caches, or logger objects.

Situations where memory savings are needed by creating only a single instance.

Implementation Example: Enum Singleton (thread‑safe, resistant to reflection)

/**
 * Enum singleton (recommended)
 * Advantages: thread‑safe, prevents reflection attacks, concise code
 */
public enum Singleton {
    INSTANCE; // unique instance

    // Example method: simulate fetching configuration
    public String getConfig() {
        return "config from singleton";
    }
}

// Usage example
public class Client {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance1 == instance2); // prints true
    }
}

2. Factory Pattern

Definition: Encapsulates object‑creation logic in a factory class so the client does not need to know concrete implementations.

Applicable Scenarios:

Complex object creation (e.g., parameter validation, initialization steps).

Decoupling object creation from usage.

Implementation Example: Simple Logger Factory

// Logger interface
interface Logger {
    void log(String message);
}

// Console logger implementation
class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("Console: " + message);
    }
}

// File logger implementation
class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("File: " + message);
    }
}

// Factory class
class LoggerFactory {
    // Create logger based on type
    public static Logger createLogger(String type) {
        if ("console".equalsIgnoreCase(type)) {
            return new ConsoleLogger();
        } else if ("file".equalsIgnoreCase(type)) {
            return new FileLogger();
        } else {
            throw new IllegalArgumentException("Unsupported logger type: " + type);
        }
    }
}

// Usage example
public class Client {
    public static void main(String[] args) {
        Logger consoleLogger = LoggerFactory.createLogger("console");
        consoleLogger.log("Hello, World!"); // prints: Console: Hello, World!
    }
}

3. Strategy Pattern

Definition: Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

Applicable Scenarios:

Dynamic algorithm switching (e.g., payment methods, sorting strategies).

Avoiding multiple conditional branches by encapsulating logic into strategy classes.

Implementation Example: Payment Strategies (Alipay / WeChat)

// Strategy interface
interface PaymentStrategy {
    void pay(double amount);
}

// Alipay strategy
class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Using Alipay to pay: " + amount + " yuan");
    }
}

// WeChat Pay strategy
class WechatPayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("Using WeChat Pay to pay: " + amount + " yuan");
    }
}

// Context class managing the strategy
class PaymentContext {
    private PaymentStrategy strategy;

    public PaymentContext(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(PaymentStrategy strategy) {
        this.strategy = strategy;
    }

    public void executePayment(double amount) {
        strategy.pay(amount);
    }
}

// Usage example
public class Client {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext(new AlipayStrategy());
        context.executePayment(199.9); // Alipay output
        context.setStrategy(new WechatPayStrategy());
        context.executePayment(88.8); // WeChat Pay output
    }
}

4. Observer Pattern

Definition: Defines a one‑to‑many dependency so that when one object changes state, all its dependents are automatically notified.

Applicable Scenarios:

Message subscription/publishing systems (event bus, notification services).

Monitoring state changes (stock price updates, weather alerts).

Implementation Example: Weather Update Observer

// Subject interface (observable)
interface WeatherSubject {
    void addObserver(WeatherObserver observer);
    void removeObserver(WeatherObserver observer);
    void notifyObservers();
    void setTemperature(double temperature);
}

// Observer interface
interface WeatherObserver {
    void update(double temperature);
}

// Concrete subject
class WeatherDataCenter implements WeatherSubject {
    private double temperature;
    private final List<WeatherObserver> observers = new ArrayList<>();

    @Override
    public void addObserver(WeatherObserver observer) { observers.add(observer); }
    @Override
    public void removeObserver(WeatherObserver observer) { observers.remove(observer); }
    @Override
    public void notifyObservers() {
        for (WeatherObserver observer : observers) {
            observer.update(temperature);
        }
    }
    @Override
    public void setTemperature(double temperature) {
        this.temperature = temperature;
        notifyObservers();
    }
}

// Concrete observer (mobile app)
class MobileAppObserver implements WeatherObserver {
    private final String name;
    public MobileAppObserver(String name) { this.name = name; }
    @Override
    public void update(double temperature) {
        System.out.println(name + " received notification: current temperature is " + temperature + "℃");
    }
}

// Usage example
public class Client {
    public static void main(String[] args) {
        WeatherSubject subject = new WeatherDataCenter();
        WeatherObserver app1 = new MobileAppObserver("WeatherPro");
        WeatherObserver app2 = new MobileAppObserver("CloudyApp");
        subject.addObserver(app1);
        subject.addObserver(app2);
        subject.setTemperature(25.5);
    }
}

5. Decorator Pattern

Definition: Dynamically adds responsibilities to an object without affecting other objects of the same class.

Applicable Scenarios:

Extending object functionality (e.g., I/O stream buffering, encryption decorators).

Avoiding class explosion caused by deep inheritance hierarchies.

Implementation Example: Coffee Beverage Decorators

// Component interface
interface Beverage {
    String getDescription();
    double cost();
}

// Concrete component
class BasicCoffee implements Beverage {
    @Override
    public String getDescription() { return "Basic Coffee"; }
    @Override
    public double cost() { return 15.0; }
}

// Abstract decorator
abstract class BeverageDecorator implements Beverage {
    protected final Beverage beverage;
    public BeverageDecorator(Beverage beverage) { this.beverage = beverage; }
    @Override
    public abstract String getDescription();
    @Override
    public abstract double cost();
}

// Milk decorator
class MilkDecorator extends BeverageDecorator {
    public MilkDecorator(Beverage beverage) { super(beverage); }
    @Override
    public String getDescription() { return beverage.getDescription() + " + Milk"; }
    @Override
    public double cost() { return beverage.cost() + 3.0; }
}

// Syrup decorator
class SyrupDecorator extends BeverageDecorator {
    public SyrupDecorator(Beverage beverage) { super(beverage); }
    @Override
    public String getDescription() { return beverage.getDescription() + " + Syrup"; }
    @Override
    public double cost() { return beverage.cost() + 2.0; }
}

// Usage example
public class Client {
    public static void main(String[] args) {
        Beverage coffee = new BasicCoffee();
        coffee = new MilkDecorator(coffee);
        coffee = new SyrupDecorator(coffee);
        System.out.println("Beverage: " + coffee.getDescription()); // Basic Coffee + Milk + Syrup
        System.out.println("Price: " + coffee.cost() + "元"); // 20.0元
    }
}

6. Adapter Pattern

Definition: Converts the interface of a class into another interface clients expect, allowing incompatible interfaces to work together.

Applicable Scenarios:

Adapting legacy system interfaces to new requirements.

When a class cannot be modified but its interface does not match client expectations.

Implementation Example: Voltage Adapter (220V → 5V)

// Target interface (5V needed by phone)
interface FiveVolt {
    int getVolt5();
}

// Adaptee (provides 220V)
class AC220Volt {
    public int getVolt220() { return 220; }
}

// Adapter class
class VoltageAdapter extends AC220Volt implements FiveVolt {
    @Override
    public int getVolt5() {
        int volt220 = getVolt220();
        // Simulate step‑down logic (real hardware needed)
        return volt220 / 44; // 220V → 5V
    }
}

// Usage example
public class Client {
    public static void main(String[] args) {
        FiveVolt charger = new VoltageAdapter();
        System.out.println("Phone charging voltage: " + charger.getVolt5() + "V"); // prints 5V
    }
}

7. Template Method Pattern

Definition: Defines the skeleton of an algorithm in a method, deferring some steps to subclasses.

Applicable Scenarios:

Multiple subclasses share common workflow (e.g., file processing, approval processes).

Eliminating code duplication by centralizing invariant parts of an algorithm.

Implementation Example: File Processor with CSV Subclass

// Abstract template class
abstract class FileProcessor {
    // Template method: fixed processing flow
    public final void processFile(String path) {
        String content = readFile(path); // subclass implements
        parseContent(content);          // subclass implements
        saveToDatabase(content);        // common logic
    }
    protected abstract String readFile(String path);
    protected abstract void parseContent(String content);
    protected void saveToDatabase(String content) {
        System.out.println("Saving content to DB: " + content);
    }
}

// Concrete CSV processor
class CSVProcessor extends FileProcessor {
    @Override
    protected String readFile(String path) {
        return "Simulated CSV content: Name,Age
Alice,30";
    }
    @Override
    protected void parseContent(String content) {
        System.out.println("Parsing CSV: " + content);
    }
    @Override
    protected void saveToDatabase(String content) {
        System.out.println("Special handling: store CSV into dedicated table");
    }
}

// Usage example
public class Client {
    public static void main(String[] args) {
        FileProcessor processor = new CSVProcessor();
        processor.processFile("data.csv");
    }
}

Summary: Core Ideas Behind Design Patterns

Single Responsibility Principle: Each class should have only one responsibility (e.g., a factory class only creates objects).

Open‑Closed Principle: Software entities should be open for extension but closed for modification (e.g., adding new strategies without changing existing code).

Composition Over Inheritance: Prefer composition/aggregation to inheritance for extending behavior (e.g., decorator pattern adds functionality by holding a reference).

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Software ArchitectureCode Examplesobject‑oriented programming
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.