Fundamentals 23 min read

Unlock Cleaner Code: How Design Patterns Transform Everyday Java Development

This article explores common design patterns—including Strategy, Chain of Responsibility, Template Method, Observer, Factory, and Singleton—explaining their real‑world business scenarios, core principles, and providing concrete Java code examples to help developers write more maintainable and extensible software.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Unlock Cleaner Code: How Design Patterns Transform Everyday Java Development

Introduction

Hello, I am Su San . In daily coding we often write pipeline‑style code to implement business logic, but using design patterns can make the code more enjoyable and maintainable. Below are the design patterns I use most often.

1. Strategy Pattern

1.1 Business Scenario

When a big‑data system pushes files, different file types require different parsing logic. A typical implementation uses a long if...else chain, which becomes bloated and violates the Open/Closed and Single Responsibility principles.

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

Using the Strategy pattern, we define an interface and separate implementations for each parsing strategy.

1.2 Strategy Definition

Imagine dating different personalities: you need a different strategy—movie, snack, or shopping—to win their hearts. Similarly, the Strategy pattern encapsulates interchangeable algorithms.

1.3 Strategy Usage

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

Concrete strategy classes implement these methods.

A service loads all strategies into a map (using Spring's ApplicationContextAware) 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) { logger.info("A type parsing, param: {}", objectParam); }
}

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

2. Chain of Responsibility Pattern

2.1 Business Scenario

Order processing often involves parameter validation, security checks, blacklist checks, and rule interception. Using exceptions for flow control leads to tangled code.

public class Order {
    public void checkNullParam(Object p){ 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 o = new Order();
        try {
            o.checkNullParam();
            o.checkSecurity();
            o.checkBackList();
            o.checkRule();
            System.out.println("order success");
        } catch (RuntimeException e) {
            System.out.println("order fail");
        }
    }
}

The Chain of Responsibility decouples the sender from multiple handlers, allowing each handler to process or pass the request.

2.3 Chain Usage

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

Concrete handlers implement the specific logic (parameter check, security check, etc.).

A demo class wires the handlers into a list and initializes the chain in @PostConstruct.

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

3. Template Method Pattern

3.1 Business Scenario

Different merchants require slightly different HTTP request flows (proxy vs direct). The overall steps—query merchant, sign request, send HTTP, verify response—are the same.

3.2 Definition

The Template Method defines the skeleton of an algorithm in an abstract class, leaving some steps to subclasses.

abstract class AbstractMerchantService {
    // template method
    public Resp handleTemplate(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();
}

3.3 Usage

Merchant‑specific subclasses implement isRequestByProxy to decide whether to use a proxy.

class CompanyAService extends AbstractMerchantService {
    @Override protected boolean isRequestByProxy(){ return true; }
}
class CompanyBService extends AbstractMerchantService {
    @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 should not modify the registration code.

4.2 Definition

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

4.3 Usage

A subject class maintains a list of observers.

Observers implement a doEvent method.

Guava's EventBus can serve as a ready‑made event bus.

public class Observerable {
    private List<Observer> observers = new ArrayList<>();
    public void addObserver(Observer o){ observers.add(o); }
    public void notifyAll(int state){
        if(state!=1){ System.out.println("Not the notifying state"); return; }
        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"); } }
class EmailObserver implements Observer { public void doEvent(){ System.out.println("Send Email"); } }

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 Usage

Define a factory interface.

Concrete factories create specific strategy objects.

The client obtains a factory and calls resolve().

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

// client code
IFileResolveFactory factory;
if(fileType.equals("A")) { factory = new AFileResolve(); }
else if(fileType.equals("B")) { factory = new BFileResolve(); }
else { factory = new DefaultFileResolve(); }
factory.resolve();

6. Singleton Pattern

6.1 Business Scenario

Singleton ensures a class has only one instance, useful for I/O, database connections, etc.

6.2 Classic Implementations

Lazy (懒汉) singleton with optional synchronized for thread safety.

Eager (饿汉) singleton creates the instance at class loading.

Double‑checked locking.

Static inner‑class holder.

Enum singleton.

// Lazy 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;
    }
}

// Inner‑class holder
public class HolderSingleton {
    private HolderSingleton() {}
    private static class Holder { private static final HolderSingleton INSTANCE = new HolderSingleton(); }
    public static HolderSingleton getInstance(){ return Holder.INSTANCE; }
}

// Enum singleton
public enum EnumSingleton { INSTANCE; }

These patterns collectively demonstrate how to structure Java code for better extensibility, maintainability, and clarity.

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 ResponsibilityTemplate MethodSingletonstrategyFactoryObserver
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.