从屎山说起:支付流程重构实战,三种设计模式灵活运用

引言

前一阵在复盘公司里支付平台的代码时,看到支付下单流程的代码,我的内心是崩溃的。

密密麻麻的if-else嵌套,几种支付渠道的处理逻辑混在一起。如果想新增一个支付方式,得在好几块个地方修改代码,维护起来必然痛苦。

实在看不下去了,于是组织了几个小伙伴,决定对这块代码进行重构优化,当然也是挣一点KPI。

经过一番讨论和设计,我们通过运用多种设计模式,对支付流程进行了彻底的重构。

重构后的代码不仅逻辑清晰,扩展性也得到了提升。如果想要新增支付方式,只需要实现相应的策略类即可,再也不用去一堆if-else里面找代码了。

借此机会,正好将这次重构的核心思路整理出来,分享给大家,希望能提供一个在实际项目中灵活运用设计模式、解决复杂业务问题的参考案例,也希望能给遇到类似问题的同学一些启发。

当然,整个重构流程是相当复杂的,我们循序渐进展开,先以最常见的工厂模式、策略模式、适配器模式来切入,后续再开一篇继续讲讲这次重构过程中用到的责任链模式、模版方法模式、观察者模式。

正文

问题

一个典型的订单支付场景通常需要支持多种支付方式,例如微信支付、支付宝支付、银行卡支付等。随着业务的发展,未来还可能接入新的支付渠道,或者对现有支付逻辑进行调整。

如果采用硬编码的方式,在代码中使用大量的if-else语句来判断支付方式并执行相应的支付逻辑,会带来以下这些很关键的问题:

  • 违反开闭原则:每当需要新增一种支付方式时,都必须修改原有的支付处理代码,风险很高;
  • 代码臃肿,可读性差 :随着需要支持的支付方式增多,if-else会变得越来越多,核心逻辑与具体实现细节耦合在一起,难以理解和维护;
  • 扩展性差:不同支付渠道的API接口、参数和返回格式往往各不相同。将这些差异化的处理逻辑都塞在一个方法里,会使代码结构变得混乱不堪;

为了解决这些问题,我们决定引入多种设计模式来重构支付流程。

我们也做一次愚公,好好移一下这座代码屎山。

核心设计思想与原则

本次重构的核心思想是解耦和封装,遵循的理念则是要符合开闭原则、单一职责和依赖倒置。

正如引言所讲,整个重构过程极其复杂的,涉及的设计模式相当多,这里先将主流程的三种设计模式,也是相对来说在业务系统里最容易被运用的设计模式进行说明。

  • 策略模式 (Strategy Pattern) :用于封装不同的支付算法。每种支付方式(微信支付、支付宝支付)都对应一个具体的策略实现。这样,支付的算法可以独立于使用它的客户端而变化。
  • 工厂模式 (Factory Pattern) :用于创建具体的支付策略实例。客户端不需要知道具体创建哪个支付策略,只需要告诉工厂它需要哪种支付方式,工厂就会返回对应的策略实例。
  • 适配器模式 (Adapter Pattern) :用于解决不同支付渠道API接口不兼容的问题。当我们需要接入一个接口规范与我们系统内部标准不一致的第三方支付服务时,适配器模式可以充当一个转换器,使得原本不兼容的接口可以协同工作。

下面,我们结合实际的项目代码,逐步展示如何应用这三种设计模式。

第一步:策略模式------封装支付行为

策略模式的核心在于定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。

1. 定义支付策略接口 (PaymentStrategy)

我们首先定义一个统一的支付策略接口,它包含所有支付方式都需要实现的通用方法,例如支付(pay)和查询(query)。

Java

java 复制代码
package com.example.payment.strategy;

import com.example.payment.model.PaymentRequest;
import com.example.payment.model.PaymentResponse;

/**
 * 支付策略接口
 * 定义了所有支付方式必须实现的通用方法。
 */
public interface PaymentStrategy {

    /**
     * 获取该策略支持的支付类型
     * @return 支付类型标识,例如WECHAT_PAY,ALI_PAY等
     */
    PayTypeEnum getPayType();

    /**
     * 执行支付操作
     * @param request 支付请求参数
     * @return 支付响应结果
     */
    PaymentResponse pay(PaymentRequest request);
}
  • 说明getPayType() 方法用于标识不同的策略实现,方便后续的工厂进行查找。pay() 方法是核心的支付逻辑。

2. 实现具体的支付策略

目前我们有两种支付方式需要对接,一个微信一个支付宝。

接下来,我们为这两种支付方式创建具体的策略实现类。

微信支付策略 (WeChatPayStrategy)

Java

java 复制代码
package com.example.payment.strategy.impl;

import com.example.payment.model.PaymentRequest;
import com.example.payment.model.PaymentResponse;
import com.example.payment.strategy.PaymentStrategy;
import org.springframework.stereotype.Service;

