Unlock Cleaner Code: How Design Patterns Transform Business Logic and Boost Developer Joy
This article explores how applying classic design patterns—Chain of Responsibility, Strategy, Template Method, Observer, Decorator, and Bridge—can turn repetitive CRUD business code into clean, robust, and extensible solutions, illustrated with real‑world Java examples, practical experiences, and step‑by‑step code snippets.
In many business development projects, most tasks are solved with simple CRUD operations, which provide little technical growth. Using design patterns can make code cleaner, more robust, and bring joy to coding.
Preface
Excellent engineers share strong thinking abilities. While many stay at the surface level, true progress comes from deep thinking and planning. After routine work, finding pleasure in code requires improving these skills.
Chain of Responsibility Design Pattern
The Chain of Responsibility (CoR) pattern is a behavioral pattern where a request passes along a chain of handlers until one processes it.
Applicable scenarios include multi‑node workflow processing where each node works independently, such as OA approval flows or Java Web filters.
Applicable Scenarios
Multiple objects can handle the same request, with the actual handler decided at runtime.
When the handler is not known in advance, the request is submitted to a set of possible handlers.
Dynamic handling of a group of objects.
Example: an OA leave request where a half‑day leave is approved by a supervisor, 1‑3 days by a manager, 3‑30 days by a general manager, and more than 30 days is not approved.
Practical Experience
Business flow for canceling a credit card:
Call to cancel the credit card.
Staff cancels the credit card.
Before cancellation, three checks are needed: unpaid bills, overflow funds, and high‑value points.
UserLogoutUnpaidBillsLimitFilter – checks for unpaid bills.
UserLogoutOverflowLimitFilter – checks for overflow funds.
UserLogoutGiveUpPointsLimitFilter – checks for high‑value points.
The filter chain processes these checks sequentially; if any filter fails, the process stops.
public boolean canLogout(String userId) {
// Get user info
UserInfo userInfo = getUserInfo(userId);
// Build filter chain
LogoutLimitFilterChain filterChain = new LogoutLimitFilterChain();
filterChain.addFilter(new UserLogoutUnpaidBillsLimitFilter());
filterChain.addFilter(new UserLogoutOverflowLimitFilter());
filterChain.addFilter(new UserLogoutGiveUpPointsLimitFilter());
boolean checkResult = filterChain.doFilter(filterChain, userInfo);
// Continue if all passed
return checkResult;
}
public boolean doFilter(LogoutLimitFilterChain filterChain, UserInfo userInfo) {
if (index < filters.size()) {
return filters.get(index++).doFilter(filterChain, userInfo);
}
return true;
}
@Override
public boolean doFilter(LogoutLimitFilterChain filterChain, UserInfo userInfo) {
// Check unpaid bills
UserCardBillInfo billInfo = findUserCardBillInfo(userInfo);
if (billInfo != null && !CAN_LOGOUT.equals(billInfo.getEnabledLogout())) {
return false;
}
return filterChain.doFilter(filterChain, memberInfo, consumeConfig);
}Strategy Design Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable.
It is useful when you have multiple algorithms that can be swapped without affecting clients, such as different discount calculations in an e‑commerce system.
Applicable Scenarios
Multiple classes with slightly different algorithms.
Algorithms that need to be switched at runtime.
Scenarios where the algorithm should be hidden from the client.
Example code for discount strategies:
public abstract class DiscountStrategy {
public abstract CalculationResult getDiscountPrice(Long userId, BigDecimal price);
}
public class FullReductionStrategyOne extends DiscountStrategy {
@Override
public CalculationResult getDiscountPrice(Long userId, BigDecimal price) {
if (price.doubleValue() < 300) {
return price;
}
BigDecimal dealPrice = price.subtract(BigDecimal.valueOf(80));
return getCalculationResult(userId, dealPrice);
}
}
public abstract class AbstractDiscountStrategy implements DiscountStrategy {
@Override
public CalculationResult getDiscountPrice(Long userId, BigDecimal price) {
Span span = buildSpan(); // start trace
CalculationResult result = getCalculationResult(userId, price);
if (!result.isSuccess() && canUpgrade()) {
upgradeLevel(userId, result);
}
span.finish(); // end trace
return result;
}
protected abstract CalculationResult getCalculationResult(Long userId, BigDecimal price);
protected abstract boolean canUpgrade(Long userId, CallResult callResult);
}Template Method Design Pattern
The Template pattern defines the skeleton of an algorithm in an abstract class, allowing subclasses to fill in specific steps.
Typical steps: cut vegetables, add oil, stir‑fry, and serve. The third step varies between dishes, which is where the template method shines.
Practical Experience
When adding new discount requirements (e.g., tracing and membership upgrade), the common steps (trace start/end) are placed in an abstract template, while the variable steps are implemented by concrete strategies.
abstract class AbstractDiscountStrategy implements DiscountStrategy {
@Override
public CalculationResult getDiscountPrice(Long userId, BigDecimal price) {
Span span = buildSpan();
CalculationResult result = getCalculationResult(userId, price);
if (!result.isSuccess() && canUpgrade()) {
upgradeLevel(userId, result);
}
span.finish();
return result;
}
protected abstract CalculationResult getCalculationResult(Long userId, BigDecimal price);
protected abstract boolean canUpgrade(Long userId, CallResult callResult);
}Observer Design Pattern
The Observer pattern defines a one‑to‑many dependency so that when one object changes state, all its dependents are notified.
Typical use cases include auction systems, event buses, and Java's built‑in Observer/Observable.
In the credit‑card cancellation flow, a payment success event triggers observers that handle point accumulation and voice broadcast.
public class PayCallBackController implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.applicationContext = ctx;
}
@RequestMapping("/pay/callback.do")
public View callback(HttpServletRequest request) {
if (paySuccess(request)) {
PaySuccessEvent event = buildPaySuccessEvent(...);
this.applicationContext.publishEvent(event);
}
return null;
}
}
public class VoiceBroadcastHandler implements ApplicationListener<PaySuccessEvent> {
@Override
public void onApplicationEvent(PaySuccessEvent event) {
// voice broadcast logic
}
}Decorator Design Pattern
The Decorator pattern adds responsibilities to objects dynamically without affecting other objects.
Example: a coffee shop where customers can add milk, sugar, mint, etc., each represented by a decorator that adjusts the name and price.
public abstract class Coffee {
public abstract String getName();
public abstract double getPrice();
}
public abstract class CoffeeDecorator implements Coffee {
protected Coffee delegate;
public CoffeeDecorator(Coffee coffee) { this.delegate = coffee; }
@Override
public String getName() { return delegate.getName(); }
@Override
public double getPrice() { return delegate.getPrice(); }
}
public class MilkCoffeeDecorator extends CoffeeDecorator {
public MilkCoffeeDecorator(Coffee coffee) { super(coffee); }
@Override
public String getName() { return "Milk, " + super.getName(); }
@Override
public double getPrice() { return 1.1 + super.getPrice(); }
}Bridge Design Pattern
The Bridge pattern decouples an abstraction from its implementation so that the two can vary independently.
Use case: a performance‑monitoring system where data collection, aggregation, and storage each have multiple interchangeable implementations (e.g., SNMP vs EMS, Storm vs Spark, HDFS vs MPPDB).
public abstract class CollectionService {
public abstract void execute();
public void run() { execute(); }
}
public abstract class AggregationService {
protected CollectionService collectionService;
public AggregationService(CollectionService cs) { this.collectionService = cs; }
public void run() { if (collectionService != null) collectionService.run(); execute(); }
protected abstract void execute();
}
public abstract class StoreService {
protected AggregationService aggregationService;
public StoreService(AggregationService as) { this.aggregationService = as; }
public void run() { if (aggregationService != null) aggregationService.run(); execute(); }
protected abstract void execute();
}
public class SNMPCollectionService extends CollectionService {
@Override public void execute() { System.out.println("SNMP collection."); }
}
public class StormAggregationService extends AggregationService {
public StormAggregationService(CollectionService cs) { super(cs); }
@Override protected void execute() { System.out.println("Storm aggregation."); }
}
public class HDFSStoreService extends StoreService {
public HDFSStoreService(AggregationService as) { super(as); }
@Override protected void execute() { System.out.println("HDFS store."); }
}
public class BridgeTest {
public static void main(String[] args) {
CollectionService snmp = new SNMPCollectionService();
AggregationService storm = new StormAggregationService(snmp);
StoreService hdfs = new HDFSStoreService(storm);
hdfs.run();
}
}Conclusions
Design patterns such as Chain of Responsibility, Strategy, Template Method, Observer, Decorator, and Bridge help turn tangled business code into modular, maintainable, and extensible solutions. They embody the Open/Closed Principle, improve code readability, and enable flexible feature addition without modifying existing code.
Source: https://my.oschina.net/u/4662964/blog/4702495
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.
ITFLY8 Architecture Home
ITFLY8 Architecture Home - focused on architecture knowledge sharing and exchange, covering project management and product design. Includes large-scale distributed website architecture (high performance, high availability, caching, message queues...), design patterns, architecture patterns, big data, project management (SCRUM, PMP, Prince2), product design, and more.
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.
