拒绝 If-Else 屎山:利用适配器模式(Adapter)构建第三方登录的“防腐层”实战

前言

做过第三方对接的兄弟都知道,接一个是享受,接三个是工作,接十个那就是灾难。

最近在负责公司核心的多渠道第三方登录与用户信息同步模块。我们面临的现状是:业务方要求接入七八个外部渠道(包括某头部电商、某保险平台、某社交巨头等)。

起初我觉得没啥,不就是调个 API 拿个 JSON 吗?结果拿到接口文档我就裂开了:

  • A 渠道:姓名分了"汉字"和"拼音/片假名"两个字段返回;性别用 1/2 表示。

  • B 渠道:姓名是一个合并的长字符串;性别直接给的是 male/female。

  • C 渠道:日期格式甚至是 YYYY/MM/DD,跟我们库里的 Date 完全不兼容。

如果直接在业务核心代码里写 if ("ChannelA".equals(type)) 然后去解析数据,那这块代码不出一个月就会变成无人敢动的"屎山"。

所以这里使用了适配器模式(Adapter Pattern)来构建一层防腐层(ACL, Anti-Corruption Layer)。

什么是防腐层?

我们就是DDD架构,DDD架构中的防腐层。就是防止外部的其他逻辑,污染内部的核心逻辑。你要学很多语言,不能学一个语言就写到家规里,你得有一个翻译官,翻译官就是防腐层。

其实说人话就是:别让外面的脏数据,污染了我们内部的代码。

外部渠道的数据结构是他们定的,千奇百怪;但我们系统内部的 User 模型是标准的。我需要一个中间人,把那些乱七八糟的数据,"清洗"成我们看着舒服的标准模样,然后再放进业务逻辑里。

这个"中间人",就是适配器。 适配器 + 转换数据的逻辑就是防腐层。

具体落地

第一步:定义通用的"转换器"标准

首先,利用 Java 的泛型,定义一个所有适配器都必须遵守的接口。 S 代表 Source(外部的脏数据),T 代表 Target(内部的标准数据)。

java 复制代码
/**
 * 外部用户信息适配器接口
 * @param <S> Source: 外部渠道返回的原始对象
 * @param <T> Target: 内部标准用户模型
 */
public interface ExternalUserInfoAdapter<S, T> {
    /**
     * 核心转换方法:把脏数据变成标准数据
     */
    T adapt(S source);
}

第二步:编写具体的渠道适配器

以那个"姓名分段、性别用英文"的某电商渠道为例。我写了一个专门的 Adapter 类,把脏活累活全封装在这里。

注意这里加了 @Component,交给 Spring 管理。

java 复制代码
@Component
public class ChannelAUserInfoAdapter implements ExternalUserInfoAdapter<ChannelAUserResponse, StandardUserDTO> {

    @Override
    public StandardUserDTO adapt(ChannelAUserResponse source) {
        if (source == null) return null;

        StandardUserDTO target = new StandardUserDTO();

        // 1. 处理姓名:该渠道把名字拆成了两部分,我们需要分别映射
        // 比如 source.getKanjiName() -> 映射到内部的 givenName
        // source.getKanaName() -> 映射到内部的 localGivenName
        target.setGivenName(source.getKanjiName());
        target.setLocalGivenName(source.getKanaName());

        // 2. 处理性别:把字符串转成内部枚举
        // 外部是 "male"/"female",内部是 GenderEnum
        if ("male".equalsIgnoreCase(source.getGenderStr())) {
            target.setGender(GenderEnum.MALE);
        } else if ("female".equalsIgnoreCase(source.getGenderStr())) {
            target.setGender(GenderEnum.FEMALE);
        } else {
            target.setGender(GenderEnum.UNKNOWN);
        }

        // 3. 处理日期:字符串转 Date 对象
        // 这里省略具体的 DateUtil 解析代码
        target.setBirthday(DateUtil.parse(source.getBirthStr()));

        return target;
    }
}

第三步:在业务层清爽地调用

