Java示例:设计模式是如何在实战中“自然生长”出来

您问到了最关键的点子上,也是所有学习者从理论到实践最难跨越的一步。理论之所以感觉"一套套的",是因为它被系统地、干净地总结了出来。而现实是混乱、复杂且充满约束的。

下面,我抛弃纯理论,用一个 "实战推演" 的思路,带你看看设计模式是如何在实战中"自然生长"出来的。

核心理念:模式不是起点,而是重构的结果

不要带着"我要用模式"的心态去写代码,而要带着 "我要解决问题,让代码更干净" 的心态。模式是你到达终点后,别人给你起的名字。


实战推演:从一个简单需求开始

假设我们要开发一个电商系统的 OrderService,核心需求是创建订单。

第零步:最直接的实现

java 复制代码
public class OrderService {
    public Order createOrder(CreateOrderRequest request) {
        // 1. 验证参数
        if (request.getItems().isEmpty()) {
            throw new ValidationException("订单项不能为空");
        }
        // 2. 计算价格
        BigDecimal total = calculateTotal(request.getItems());
        // 3. 扣减库存(调用库存服务)
        inventoryClient.decreaseStock(request.getItems());
        // 4. 保存订单到数据库
        Order order = orderRepository.save(new Order(total, request.getItems()));
        // 5. 发送订单创建成功事件(比如发邮件、短信)
        eventBus.publish(new OrderCreatedEvent(order.getId()));
        return order;
    }
}

现状 :代码能工作,逻辑清晰。此时,任何设计模式都是多余的。


第一步:需求变化 -> 引入"策略模式"的种子

新需求:现在要支持不同类型的订单:普通订单、团购订单、秒杀订单。它们的价格计算规则完全不同。

糟糕的改法 :在 calculateTotal 方法里加 if-else

java 复制代码
// 坏味道:冗长的if-else
private BigDecimal calculateTotal(OrderType type, List<Item> items) {
    if (type == OrderType.NORMAL) {
        // ... 普通计算逻辑
    } else if (type == OrderType.GROUP_BUY) {
        // ... 团购计算逻辑
    } else if (type == OrderType.FLASH_SALE) {
        // ... 秒杀计算逻辑
    }
    // ... 未来每加一种类型,就要修改这里
}

问题:违反了"开闭原则"(对扩展开放,对修改关闭)。每次加新类型,都要修改这个核心类,风险高。

重构(模式自然浮现)

  1. 识别变化点:价格计算逻辑是变化的。

  2. 抽取接口 :定义一个 PricingStrategy 接口。

    java 复制代码
    public interface PricingStrategy {
        boolean supports(OrderType type);
        BigDecimal calculate(List<Item> items);
    }
  3. 封装实现 :为每种订单类型创建一个策略类。

    java 复制代码
    @Component
    public class NormalPricingStrategy implements PricingStrategy {
        @Override public boolean supports(OrderType type) { return type == OrderType.NORMAL; }
        @Override public BigDecimal calculate(List<Item> items) { /* ... */ }
    }
    // ... 其他策略类
  4. 在Service中使用策略

    java 复制代码
    @Autowired
    private List<PricingStrategy> strategies; // Spring会自动注入所有实现
    
    private BigDecimal calculateTotal(OrderType type, List<Item> items) {
        for (PricingStrategy strategy : strategies) {
            if (strategy.supports(type)) {
                return strategy.calculate(items);
            }
        }
        throw new UnsupportedOperationException("不支持的订单类型");
    }

你看,我们并没有"使用策略模式",而是通过"消除重复的if-else"和"隔离变化",让策略模式的结构自然呈现了出来。 现在,增加新订单类型,我只需要新建一个 PricingStrategy 实现类,OrderService 完全不用动。


第二步:逻辑复杂 -> 引入"模板方法模式"的种子

新问题:创建订单的步骤越来越复杂,每种订单类型在核心步骤上一致(验证->算价->扣库存->保存->发事件),但某些步骤的实现细节不同(比如团购订单的验证逻辑更复杂)。

糟糕的改法 :在 createOrder 方法里写满针对类型的 if-else。代码会变成一团乱麻。

