Fundamentals 25 min read

Unlock Cleaner Code: Master 6 Essential Design Patterns for Java Projects

This article introduces six classic design patterns—Strategy, Chain of Responsibility, Template Method, Observer, Factory, and Singleton—explaining their real‑world business scenarios, core principles, and providing clean Java implementations to help developers write more maintainable and extensible code.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Unlock Cleaner Code: Master 6 Essential Design Patterns for Java Projects

Introduction

Hello, I am Su San . In daily development we often write straightforward pipeline code, but to make coding enjoyable we can use design patterns to optimize business logic. Below are the design patterns I frequently use.

Design Patterns Overview
Design Patterns Overview

1. Strategy Pattern

1.1 Business Scenario

When a big‑data system pushes files of different types, each type requires a different parsing logic. A naïve implementation uses multiple if...else branches, which quickly becomes bloated and violates the Open/Closed and Single Responsibility principles.

if(type == "A") {
    // parse A format
} else if(type == "B") {
    // parse B format
} else {
    // default parsing
}

1.2 Definition

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable, allowing the algorithm to vary independently from the clients that use it.

Imagine dating different personality types: you need different strategies—movie, snack, shopping—to win their hearts.

1.3 Usage

An interface or abstract class with two methods: one to identify the file type, another to perform the parsing.

Concrete strategy classes implement the interface.

A service loads all strategies into a map (using Spring) and selects the appropriate one at runtime.

public interface IFileStrategy {
    FileTypeResolveEnum gainFileType();
    void resolve(Object objectParam);
}

@Component
public class AFileResolve implements IFileStrategy {
    @Override
    public FileTypeResolveEnum gainFileType() { return FileTypeResolveEnum.File_A_RESOLVE; }
    @Override
    public void resolve(Object objectParam) { /* A‑type logic */ }
}

@Component
public class BFileResolve implements IFileStrategy {
    @Override
    public FileTypeResolveEnum gainFileType() { return FileTypeResolveEnum.File_B_RESOLVE; }
    @Override
    public void resolve(Object objectParam) { /* B‑type logic */ }
}

@Component
public class DefaultFileResolve implements IFileStrategy {
    @Override
    public FileTypeResolveEnum gainFileType() { return FileTypeResolveEnum.File_DEFAULT_RESOLVE; }
    @Override
    public void resolve(Object objectParam) { /* default logic */ }
}

@Component
public class StrategyUseService implements ApplicationContextAware {
    private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();
    @Override
    public void setApplicationContext(ApplicationContext ctx) {
        ctx.getBeansOfType(IFileStrategy.class).values()
           .forEach(s -> iFileStrategyMap.put(s.gainFileType(), s));
    }
    public void resolveFile(FileTypeResolveEnum type, Object param) {
        IFileStrategy strategy = iFileStrategyMap.get(type);
        if (strategy != null) {
            strategy.resolve(param);
        }
    }
}

2. Chain of Responsibility Pattern

2.1 Business Scenario

Order processing often involves multiple checks (null, security, blacklist, rule). Using exceptions for flow control leads to tangled code and violates best practices.

public class Order {
    public void checkNullParam(Object param) { throw new RuntimeException(); }
    public void checkSecurity() { throw new RuntimeException(); }
    public void checkBackList() { throw new RuntimeException(); }
    public void checkRule() { throw new RuntimeException(); }
    public static void main(String[] args) {
        Order order = new Order();
        try {
            order.checkNullParam();
            order.checkSecurity();
            order.checkBackList();
            order.checkRule();
            System.out.println("order success");
        } catch (RuntimeException e) {
            System.out.println("order fail");
        }
    }
}

2.2 Definition

The pattern creates a chain of handler objects, each having a chance to process a request and optionally pass it to the next handler, thus decoupling sender and receiver.

Think of passing a love note through a row of classmates until it reaches the intended person.

2.3 Usage

An abstract handler with a reference to the next handler and a filter method.

Concrete handlers implement the specific check logic.

A demo component wires the handlers into a list and links them.