/**
 * 微信支付策略实现
 */
@Service
public class WeChatPayStrategy implements PaymentStrategy {

    @Override
    public PayTypeEnum getPayType() {
        return PayTypeEnum.PAY_TYPE;
    }

    @Override
    public PaymentResponse pay(PaymentRequest request) {
        // 1. 调用微信支付SDK的预下单接口
        System.out.println("正在使用微信支付...");
        System.out.println("支付金额: " + request.getAmount());

        // 2. 构造并返回响应
        PaymentResponse response = new PaymentResponse();
        response.setSuccess(true);
        response.setMessage("微信支付成功");
        return response;
    }
}

支付宝支付策略 (AliPayStrategy)

Java

java 复制代码
package com.example.payment.strategy.impl;

import com.example.payment.model.PaymentRequest;
import com.example.payment.model.PaymentResponse;
import com.example.payment.strategy.PaymentStrategy;
import org.springframework.stereotype.Service;

/**
 * 支付宝支付策略实现
 */
@Service
public class AliPayStrategy implements PaymentStrategy {

    @Override
    public PayTypeEnum getPayType() {
        return PayTypeEnum.ALI_PAY;
    }

    @Override
    public PaymentResponse pay(PaymentRequest request) {
        // 1. 调用支付宝SDK的支付接口
        System.out.println("正在使用支付宝支付...");
        System.out.println("支付金额: " + request.getAmount());

        // 2. 构造并返回响应
        PaymentResponse response = new PaymentResponse();
        response.setSuccess(true);
        response.setMessage("支付宝支付成功");
        return response;
    }
}
  • 说明

    • 我们使用@Service注解将这两个策略实现类注册为Spring Bean。
    • 每个类都实现了PaymentStrategy接口,并提供了具体的支付逻辑。在实际项目中,这里会调用相应第三方支付平台的SDK。

策略模式的好处

  • 关注点分离:每种支付方式的实现逻辑被封装在各自的类中,与主业务流程分离。
  • 易于扩展 :当需要支持一种新的支付方式时,只需创建一个新的策略类实现PaymentStrategy接口,而无需修改任何现有代码,这完全符合开闭原则。

有没有发现优化点:

考验下,上面的示例代码,是不是存在一些可以优化的点?

如果没有发现的话,不着急,看评论区。

第二步:工厂模式------创建策略实例

虽然我们有了不同的支付策略,但业务代码如何根据实际参数来获取正确的策略实例呢?

这时,工厂模式就派上用场了。

我们将创建一个工厂类,它负责根据传入的支付类型来创建并返回相应的支付策略对象。

支付策略工厂 (PaymentStrategyFactory)

Java

java 复制代码
package com.example.payment.factory;

import com.example.payment.strategy.PaymentStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 支付策略工厂
 * 负责根据支付类型创建并返回对应的支付策略实例。
 */
@Component
public class PaymentStrategyFactory {

    private final Map<PayTypeEnum, PaymentStrategy> strategyMap = new HashMap<>();

    /**
     * 利用Spring的依赖注入特性,在构造函数中注入所有PaymentStrategy的实现。
     * Spring会自动查找所有实现了PaymentStrategy接口的Bean,并将它们注入进来。
     * @param strategySet 包含所有支付策略实现的集合
     */
    @Autowired
    public PaymentStrategyFactory(Set<PaymentStrategy> strategySet) {
        strategySet.forEach(strategy -> this.strategyMap.put(strategy.getPayType(), strategy));
    }

    /**
     * 根据支付类型获取对应的支付策略
     * @param payType 支付类型标识
     * @return 对应的支付策略实例
     * @throws IllegalArgumentException 如果找不到对应的策略
     */
    public PaymentStrategy getStrategy(PayTypeEnum payType) {
        PaymentStrategy strategy = strategyMap.get(payType);
        if (strategy == null) {
            throw new IllegalArgumentException("Unsupported payment type: " + payType);
        }
        return strategy;
    }
}
  • 说明

    • 这里利用了Spring框架的依赖注入特性。构造函数PaymentStrategyFactory(Set<PaymentStrategy> strategySet)会自动注入所有实现了PaymentStrategy接口的Bean;
    • 我们将所有策略实例存储在一个Map中,键是支付类型(例如WECHAT_PAY),值是对应的策略对象;
    • getStrategy(PayTypeEnum payType)方法提供了一个统一的入口,可以根据支付类型快速查找到相应的策略;

工厂模式的好处

  • 解耦:业务代码(调用方)与具体的策略实现类解耦。业务侧不再需要关心如何创建策略对象,只需要通过工厂获取即可。
  • 集中管理:对象的创建逻辑被集中在工厂类中,使得对象的创建和管理更加方便。

