Mastering Design Patterns for Scalable Middleware Services
This article explores how to apply core design patterns—Observer, Factory, Proxy, and Template Method—in Java middleware development, illustrating each pattern with practical code snippets and explaining the design rationale for building maintainable, extensible, and flexible services.
I have been developing middleware services that prioritize functionality, maintainability, extensibility, and flexibility, which often requires heavy use of interfaces, abstract classes, and inheritance, optimized through design patterns.
Observer Pattern
When an object's state changes, multiple related objects need to be notified and updated, following an event‑driven model.
Design Idea: Use annotations or defined interfaces to collect all execution methods, then obtain them via reflection and invoke them in a loop. This approach is used in alarm listener development, where runtime parameters are collected and sent to all listener methods.
Code Example:
for (EvaluatorListenterAware value : functionalEvaluators.values()) {
Class<?> clazz = AopProxyUtils.ultimateTargetClass(value);
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(EvaluatorListener.class)) {
EvaluatorListener annotation = method.getAnnotation(EvaluatorListener.class);
EvaluatorMethod evaluatorMethod = EvaluatorMethod.builder()
.method(method)
.bean(clazz.newInstance())
.annotation(annotation)
.build();
evaluatorMethod.setPriority(evaluatorMethod.getPriority());
evaluatorMethodMap.computeIfAbsent(key, k -> new ArrayList<>()).add(evaluatorMethod);
}
}
}
@EvaluatorPriority(1)
@EvaluatorListener(method = "awardByRateLimit", message = "抽奖发生异常", permits = 3, time = 30)
public boolean evaluateException(EvaluatorEvent event) {
// ...
return false;
}Factory Pattern
The creation process of objects, or deciding which class to instantiate based on parameters, is similar to the Strategy pattern.
Design Idea: Define an interface and multiple implementing classes, then use reflection and dynamic parameters to obtain object instances.
In a virtual recharge system, orders are routed to specific recharge methods based on product categories, with DispatcherHolder acting as the factory manager.
Code Example:
BizResult result = dispatcherHolder.route(channelType).doDispatcher(model);
public interface Dispatcher {
BizResult doDispatcher(DispatchModel model);
}
public abstract class BaseDispatcher implements Dispatcher {
@Resource
private DispatcherHolder dispatcherHolder;
@PostConstruct
private void init() {
dispatcherHolder.putDispatcher(channelCode, this);
}
}
@Service
public class JdDispatcher extends BaseDispatcher {
// JD specific implementation
}
@Service
public class MeiTuanDispatcher extends BaseDispatcher {
// Meituan specific implementation
}Proxy Pattern
In AOP (Aspect‑Oriented Programming), many Spring features such as transactions and logging are implementations of the proxy pattern, similar to the decorator pattern for extending functionality.
Design Idea: Design a proxy class and inject the target class as a parameter of the proxy.
Code Example:
@Slf4j
@Aspect
public class OperationLogAspect {
@Around("@annotation(logOperation)")
public Object logOperation(ProceedingJoinPoint joinPoint, LogOperation logOperation) throws Throwable {
Object result = joinPoint.proceed();
// proxy actions such as logging
return result;
}
}Template Method Pattern
When the overall algorithm structure is fixed but certain steps need subclass implementation, the template method pattern provides a fixed framework while delegating specific steps to subclasses.
In a unified message‑push platform, each message follows a fixed flow: validate parameters, assemble parameters, rate‑limit, send to MQ, and invoke the push API. These steps are encapsulated as separate subclasses, often using generics for strict parameter design.
Code Example:
public interface BusinessProcess<T extends ProcessModel> {
void process(ProcessContext<T> context);
}
public class SendAssembleProcess implements BusinessProcess<SendTaskModel> {
// assemble parameters implementation
}
public class SendMqProcess implements BusinessProcess<SendTaskModel> {
// send to MQ implementation
}
List<BusinessProcess> processList = templateContainer.get(context.getCode()).getProcessList();
for (BusinessProcess businessProcess : processList) {
businessProcess.process(context);
if (Boolean.TRUE.equals(context.getNeedBreak())) {
break;
}
}Design patterns often differ from real business scenarios. From factory to observer to template method, choosing and implementing the right pattern in complex requirements directly impacts development efficiency and system extensibility. The above real‑world examples share practical experiences to help you design flexible software architectures.
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.
Lin is Dream
Sharing Java developer knowledge, practical articles, and continuous insights into computer engineering.
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.
