Fundamentals 20 min read

Java Design Patterns: Strategy, Factory, Singleton, Proxy, Observer, Template Method, Adapter and More

This article provides a comprehensive introduction to common Java design patterns—including Strategy, Factory, Singleton, Proxy, Observer, Template Method, and Adapter—explaining their concepts, typical use‑cases, and complete code implementations with Spring Boot integration for practical application development.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Java Design Patterns: Strategy, Factory, Singleton, Proxy, Observer, Template Method, Adapter and More

1️⃣ Design Pattern Overview

Design patterns are reusable solutions to recurring design problems; they can be applied in domain‑driven design as well as general software architecture.

2️⃣ Common Design Patterns

Strategy Pattern

Factory Pattern

Singleton Pattern

Proxy Pattern

Factory Method Pattern

Observer Pattern

Template Method Pattern

Adapter Pattern

3️⃣ Simple Implementation Templates

Scenario: a shopping mall offers discounts based on purchase amount (≥2000 → 0.8, 500‑1000 → 0.9, 0‑500 → 0.95). Different discount calculations are encapsulated as separate strategies.

3.1 Strategy Pattern

First, define a Strategy interface:

public interface Strategy {
    /**
     * Identify the strategy
     */
    String strategy();
    /**
     * Execute the algorithm
     */
    void algorithm();
}

Three concrete strategies implement this interface:

public class ConcreteStrategyA implements Strategy {
    @Override
    public String strategy() { return StrategySelector.strategyA.getStrategy(); }
    @Override
    public void algorithm() { System.out.println("process with strategyA..."); }
}

public class ConcreteStrategyB implements Strategy {
    @Override
    public String strategy() { return StrategySelector.strategyB.getStrategy(); }
    @Override
    public void algorithm() { System.out.println("process with strategyB..."); }
}

public class ConcreteStrategyC implements Strategy {
    @Override
    public String strategy() { return StrategySelector.strategyC.getStrategy(); }
    @Override
    public void algorithm() { System.out.println("process with strategyC..."); }
}

Enum StrategySelector maps strategy identifiers to class names:

@Getter
public enum StrategySelector {
    strategyA(1, "strategyA"),
    strategyB(2, "strategyB"),
    strategyC(3, "strategyC");
    private Integer code;
    private String strategy;
    StrategySelector(Integer code, String strategy) {
        this.code = code;
        this.strategy = strategy;
    }
}

A StrategyRunner holds a list of all strategies and a map for quick lookup:

public interface StrategyRunner {
    void execute(String strategy);
}

public class StrategyRunnerImpl implements StrategyRunner {
    private static final List
STRATEGIES = Arrays.asList(
        new ConcreteStrategyA(), new ConcreteStrategyB(), new ConcreteStrategyC());
    private static final Map
STRATEGY_MAP = new HashMap<>();
    static {
        STRATEGY_MAP = STRATEGIES.stream()
            .collect(Collectors.toMap(Strategy::strategy, s -> s));
    }
    @Override
    public void execute(String strategy) {
        STRATEGY_MAP.get(strategy).algorithm();
    }
}

Spring Boot can inject the runner automatically:

@RestController
@RequestMapping("/designPatterns")
public class DesignPatternController {
    @Autowired
    private StrategyRunner strategyRunner;
    @GetMapping("/algorithm")
    public void algorithm(@RequestParam("strategy") String strategy) {
        strategyRunner.execute(strategy);
    }
}

Calling the endpoint prints, for example, process with strategyA... .

3.2 Simple Factory Pattern

Use case: payment processing (Alipay, WeChat Pay, UnionPay). Define a common IPayment interface and concrete implementations:

public interface IPayment {
    Boolean pay(PaymentBody paymentBody);
}

public class AliPay implements IPayment { /* ... */ }
public class WechatPay implements IPayment { /* ... */ }
public class UnionPay implements IPayment { /* ... */ }

Factory class creates the appropriate strategy via reflection:

public class PaymentFactory {
    public static IPayment getPayStrategy(String type) {
        String value = EnumUtil.getFieldBy(PayStrategyEnum::getValue, PayStrategyEnum::getType, type);
        return ReflectUtil.newInstance(value);
    }
}

Enum PayStrategyEnum stores the mapping between type codes and class names.

3.3 Singleton Pattern

Lazy‑initialized static inner class implementation:

class Singleton {
    private Singleton() {}
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }
    public static synchronized Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

3.4 Proxy Pattern

Example: a landlord (subject) and a real‑estate agent (proxy). The proxy adds a fee before delegating to the landlord.

public interface Subject { void rentHouse(); }
public class HouseOwner implements Subject { public void rentHouse() { System.out.println("Owner rents house..."); } }
public class HouseProxy implements Subject {
    private HouseOwner houseOwner = new HouseOwner();
    @Override
    public void rentHouse() {
        System.out.println("Proxy charges fee and helps rent house...");
        houseOwner.rentHouse();
    }
}

3.5 Factory Method Pattern

Defines an interface NetworkConfigFactoryService to obtain a specific service implementation based on a product type, with concrete services AServiceImpl , BServiceImpl , etc.

3.6 Observer Pattern

Subject‑observer relationship for a weather‑station example. Interfaces Subject , Observer , and Display are defined, and WeatherData implements Subject to notify observers when measurements change.

class WeatherData implements Subject {
    private List
observers = new ArrayList<>();
    private float temperature, humidity, pressure;
    public void registerObserver(Observer o) { observers.add(o); }
    public void removeObserver(Observer o) { observers.remove(o); }
    public void notifyObserver() { for (Observer o : observers) o.update(temperature, humidity, pressure); }
    public void setMeasurements(float t, float h, float p) { this.temperature=t; this.humidity=h; this.pressure=p; measurementsChanged(); }
    private void measurementsChanged() { notifyObserver(); }
}

Concrete display classes implement Observer and Display to show current conditions.

3.7 Template Method Pattern

Abstract class HouseTemplate defines the final algorithm buildHouse() and abstract steps buildPillars() and buildWalls() . Concrete subclasses ( WoodenHouse , GlassHouse , ConcreteHouse ) provide specific implementations.

public abstract class HouseTemplate {
    public final void buildHouse() {
        buildFoundation();
        buildPillars();
        buildWalls();
        buildWindows();
        System.out.println("House is built successfully");
    }
    private void buildFoundation() { System.out.println("Building foundation with cement, iron rods and sand"); }
    public abstract void buildPillars();
    public abstract void buildWalls();
    private void buildWindows() { System.out.println("Building Glass Windows"); }
}

public class WoodenHouse extends HouseTemplate {
    @Override public void buildPillars() { System.out.println("Building Pillars With Wood coating..."); }
    @Override public void buildWalls() { System.out.println("Building Wooden Walls..."); }
}
// GlassHouse and ConcreteHouse are similar.

3.8 Adapter Pattern

Class adapter example: an Adaptee (network cable) provides request() . Target interface NetToUsb defines handleRequest() . Adapter class extends Adaptee and implements NetToUsb to bridge the gap.

public class Adaptee { public void request() { System.out.println("Connect cable to internet"); } }
public interface NetToUsb { void handleRequest(); }
public class Adapter extends Adaptee implements NetToUsb {
    @Override public void handleRequest() { super.request(); }
}
public class Computer {
    public void net(NetToUsb adapter) { adapter.handleRequest(); }
    public static void main(String[] args) {
        Computer computer = new Computer();
        Adapter adapter = new Adapter();
        computer.net(adapter);
    }
}

4️⃣ Summary

Design patterns represent best practices that experienced object‑oriented developers adopt to solve recurring software problems. Understanding these patterns and their implementations is essential for advanced developers.

Source: juejin.cn/post/7199549049787465765

design patternsJavasoftware architectureStrategy PatternFactory Pattern
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login 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.