2、SDK架构设计:模块化设计原则、接口设计规范、依赖管理、版本兼容性策略

好,咱们直接进入正题。SDK 的架构设计,说白了就是决定这个 SDK 能活多久、好不好用的关键。我见过太多 SDK,功能挺强,但用起来像一团乱麻,最后开发者全跑了。今天我就把这几年的血泪经验掰开揉碎讲给你听。

2.1 模块化设计原则:别把鸡蛋放在一个篮子里

模块化,听起来高大上,其实核心就一句话:高内聚,低耦合。每个模块只干一件事,并且把它干好。

我个人习惯把 SDK 拆成三层:

  • 核心层(Core):最底层的公共能力,比如网络请求、日志、加解密。这一层尽量稳定,不要轻易动。
  • 业务层(Business):封装具体的业务逻辑,比如登录、支付、分享。每个业务一个独立模块。
  • 接口层(API):对外暴露的门面,负责把内部复杂的逻辑简化成几个简单的接口。

为什么要这么分?我在项目中遇到过,一个团队把网络请求和支付逻辑写在一个类里。后来要换网络库,结果支付模块也得跟着改,牵一发动全身,改了两周还没改完。这就是典型的耦合过重。

核心原则:模块之间只能通过接口通信,不能直接依赖内部实现。就像你点外卖,只关心 App 上的菜单和下单按钮,不关心厨房里怎么炒菜。

这里我画了一张图,帮你理解模块间的依赖关系:

接口层 (API) 登录模块 支付模块 分享模块 核心层 (网络、日志、加解密)

小技巧:每个模块都独立编译成一个 jar/aar。这样测试的时候,哪个模块出问题就修哪个,不会影响其他模块。

2.2 接口设计规范:少即是多

接口设计,我踩过的坑最多。你想想看,一个 SDK 如果暴露了几十个接口,开发者光看文档就得看半天,谁还有耐心用?

我总结了几条铁律:

  1. 接口数量要少:能 3 个接口搞定的事,绝不用 5 个。多一个接口,就多一份维护成本。
  2. 命名要直观:比如 login() 就比 authenticateUser() 好懂。别搞那些花里胡哨的命名。
  3. 参数要简单:能用基本类型就别用复杂对象。实在不行,用 Builder 模式。
  4. 返回值要明确:成功返回数据,失败返回错误码和错误信息。别返回 null 让开发者猜。

举个例子,一个支付接口,我见过有人这么设计:

// 不好的设计
public void pay(String orderId, String userId, String amount, 
                String channel, Map<String, Object> extraParams, 
                Callback callback) {
    // 一堆逻辑...
}

// 好的设计
public void pay(PayRequest request, PayCallback callback) {
    // 逻辑清晰
}

// 配合 Builder
public class PayRequest {
    private String orderId;
    private String userId;
    private double amount;
    private PayChannel channel;
    
    private PayRequest(Builder builder) {
        this.orderId = builder.orderId;
        this.userId = builder.userId;
        this.amount = builder.amount;
        this.channel = builder.channel;
    }
    
    public static class Builder {
        // 省略 Builder 实现
    }
}

注意:接口一旦发布,就不要轻易修改。我曾经因为改了一个接口的参数类型,导致所有接入方都要重新编译,被骂了一周。所以接口设计一定要想清楚再发布。

2.3 依赖管理:别让开发者陷入依赖地狱

依赖管理,说白了就是你的 SDK 用了哪些第三方库。这个问题处理不好,开发者接入你的 SDK 时,可能会遇到各种版本冲突。

我建议遵循这几个原则:

  • 最小依赖原则:能自己写的就别引入第三方。比如一个简单的 JSON 解析,完全没必要引入 Gson 或 Jackson,自己写个轻量级的解析器就行。
  • 依赖版本要宽松:尽量用 + 号或者范围版本,比如 1.x,而不是固定死 1.2.3。这样能减少版本冲突。
  • 避免传递依赖:你的 SDK 依赖了 A 库,A 库又依赖了 B 库。如果开发者项目里也有 B 库,就可能冲突。所以尽量把依赖打平。

举个例子,在 Maven 中配置依赖:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>my-sdk</artifactId>
    <version>2.0.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </exclusion>
    </exclusions>
</dependency>

避坑指南:我曾经在 SDK 里依赖了 OkHttp 3.x,结果接入方项目里用的是 OkHttp 4.x,两个版本不兼容,导致网络请求全部失败。后来我学乖了,要么自己封装网络层,要么用 compileOnly 让接入方自己提供。

2.4 版本兼容性策略:向前兼容是底线

版本兼容性,这是 SDK 发布中最头疼的问题。你想想看,你的 SDK 升级了,但接入方不想升级,怎么办?

我常用的策略有这几种:

策略 说明 适用场景
语义化版本 主版本.次版本.修订号,比如 2.1.3 所有 SDK 都推荐使用
向后兼容 新版本必须兼容旧版本的接口 主版本号不变时
废弃标记 用 @Deprecated 标记旧接口,并给出迁移指南 接口需要删除时
多版本共存 同时维护多个主版本,比如 v1.x 和 v2.x 重大变更时

具体怎么做?我举个例子:

// 旧接口,标记为废弃
@Deprecated
public void login(String username, String password) {
    // 内部调用新接口
    login(new LoginRequest.Builder()
            .setUsername(username)
            .setPassword(password)
            .build());
}

// 新接口
public void login(LoginRequest request) {
    // 新的登录逻辑
}

我的经验:每次发布新版本,我都会写一个 CHANGELOG,详细列出新增、废弃、删除的内容。这样接入方升级时,一眼就能看出哪些地方需要改。别小看这个文档,它能帮你省掉 80% 的答疑时间。

嗯,关于 SDK 架构设计,核心就是这些。模块化让你好维护,接口规范让你好用,依赖管理让你不冲突,版本策略让你能持续迭代。把这四点吃透了,你的 SDK 架构就稳了。

可可精品资料库