4、平台抽象层设计:抽象层的作用、如何设计平台无关接口、接口隔离原则、依赖注入

好,咱们今天聊聊平台抽象层。说实话,这是跨平台开发里最核心的一环,也是我踩坑最多的地方。

你想想看,我们做跨平台,最怕什么?最怕代码写死了,换个平台就得重写。抽象层就是来解决这个问题的——它像一层“缓冲垫”,把业务逻辑和平台细节隔开。

抽象层到底在干什么?

说白了,抽象层就是定义一套“接口契约”。

业务层只跟这套契约打交道,不关心底层是 iOS 还是 Android,是 Windows 还是 Linux。底层怎么实现?那是抽象层实现类的事。

举个例子。我在项目中做过一个文件读写模块。业务层只需要调用 saveFile() 和 readFile(),至于底层是用 NSFileManager 还是 java.io.File,业务层完全不知道。这就是抽象层的作用——解耦。

核心思想: 业务逻辑只依赖抽象,不依赖具体实现。

如何设计平台无关接口?

设计平台无关接口,我个人习惯遵循几个原则:

  • 接口要小,职责要单一。 一个接口只做一件事。别搞那种“万能接口”,什么都能干,最后谁也接不住。
  • 参数类型要通用。 尽量用基础类型(int、string、byte[]),别用平台特有的类型。比如 Android 的 Bitmap,iOS 的 UIImage,这些就别出现在接口里了。
  • 返回值要稳定。 返回错误码或者自定义异常,别把平台异常直接抛出去。

来看一个我常用的接口设计示例:

// 平台无关的网络请求接口
public interface INetworkService {
    // 异步请求,返回结果通过回调
    void request(String url, 
                 Map<String, String> headers, 
                 INetworkCallback callback);
    
    // 取消请求
    void cancel(String requestId);
}

// 回调接口也是平台无关的
public interface INetworkCallback {
    void onSuccess(String response);
    void onError(int errorCode, String errorMessage);
}

你看,这里没有出现 NSURLSession,也没有 OkHttp。接口只定义了“做什么”,不关心“怎么做”。

接口隔离原则:别让调用方知道太多

接口隔离原则(ISP)在抽象层设计里特别重要。什么意思呢?就是不要让调用方依赖它不需要的方法。

我曾经见过一个项目,一个 IDeviceInfo 接口里塞了二十多个方法:获取屏幕尺寸、获取电池电量、获取设备型号、获取传感器列表……结果每个平台实现类都得实现所有方法,哪怕有些方法根本用不上。

正确的做法是:把大接口拆成小接口。

错误做法 正确做法
IDeviceInfo(20+方法) IScreenInfo、IBatteryInfo、ISensorInfo
调用方被迫依赖不需要的方法 调用方只依赖自己需要的接口
修改一个方法影响所有实现 修改只影响相关接口

嗯,这里要注意:接口拆得太细也不行,会变成“接口爆炸”。找到一个合适的粒度,需要经验积累。

依赖注入:让抽象层活起来

接口定义好了,谁来提供实现?这就是依赖注入(DI)的用武之地。

依赖注入说白了就是:别让对象自己去找依赖,而是由外部把依赖“注入”进来。

我习惯用构造函数注入,简单直接:

// 业务类只依赖接口,不依赖具体实现
public class FileManager {
    private IFileStorage storage;
    
    // 依赖通过构造函数注入
    public FileManager(IFileStorage storage) {
        this.storage = storage;
    }
    
    public void saveUserData(String data) {
        storage.write("user_data.txt", data);
    }
}

这样写的好处是:测试时可以传入 mock 实现,切换平台时只需要换一个实现类。我在项目中就用这种方式,从 Android 切到 iOS,业务代码一行没改。

小技巧: 可以用一个工厂类或者 DI 容器来管理实现类的创建。比如在应用启动时,根据当前平台注册对应的实现。

一张图看懂抽象层设计

下面这张 SVG 图展示了抽象层的核心结构:

业务逻辑层 只依赖抽象接口,不依赖平台实现 平台抽象层(接口定义) INetworkService、IFileStorage、IDatabase Android 实现 OkHttpNetworkService SharedPrefsFileStorage iOS 实现 NSURLSessionNetworkService NSFileManagerFileStorage

从这张图可以看得很清楚:业务层只跟抽象层打交道,抽象层定义接口,具体实现由各平台完成。这就是“依赖倒置”——高层模块不依赖低层模块,两者都依赖抽象。

避坑指南

我曾经踩过的坑:
  • 接口里用了平台特有的数据类型,结果换平台时接口也得改。
  • 接口方法参数太多,超过5个参数时调用方很难用。
  • 没有考虑异步场景,同步接口在 UI 线程上直接卡死。

我的建议是:接口设计时多想想“如果换一个平台,这个接口还能用吗?”如果答案是否定的,那就得重新设计。

好了,抽象层设计就聊到这里。记住一句话:抽象层是跨平台的基石,设计得好,后面一路顺畅;设计得不好,后面全是坑。

总结:
  • 抽象层的作用:隔离业务逻辑和平台细节
  • 设计平台无关接口:用通用类型、单一职责、稳定返回值
  • 接口隔离原则:拆大接口为小接口,避免调用方依赖不需要的方法
  • 依赖注入:通过构造函数或 DI 容器注入实现,方便测试和切换平台

可可精品资料库