Fundamentals 32 min read

Mastering SOLID: Real-World Refactoring of Order Systems for Cleaner Code

This article explains the five SOLID principles—Single Responsibility, Open‑Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion—using an order‑processing system example, showing common violations, step‑by‑step refactorings, practical guidelines, and Java code snippets to improve code modularity, maintainability, and extensibility.

Deepin Linux
Deepin Linux
Deepin Linux
Mastering SOLID: Real-World Refactoring of Order Systems for Cleaner Code

SOLID principles are coding standards that help developers avoid bad designs; promoted by Robert C. Martin, they improve code extensibility, logic, and readability.

When developers follow poor designs, code loses flexibility and robustness, making even small changes likely to introduce bugs. Understanding and correctly applying SOLID dramatically raises code quality and aids comprehension of well‑designed software.

1. Single Responsibility Principle (SRP)

The core idea of SRP is that a class should have only one reason to change. Each class focuses on a single responsibility, so when requirements evolve, only that class is affected.

In an order‑processing system, a class that handles order calculation, inventory deduction, and logging violates SRP. Changing the inventory logic could unintentionally affect price calculation, illustrating the problem.

1.1 Order System Practice: Split Responsibilities into Atomic Classes

To follow SRP, we can separate the responsibilities into independent classes:

OrderCalculator : Handles only order price calculation, including promotions and shipping.

StockManager : Manages all inventory‑related operations such as deduction and rollback.

OrderLogger : Centralizes order‑related logging, abstracting the storage medium.

1.2 Code Example: From “Big Mess” to “Atomic”

public class OrderService {
    // 计算订单价格,包含促销规则和运费
    public double calculateOrderPrice(Order order) {
        // 复杂的价格计算逻辑,包含各种促销规则和运费计算
        // 假设这里有满减、折扣、优惠券等规则,以及运费分摊逻辑
        double totalPrice = 0;
        // 遍历订单中的商品,计算商品总价
        for (OrderItem item : order.getItems()) {
            totalPrice += item.getPrice() * item.getQuantity();
        }
        // 应用促销规则,比如满减
        if (totalPrice >= 100) {
            totalPrice -= 10;
        }
        // 计算运费,假设每单固定运费10元
        totalPrice += 10;
        return totalPrice;
    }

    // 扣减库存
    public void deductStock(Order order) {
        for (OrderItem item : order.getItems()) {
            // 假设这里调用外部库存服务扣减库存
            StockService.deductStock(item.getProductId(), item.getQuantity());
        }
    }

    // 记录订单日志
    public void logOrder(Order order) {
        // 假设这里使用日志框架记录日志
        Logger logger = LoggerFactory.getLogger(OrderService.class);
        logger.info("Order created: " + order.getOrderId());
    }
}

This class violates SRP by handling calculation, stock deduction, and logging together. Any change to one function may impact the others.

Next, we refactor according to SRP:

// 订单计算类
public class OrderCalculator {
    public double calculateOrderPrice(Order order) {
        double totalPrice = 0;
        for (OrderItem item : order.getItems()) {
            totalPrice += item.getPrice() * item.getQuantity();
        }
        if (totalPrice >= 100) {
            totalPrice -= 10;
        }
        totalPrice += 10;
        return totalPrice;
    }
}

// 库存管理类
public class StockManager {
    public void deductStock(Order order) {
        for (OrderItem item : order.getItems()) {
            StockService.deductStock(item.getProductId(), item.getQuantity());
        }
    }
}

// 日志记录类
public class OrderLogger {
    public void logOrder(Order order) {
        Logger logger = LoggerFactory.getLogger(OrderLogger.class);
        logger.info("Order created: " + order.getOrderId());
    }
}

After refactoring, each class has a single responsibility, greatly improving readability and maintainability. Modifying order calculation, inventory logic, or logging now only requires changes in the corresponding class.

1.3 Practice Tips: Three Ways to Judge Single Responsibility

How to determine whether a class has a single responsibility?

Does the class exceed 300 lines of code or contain more than 10 methods? Excessive size often indicates multiple responsibilities.

Do you worry that changing one feature will affect unrelated modules? If yes, the class likely violates SRP.

Can you describe the class’s core function in a single sentence? If not, the class probably has too many duties.

Using these checks helps identify and resolve SRP violations, leading to higher code quality.

2. Open‑Closed Principle (OCP)

OCP states that software entities should be open for extension but closed for modification. Adding new features should be done by extending existing code rather than altering stable, tested code.

2.2 Order System Practice: Define Interface + Strategy Pattern

