Fundamentals 23 min read

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.

ITFLY8 Architecture Home
ITFLY8 Architecture Home
ITFLY8 Architecture Home
Unlock Cleaner Code: How Design Patterns Transform Business Logic and Boost Developer Joy

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.

Chain of Responsibility diagram
Chain of Responsibility diagram

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.

Strategy diagram
Strategy diagram

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.

Template Method diagram
Template Method diagram

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.

Observer diagram
Observer diagram

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.

Decorator diagram
Decorator diagram

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.

Bridge diagram
Bridge diagram

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

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 ResponsibilitystrategybridgeDecorator
ITFLY8 Architecture Home
Written by

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.

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.