有了适配器,Service 层的代码简直干净得令人感动。我完全不需要关心对方是给的 male 还是 1,我只管拿标准对象。

java 复制代码
@Service
public class ChannelAUserHandler {

    @Autowired
    private ChannelAUserInfoAdapter adapter; // 注入上面的适配器

    public void syncUser(String token) {
        // 1. 调用外部 RPC/API 拿到原始数据(脏数据)
        ChannelAUserResponse rawData = thirdPartyClient.getUserInfo(token);

        // 2. 一行代码完成"清洗"
        // 业务层不需要任何 if-else 来判断性别格式,适配器都搞定了
        StandardUserDTO standardUser = adapter.adapt(rawData);

        // 3. 执行核心业务(入库、更新Session等)
        userRepository.saveOrUpdate(standardUser);
    }
}

进阶思考:适配器模式 vs 策略模式

在做代码 Review 的时候,有同事问我:"这玩意儿跟策略模式(Strategy Pattern)看着很像啊,都是接口+实现类,为啥叫适配器?"

这也是面试中常考的点,我是这么区分的:

  1. 意图不同(事后 vs 事前)

适配器(Adapter)是"事后补救":两边的接口(外部API vs 内部模型)已经定死了,谁都改不了,且死活对不上。这时候只能插一个适配器在中间做胶水。

策略(Strategy)是"事前规划":我要做一件事(比如登录),但有多种做法(A渠道登录、B渠道登录)。我定义一个策略接口,可以在运行时灵活切换算法。

  1. 职责不同(数据 vs 行为)

适配器关注"数据流转":看我的 adapt 方法,输入是 A,输出是 B。它只负责类型转换(Type Conversion),不应该包含复杂的业务逻辑(比如查库、发短信),它是无副作用的。

策略关注"业务行为":策略模式的 execute 方法通常包含具体的业务逻辑。

在我的项目中,ChannelAUserInfoAdapter 负责把数据变身(适配器),而 ChannelAUserHandler 负责具体的登录流程控制(这其实更像策略模式的使用场景)。一个负责变身,一个负责打仗。

拓展 - 怎么区别不同的渠道

在API设计层面,把对应的渠道标识放到请求路径里,通过@PathVariable 获取字符串。

每个渠道的策略器中实现接口方法,getChannel。

然后把每个策略放到Factory中,Factory中实际就维护一个Map<String,Adapter> 然后根据getChannel获取String,自动找到对应的适配器。

核心逻辑,Service层只有三行。

  1. 调对面接口;
  2. 适配器转换,转换成我们系统的格式;
  3. 入库等业务操作;

总结

通过引入这层防腐层,我们后续再接入新渠道时,核心业务代码一行都不用改,只需要新增一个 XxxAdapter 类即可。

经过测算,这一波重构将新渠道的接入效率提升了约 40%。更重要的是,它把脏数据隔离在了核心业务之外,这让代码维护起来心里踏实多了。

相关推荐
布吉岛的石头2 分钟前
Java 程序员第 49 阶段11:Java 接 BERT:用 ONNX Runtime 做句向量推理
java·人工智能·python·深度学习·bert·transformer
第25小时记录5 分钟前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·python
Dmz丶9 分钟前
前端转 AI 100 天 Day 18:pytest 入门——为什么你必须要写测试
python
用户0190275816124 分钟前
如何用 Python 计算 DMA 平行线差指标?
python
哎呦,帅小伙哦31 分钟前
进程监控工具——htop
linux·运维·服务器
奈葵1 小时前
Python
开发语言·python
夏虫不可与之言冰1 小时前
Windows WSL2 (Ubuntu) 搭建 ESP‑IDF 6.1 开发环境全过程演示
linux·windows·ubuntu·esp32·乐鑫科技·乐鑫eim·乐鑫代理商
happylifetree1 小时前
Python54-55:核心语法-数据容器-字典dict-案例
python
小小张说故事1 小时前
Python 迭代器和生成器到底啥区别?3 个例子讲透,别再背概念了
python
外收内放1 小时前
Python基础语法练习题(56优化版补充)
开发语言·python