第三步:适配器模式------兼容异构接口

假设现在我们需要接入一个新的第三方支付渠道,例如UnionPay(银联支付)。但是,这个UnionPay提供的SDK接口与我们系统内部定义的PaymentStrategy接口完全不兼容。

UnionPay的SDK可能是这样的:

Java

csharp 复制代码
package com.example.payment.sdk.unionpay;

// 这是一个模拟的、我们无法修改的第三方SDK类
public class UnionPaySDK {
    public void submitPayment(String orderId, double totalAmount) {
        System.out.println("正在通过银联SDK进行支付...");
        System.out.println("订单号: " + orderId + ", 支付金额: " + totalAmount);
    }
}

这个UnionPaySDKsubmitPayment方法签名与我们的PaymentStrategy接口中的pay(PaymentRequest request)完全不同,直接让UnionPayStrategy实现PaymentStrategy会很困难。

这时就需要适配器模式来登场了。

创建适配器 (UnionPayAdapter)

我们创建一个适配器类UnionPayAdapter,它实现我们的PaymentStrategy接口,并在内部持有并调用UnionPaySDK的实例。

Java

java 复制代码
package com.example.payment.adapter;

import com.example.payment.model.PaymentRequest;
import com.example.payment.model.PaymentResponse;
import com.example.payment.sdk.unionpay.UnionPaySDK;
import com.example.payment.strategy.PaymentStrategy;
import org.springframework.stereotype.Service;

/**
 * 银联支付适配器
 * 实现了我们系统内部的PaymentStrategy接口,同时在内部调用了不兼容的UnionPaySDK。
 */
@Service
public class UnionPayAdapter implements PaymentStrategy {

    // 内部持有一个第三方SDK的实例
    private final UnionPaySDK unionPaySDK = new UnionPaySDK();

    @Override
    public PayTypeEnum getPayType() {
        return PayTypeEnum.UNION_PAY;
    }

    @Override
    public PaymentResponse pay(PaymentRequest request) {
        // 在适配器内部,将我们系统的请求模型转换为第三方SDK需要的参数
        String orderId = request.getOrderId();
        double amount = request.getAmount();

        // 调用第三方SDK的方法
        unionPaySDK.submitPayment(orderId, amount);

        // 将第三方SDK的返回结果(如果有)转换为我们系统的响应模型
        PaymentResponse response = new PaymentResponse();
        response.setSuccess(true);
        response.setMessage("银联支付成功");
        return response;
    }
}
  • 说明

    • UnionPayAdapter实现了PaymentStrategy接口,因此对于我们的支付系统来说,它不仅仅是适配器,也是一个标准的支付策略;
    • pay方法内部,它将PaymentRequest对象中的数据提取出来,转换成UnionPaySDKsubmitPayment方法所需要的参数格式,然后调用SDK的方法;
    • 这个适配器的职责,就是抹平我们系统和第三方系统之间的接口差异;

适配器模式的好处

  • 复用性:可以让我们在不修改现有代码(无论是我们自己的代码还是第三方SDK)的情况下,复用那些接口不兼容的类。
  • 隔离性 :将第三方系统的复杂性和变化隔离开。即使未来UnionPaySDK升级,接口发生变化,我们也只需要修改UnionPayAdapter这个适配器,而不会影响到系统的其他部分。

其实在这个案例中能够看出来,适配器模式与策略模式非常相似。

适配器模式的核心价值在于接口转换,这里需要理解适配器模式和策略模式的区别:

  • 策略模式关注的是算法的封装和替换,所有策略都实现相同的接口,可以互相替换
  • 适配器模式关注的是接口的兼容,将一个不兼容的接口转换为我们需要的接口

在这个案例中,UnionPayAdapter既是适配器也是策略。它作为适配器,负责将UnionPaySDK的接口适配成PaymentStrategy接口;它作为策略,可以被工厂创建并在策略上下文中使用。

整合与业务侧调用

现在,我们已经有了策略、工厂和适配器。让我们看看在业务逻辑中如何使用它们。

创建一个支付服务类 PaymentService,它将协调工厂和策略来完成支付。

Java

kotlin 复制代码
package com.example.payment.service;

import com.example.payment.factory.PaymentStrategyFactory;
import com.example.payment.model.PaymentRequest;
import com.example.payment.model.PaymentResponse;
import com.example.payment.strategy.PaymentStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 支付服务
 * 负责处理支付请求的核心业务逻辑。
 */
@Service
public class PaymentService {

    @Autowired
    private PaymentStrategyFactory strategyFactory;

    /**
     * 执行支付
     * @param request 包含支付类型和金额等信息的支付请求
     * @return 支付结果
     */
    public PaymentResponse processPayment(PaymentRequest request) {
        // 1. 从工厂获取对应的支付策略
        PaymentStrategy strategy = strategyFactory.getStrategy(request.getPayType());

        // 2. 执行策略的支付方法
        return strategy.pay(request);
    }
}