In an order system, initially only credit‑card payment is supported. When Alipay and WeChat Pay need to be added, modifying the existing payment class would make it bulky and error‑prone. Using the Strategy pattern keeps the system open for new payment methods without changing existing code.

(1) Define Payment Interface

public interface PaymentStrategy {
    void processPayment(double amount);
}

(2) Implement Specific Payment Methods

// Credit‑card payment
public class CreditCardPayment implements PaymentStrategy {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment with credit card: " + amount);
        // Real credit‑card logic here
    }
}

// Alipay payment
public class AlipayPayment implements PaymentStrategy {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment with Alipay: " + amount);
        // Real Alipay logic here
    }
}

// WeChat payment
public class WeChatPayment implements PaymentStrategy {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment with WeChat Pay: " + amount);
        // Real WeChat logic here
    }
}

(3) High‑Level Module Calls the Interface

public class OrderService {
    private PaymentStrategy paymentStrategy;

    public OrderService(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void processOrder(double amount) {
        System.out.println("Processing order...");
        paymentStrategy.processPayment(amount);
        System.out.println("Order processed successfully.");
    }
}

2.3 Practical Key: Three Steps to Build an “Expandable Architecture”

To construct a maintainable, extensible system:

Define a stable top‑level interface or abstract class that captures the contract.

Implement concrete classes that inherit or implement the abstraction, encapsulating variation details.

High‑level modules depend on the abstraction, not on concrete implementations.

Following these steps ensures new features can be added without modifying existing stable code.

3. Liskov Substitution Principle (LSP)

LSP requires that objects of a subclass can replace objects of the parent class without altering the correctness of the program.

3.1 Order System Practice: Subclass Extends Without Overriding Core Logic

Define a base Order class with basic price calculation, then a PromotionOrder subclass that adds discount logic while preserving method signatures.

public class Order {
    protected double basePrice;
    protected double shippingFee;

    public Order(double basePrice, double shippingFee) {
        this.basePrice = basePrice;
        this.shippingFee = shippingFee;
    }

    public double calculateTotalPrice() {
        return basePrice + shippingFee;
    }
}

public class PromotionOrder extends Order {
    private double discount;

    public PromotionOrder(double basePrice, double shippingFee, double discount) {
        super(basePrice, shippingFee);
        this.discount = discount;
    }

    @Override
    public double calculateTotalPrice() {
        double totalPrice = super.calculateTotalPrice();
        if (totalPrice > discount) {
            totalPrice -= discount;
        }
        return totalPrice;
    }
}

public class OrderProcessor {
    public void processOrder(Order order) {
        double totalPrice = order.calculateTotalPrice();
        System.out.println("Order total price: " + totalPrice);
        // Additional processing
    }
}

Both Order and PromotionOrder can be passed to OrderProcessor.processOrder without breaking behavior.

3.2 Anti‑Pattern: Dangerous Subclass Override

If a subclass changes the semantics of a method, LSP is violated. Example: SpecialOrder overrides getOrderStatus and may return an unexpected status.

public class Order {
    private OrderStatus status;

    public Order(OrderStatus status) {
        this.status = status;
    }

    public OrderStatus getOrderStatus() {
        return status;
    }
}

public enum OrderStatus {
    PENDING,
    PROCESSING,
    COMPLETED,
    CANCELED
}

public class SpecialOrder extends Order {
    private boolean isSpecial;

    public SpecialOrder(OrderStatus status, boolean isSpecial) {
        super(status);
        this.isSpecial = isSpecial;
    }

    @Override
    public OrderStatus getOrderStatus() {
        if (isSpecial) {
            return OrderStatus.COMPLETED;
        }
        return super.getOrderStatus();
    }
}

public class OrderService {
    public void handleOrder(Order order) {
        OrderStatus status = order.getOrderStatus();
        if (status == OrderStatus.PENDING) {
            System.out.println("Processing pending order...");
        } else if (status == OrderStatus.COMPLETED) {
            System.out.println("Order completed, updating inventory...");
        }
        // Other status handling
    }
}

When a SpecialOrder with isSpecial=true is processed, the unexpected status can cause wrong logic branches, demonstrating the risk of violating LSP.

3.3 Practice Points: Subclass “Behavior Compatibility” Three Principles

Subclass method parameters must not be more restrictive than the parent’s (contravariance).

Subclass return types must not be broader than the parent’s (covariance).

Subclass must not throw new unchecked exceptions that the parent does not declare.

Observing these rules ensures substitutability and stable code.

4. Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they do not use. Large “fat” interfaces should be split into fine‑grained ones.

4.1 Order System Practice: Split into Fine‑Grained Interfaces

Define separate interfaces for creation, querying, and printing:

public interface OrderCreator {
    void createOrder(Order order);
}

public interface OrderQuery {
    Order getOrderById(String orderId);
    List<Order> listOrders();
}

public interface OrderPrinter {
    void printReceipt(Order order);
    void generatePDF(Order order);
}

public class MobileOrderService implements OrderCreator, OrderQuery {
    @Override
    public void createOrder(Order order) {
        // Creation logic
    }