public abstract class AbstractHandler {
    private AbstractHandler nextHandler;
    public void setNextHandler(AbstractHandler next) { this.nextHandler = next; }
    public void filter(Request req, Response resp) {
        doFilter(req, resp);
        if (nextHandler != null) {
            nextHandler.filter(req, resp);
        }
    }
    protected abstract void doFilter(Request req, Response resp);
}

@Component
@Order(1)
public class CheckParamFilterObject extends AbstractHandler {
    @Override
    protected void doFilter(Request req, Response resp) { System.out.println("Non‑null param check"); }
}

@Component
@Order(2)
public class CheckSecurityFilterObject extends AbstractHandler {
    @Override
    protected void doFilter(Request req, Response resp) { System.out.println("Security check"); }
}

@Component
@Order(3)
public class CheckBlackFilterObject extends AbstractHandler {
    @Override
    protected void doFilter(Request req, Response resp) { System.out.println("Blacklist check"); }
}

@Component
@Order(4)
public class CheckRuleFilterObject extends AbstractHandler {
    @Override
    protected void doFilter(Request req, Response resp) { System.out.println("Rule check"); }
}

@Component("ChainPatternDemo")
public class ChainPatternDemo {
    @Autowired
    private List<AbstractHandler> handlers;
    private AbstractHandler head;
    @PostConstruct
    public void initializeChain() {
        for (int i = 0; i < handlers.size(); i++) {
            if (i == 0) {
                head = handlers.get(0);
            } else {
                handlers.get(i - 1).setNextHandler(handlers.get(i));
            }
        }
    }
    public void exec(Request req, Response resp) { head.filter(req, resp); }
}

3. Template Method Pattern

3.1 Business Scenario

Different merchants require similar request processing steps (query merchant, sign request, send HTTP, verify response) but with variations such as proxy vs. direct HTTP.

Template Method Flow
Template Method Flow

3.2 Definition

The Template Method defines the skeleton of an algorithm in an abstract class, allowing subclasses to override specific steps without changing the overall structure.

Dating analogy: the overall steps (hand‑hold, hug, kiss) stay the same, but you can choose left or right hand.

3.3 Usage

abstract class AbstractMerchantService {
    // Template method
    public void handleTemplate(Request req) {
        queryMerchantInfo();
        signature();
        httpRequest();
        verifySignature();
    }
    protected abstract void queryMerchantInfo();
    protected abstract void signature();
    protected abstract void httpRequest();
    protected abstract void verifySignature();
    protected abstract boolean isRequestByProxy();
}

class CompanyAServiceImpl extends AbstractMerchantService {
    @Override protected void queryMerchantInfo() { /* A logic */ }
    @Override protected void signature() { /* A logic */ }
    @Override protected void httpRequest() { /* proxy request */ }
    @Override protected void verifySignature() { /* A logic */ }
    @Override protected boolean isRequestByProxy() { return true; }
}

class CompanyBServiceImpl extends AbstractMerchantService {
    @Override protected void queryMerchantInfo() { /* B logic */ }
    @Override protected void signature() { /* B logic */ }
    @Override protected void httpRequest() { /* direct request */ }
    @Override protected void verifySignature() { /* B logic */ }
    @Override protected boolean isRequestByProxy() { return false; }
}

4. Observer Pattern

4.1 Business Scenario

After a user registers, multiple notifications (IM, SMS, email) need to be sent. Adding a new notification channel should not modify existing registration code.

4.2 Definition

The Observer defines a one‑to‑many dependency so that when one object changes state, all its dependents are notified.

4.3 Usage

Observable class holds a list of observers.

Observer interface with a doEvent method.

Concrete observers implement specific notification logic.

Guava EventBus can be used as a ready‑made observable.

public class Observable {
    private List<Observer> observers = new ArrayList<>();
    public void addObserver(Observer o) { observers.add(o); }
    public void removeObserver(Observer o) { observers.remove(o); }
    public void notifyAllObservers() {
        for (Observer o : observers) { o.doEvent(); }
    }
}

interface Observer { void doEvent(); }