重构(模式自然浮现)

  1. 识别不变与变流程骨架(模板)是不变的某些步骤的实现是可变的

  2. 定义模板 :将固定流程提升到父类。

    java 复制代码
    public abstract class AbstractOrderCreator {
        // 这就是"模板方法"
        public final Order createOrder(CreateOrderRequest request) {
            // 固定流程
            validateSpecific(request);
            BigDecimal total = calculateTotal(request);
            decreaseStock(request);
            Order order = saveOrder(request, total);
            publishEvent(order);
            return order;
        }
        // 将变化的步骤声明为抽象方法,强迫子类实现
        protected abstract void validateSpecific(CreateOrderRequest request);
        protected abstract BigDecimal calculateTotal(CreateOrderRequest request);
        // ... 其他抽象或可重写的方法
    }
  3. 创建子类

    java 复制代码
    @Service
    public class GroupBuyOrderCreator extends AbstractOrderCreator {
        @Override
        protected void validateSpecific(CreateOrderRequest request) {
            // 团购订单特有的复杂验证逻辑
            if (!groupBuyService.isValid(request.getGroupId())) {
                throw new ValidationException("团购活动已失效");
            }
            // ... 更多验证
        }
        // ... 实现其他抽象方法
    }

你看,我们为了解决"流程相同,部分步骤不同"的问题,自然地使用了模板方法模式。 现在,OrderService 的职责可以简化为路由,根据类型找到对应的 AbstractOrderCreator 来执行。


第三步:依赖混乱 -> 引入"依赖注入"与"门面模式"

新问题OrderService 现在依赖了太多东西:各种 CreatorPricingStrategy、仓库、客户端等。它变成了一个"上帝类",难以测试和维护。

重构(模式自然浮现)

  1. 应用依赖注入 :这不是一个具体的GoF模式,而是一个更基础的架构原则。我们通过构造函数清晰地声明依赖,而不是用 @Autowired 到处乱注。

    java 复制代码
    @Service
    public class OrderService {
        private final Map<OrderType, AbstractOrderCreator> creatorMap;
        // 通过构造器注入,依赖关系一目了然
        public OrderService(List<AbstractOrderCreator> creators) {
            this.creatorMap = creators.stream()
                .collect(Collectors.toMap(creator -> creator.supportsOrderType(), Function.identity()));
        }
        public Order createOrder(CreateOrderRequest request) {
            AbstractOrderCreator creator = creatorMap.get(request.getType());
            return creator.createOrder(request);
        }
    }
  2. 考虑门面模式 :如果外部客户端(如Controller)需要调用多个Service才能完成一个操作,我们可以引入一个 OrderFacade,为外部提供一个统一的、简洁的接口,隐藏内部 OrderServicePaymentServiceShippingService 的复杂交互。

给你的实践路线图

  1. 忘记模式,记住原则 :首先牢记 SOLID 原则(尤其是单一职责SRP和开闭原则OCP)和 DRY(不要重复自己)。这些是产生模式的土壤。
  2. 从小处着手:不要试图设计一个完美的系统。先让代码跑起来。
  3. 培养嗅觉 :学会识别"代码坏味道"。
    • 重复代码 -> 提取方法/类。
    • 过长的函数/类 -> 分解。
    • 庞大的条件判断 -> 考虑用多态(策略、状态模式)替代。
    • 类知道得太多 -> 考虑依赖注入、中介者模式。
  4. 痛苦驱动重构:当添加新功能或修改代码变得非常困难和痛苦时,就是重构的最佳时机。问问自己:"这里是什么变化让我如此痛苦?我能否将这部分抽离出来?"
  5. 重构,而非重写:通过一系列小步、安全的重构(有测试保障最好),将"坏代码"逐步改善为"好代码"。在这个过程中,你会发现设计模式的结构自然而然地出现了。

最终,你的目标不是"在代码中使用模式",而是"写出干净的代码"。而干净的代码,往往会自然而然地体现出经典设计模式的结构。 这时,模式对你来说就不再是"一套套的理论",而是你工具箱里顺手拈来的、有名字的工具了。

相关推荐
能摆一天是一天2 小时前
JAVA Function
java
The Sheep 20232 小时前
Dotnet-Dapper的用法
java·开发语言
Juchecar2 小时前
超越经典23种设计模式:新模式、反模式与函数式编程
设计模式·云原生·函数式编程
SimonKing2 小时前
百度统计、Google Analytics平替开源网站分析工具:Umami
java·后端·程序员
Juchecar2 小时前
设计模式不是Java专属,其他语言的使用方法
java·python·设计模式
马克学长2 小时前
SSM基于Java的医疗器械销售系统oy281(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·开发语言·用户管理·ssm 框架·医疗器械销售系统
_Power_Y2 小时前
Linux&git入门&设计模式(常考点)
linux·git·设计模式
Seven973 小时前
MyBatis 常见面试题
java·mybatis
我命由我123453 小时前
Android WebView - loadUrl 方法的长度限制
android·java·java-ee·android studio·android jetpack·android-studio·android runtime