最后,在Controller中调用PaymentService,这里的代码就忽略了。

现在,当一个支付请求到达PaymentController时:

  1. PaymentService会接收到请求。
  2. PaymentServicePaymentStrategyFactory请求一个支付策略,它只需要传递支付类型字符串(如 "ALI_PAY")。
  3. PaymentStrategyFactory查找并返回对应的策略Bean(AliPayStrategy的实例)。
  4. PaymentService调用该策略实例的pay方法,执行实际的支付逻辑。

整个过程清晰、流畅,并且高度可扩展。

如果未来需要支持信用卡支付,并且其SDK接口也不兼容,我们只需要新增一个CreditCardSDK类和一个CreditCardAdapter类,最多再额外改下支付方式的枚举,系统其他部分的代码完全不需要做过多的改动。

仔细看下来,其实可以发现这整套重构后的流程充分满足了我们一开始遵循的三个理念:

  • 单一职责:每个类都有明确的职责边界
  • 开闭原则:新增支付方式时,无需修改现有代码
  • 依赖倒置:高层模块不依赖具体实现,而是依赖抽象接口

正常来讲,如果想写出健壮又优雅的代码,这三个原则是绕不开的要点。

关于真实业务场景的补充说明

需要注意的是,真实的订单支付业务往往比这里展示的复杂得多。完整的支付流程通常包括:

  • 一连串复杂的前置校验:用户身份验证、订单状态检查、库存校验、风控检测等
  • 核心支付:调用第三方支付接口(本文重点讲解的部分)
  • 后置流程:支付结果处理、订单状态更新、库存扣减、消息通知、日志记录、经验值增加等

对于这些复杂的业务流程,我们可以考虑使用:

  • 责任链模式来处理前置校验的多个步骤
  • 观察者模式来处理支付成功后的多种后续操作
  • 模板方法模式来定义支付流程的骨架,让子类实现具体细节

这里我们专注于支付策略的核心逻辑,让大家更好地理解三种设计模式的配合使用。

其他的模式,我们后面再开一篇单独来讲。

写在最后

这次对支付流程的重构虽然只是工作中的一个小插曲,但收获还是不少的。

面对那堆混乱的if-else代码时,一开始确实有些头疼。但仔细梳理后逐步重构,最终让整个代码结构变得清晰可维护,不仅成就感拉满,整个过程对我们的系统架构能力也是一次不错的提升。

设计模式是前辈们总结出来的各种最佳实践,每一种模式都有其适用的场景和解决的问题。但也不能为了套模式而强行去用,关键还是要理解本质,在合适的场景下选择合适的方案。

这次分享的工厂模式、策略模式、适配器模式只是我们重构过程中用到的一部分。后续我会继续整理责任链模式、模板方法模式、观察者模式等其他模式的应用,形成一个完整的重构案例分析。

希望这篇文章能够给遇到类似问题的同学一些参考和启发。

如果大家在实际项目中也遇到了复杂的业务逻辑需要重构,不妨也这样来分析和解决。

也希望大家在日常开发中能够多思考、多总结,不断提升自己的代码设计能力,写出更加优雅和可维护的代码。

相关推荐
超浪的晨17 分钟前
Java 实现 B/S 架构详解:从基础到实战,彻底掌握浏览器/服务器编程
java·开发语言·后端·学习·个人开发
Littlewith28 分钟前
Java进阶3:Java集合框架、ArrayList、LinkedList、HashSet、HashMap和他们的迭代器
java·开发语言·spring boot·spring·java-ee·eclipse·tomcat
追逐时光者1 小时前
一款超级经典复古的 Windows 9x 主题风格 Avalonia UI 控件库,满满的回忆杀!
后端·.net
进击的码码码码N2 小时前
HttpServletRequestWrapper存储Request
java·开发语言·spring
weixin_lynhgworld2 小时前
旧物回收小程序系统开发——开启绿色生活新篇章
java·小程序·生活
Python涛哥2 小时前
go语言基础教程:【1】基础语法:变量
开发语言·后端·golang
野蛮人6号2 小时前
黑马点评系列问题之p44实战篇商户查询缓存 jmeter如何整
java·redis·jmeter·黑马点评
我命由我123452 小时前
PostgreSQL 保留关键字冲突问题:语法错误 在 “user“ 或附近的 LINE 1: CREATE TABLE user
数据库·后端·sql·mysql·postgresql·问题·数据库系统
德育处主任Pro2 小时前
亚马逊云科技实战架构:构建可扩展、高效率、无服务器应用
科技·架构·serverless
书唐瑞3 小时前
Percona pt-archiver 出现长事务
java·服务器·数据库