class IMMessageObserver implements Observer { public void doEvent() { System.out.println("Send IM message"); } }
class MobileNoObserver implements Observer { public void doEvent() { System.out.println("Send SMS message"); } }
class EmailObserver implements Observer { public void doEvent() { System.out.println("Send Email message"); } }

// Guava EventBus example
public class EventBusCenter {
    private static final EventBus eventBus = new EventBus();
    public static void register(Object obj) { eventBus.register(obj); }
    public static void unregister(Object obj) { eventBus.unregister(obj); }
    public static void post(Object event) { eventBus.post(event); }
}

public class EventListener {
    @Subscribe
    public void handle(NotifyEvent e) {
        System.out.println("Send IM " + e.getImNo());
        System.out.println("Send SMS " + e.getMobileNo());
        System.out.println("Send Email " + e.getEmailNo());
    }
}

public class NotifyEvent {
    private final String mobileNo, emailNo, imNo;
    public NotifyEvent(String mobile, String email, String im) { this.mobileNo = mobile; this.emailNo = email; this.imNo = im; }
    public String getMobileNo() { return mobileNo; }
    public String getEmailNo() { return emailNo; }
    public String getImNo() { return imNo; }
}

public class EventBusDemoTest {
    public static void main(String[] args) {
        EventListener listener = new EventListener();
        EventBusCenter.register(listener);
        EventBusCenter.post(new NotifyEvent("13372817283", "[email protected]", "666"));
    }
}

5. Factory Pattern

5.1 Business Scenario

Factory pattern helps replace long if…else or switch statements when creating objects based on file type.

5.2 Definition & Usage

Factory interface defines a creation method.

Concrete factories implement the interface to create specific objects.

Client code uses the factory to obtain instances without knowing concrete classes.

interface IFileResolveFactory { void resolve(); }

class AFileResolve implements IFileResolveFactory { public void resolve() { System.out.println("File A type resolve"); } }
class BFileResolve implements IFileResolveFactory { public void resolve() { System.out.println("File B type resolve"); } }
class DefaultFileResolve implements IFileResolveFactory { public void resolve() { System.out.println("Default file type resolve"); } }

public class FactoryDemo {
    public static void main(String[] args) {
        IFileResolveFactory factory;
        String fileType = "A"; // example
        if ("A".equals(fileType)) {
            factory = new AFileResolve();
        } else if ("B".equals(fileType)) {
            factory = new BFileResolve();
        } else {
            factory = new DefaultFileResolve();
        }
        factory.resolve();
    }
}

6. Singleton Pattern

6.1 Business Scenario

Singleton ensures a class has only one instance and provides a global access point, useful for I/O, database connections, etc.

6.2 Classic Implementations

Lazy (lazy‑initialization) singleton.

Eager (hungry) singleton.

Double‑checked locking.

Static inner‑class holder.

Enum singleton.

// Lazy (lazy‑hand) singleton
public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}
    public static synchronized LazySingleton getInstance() {
        if (instance == null) { instance = new LazySingleton(); }
        return instance;
    }
}

// Eager singleton
public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() { return INSTANCE; }
}

// Double‑checked locking
public class DCLSingleton {
    private static volatile DCLSingleton instance;
    private DCLSingleton() {}
    public static DCLSingleton getInstance() {
        if (instance == null) {
            synchronized (DCLSingleton.class) {
                if (instance == null) { instance = new DCLSingleton(); }
            }
        }
        return instance;
    }
}

// Static inner‑class
public class InnerClassSingleton {
    private static class Holder { private static final InnerClassSingleton INSTANCE = new InnerClassSingleton(); }
    private InnerClassSingleton() {}
    public static InnerClassSingleton getInstance() { return Holder.INSTANCE; }
}

// Enum singleton
public enum EnumSingleton {
    INSTANCE;
    public void doSomething() { /* ... */ }
}
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.

Chain of ResponsibilityDesign PatternsJavaStrategy PatternFactory PatternTemplate MethodObserver Patternsingleton pattern
Su San Talks Tech
Written by

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.

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.