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