    @Override
    public Order getOrderById(String orderId) {
        // Query logic
        return null;
    }

    @Override
    public List<Order> listOrders() {
        // List logic
        return null;
    }
}

4.2 Code Example: From “Full Interface” to “On‑Demand Implementation”

Fat interface example and its refactoring:

// Fat interface
public interface OrderService {
    void createOrder(Order order);
    void queryOrder(String orderId);
    void deleteOrder(String orderId);
    void printReceipt(Order order);
    void exportOrder(Order order);
}

// Mobile implementation (needs only create & query)
public class MobileOrderService implements OrderService {
    @Override public void createOrder(Order order) { /* ... */ }
    @Override public void queryOrder(String orderId) { /* ... */ }
    @Override public void deleteOrder(String orderId) { /* no‑op */ }
    @Override public void printReceipt(Order order) { /* no‑op */ }
    @Override public void exportOrder(Order order) { /* no‑op */ }
}

// Refactored fine‑grained interfaces
public interface OrderCreator { void createOrder(Order order); }
public interface OrderQuery { void queryOrder(String orderId); }

public class MobileOrderService implements OrderCreator, OrderQuery {
    @Override public void createOrder(Order order) { /* ... */ }
    @Override public void queryOrder(String orderId) { /* ... */ }
}

After refactoring, the mobile service implements only the interfaces it truly needs, reducing unnecessary code.

4.3 Practice Tips: “Interface Slimming” Three Steps

Identify unused methods in existing interfaces.

Split the large interface into multiple cohesive small interfaces based on functionality.

Prefer composition over inheritance when a class needs multiple behaviors, implementing only the required small interfaces.

These steps produce lean interfaces that improve code quality and maintainability.

5. Dependency Inversion Principle (DIP)

DIP states that high‑level modules should depend on abstractions, not concrete implementations. Details should depend on abstractions as well.

5.1 Order System Practice: Define Abstract Data Access Layer

Introduce an OrderRepository interface that abstracts persistence operations.

public interface OrderRepository {
    void saveOrder(Order order);
    Order getOrder(String orderId);
}

// MySQL implementation
public class MySQLOrderRepository implements OrderRepository {
    @Override
    public void saveOrder(Order order) {
        System.out.println("Saving order to MySQL: " + order);
    }
    @Override
    public Order getOrder(String orderId) {
        System.out.println("Retrieving order from MySQL with ID: " + orderId);
        return null;
    }
}

// MongoDB implementation
public class MongoDBOrderRepository implements OrderRepository {
    @Override
    public void saveOrder(Order order) {
        System.out.println("Saving order to MongoDB: " + order);
    }
    @Override
    public Order getOrder(String orderId) {
        System.out.println("Retrieving order from MongoDB with ID: " + orderId);
        return null;
    }
}

public class OrderService {
    private OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public void createOrder(Order order) {
        System.out.println("Processing order...");
        orderRepository.saveOrder(order);
        System.out.println("Order created successfully.");
    }
}

5.2 Code Example: Depend on Abstraction, Not Implementation

public class Main {
    public static void main(String[] args) {
        // Use MySQL repository
        OrderRepository mySQLRepo = new MySQLOrderRepository();
        OrderService orderService1 = new OrderService(mySQLRepo);
        Order order1 = new Order("1", "Product A", 100.0);
        orderService1.createOrder(order1);

        // Use MongoDB repository
        OrderRepository mongoDBRepo = new MongoDBOrderRepository();
        OrderService orderService2 = new OrderService(mongoDBRepo);
        Order order2 = new Order("2", "Product B", 200.0);
        orderService2.createOrder(order2);
    }
}

OrderService depends only on the OrderRepository abstraction, allowing easy swapping of concrete data stores without changing business logic.

5.3 Practice Core: Two Rules of “Depend on Abstraction”

High‑level modules define the required functionality via interfaces or abstract classes.

Low‑level modules implement those abstractions; high‑level modules call the abstraction, not the concrete class.

Following these rules decouples modules, enhancing flexibility, maintainability, and scalability.

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.

JavarefactoringOOPSOLID
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

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.