8.Android 设计模式 适配器模式 在商业项目中的落地

摘要: 适配器模式是解决接口不兼容的桥梁,通过包装对象转换为目标接口。如Android的RecyclerView.Adapter将数据适配到视图,或整合不同厂商SDK(如百度/讯飞语音)。该模式提升代码复用性,保持客户端简洁,是系统扩展的利器,符合开闭原则。

1.概念

适配器模式(Adapter Pattern) 是一种结构型设计模式,用于连接两个不兼容的接口。它通过将一个类的接口转换成客户端期望的另一个接口,解决接口不兼容问题,实现类之间的协同工作。

  • 核心角色

    • Target(目标接口):客户端使用的接口
    • Adaptee(被适配者):需要被适配的类
    • Adapter(适配器):实现 Target 并包装 Adapter,负责接口转换

2.在Android源码中的应用场景

经典案例:RecyclerView.Adapter

scala 复制代码
// 适配器将数据集合适配到RecyclerView的视图系统中
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    private List<String> mData; // 被适配的数据(Adaptee)

    // 创建ViewHolder(实现Target接口)
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false);
        return new ViewHolder(view);
    }

    // 绑定数据(适配过程)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.textView.setText(mData.get(position)); // 数据 -> 视图
    }
}
2.1 适配层

智仓系统:整体来看有三个角色,包括应用层、适配层服务与数据提供方 Tire1/DH) 。 其中Adapter Service 作为中间的 纽带,对上为应用层提供数据,使用 AIDL 接口跨进程通讯 。对下需要 Tire 1 /DH 接入提供数据,在 AdapterService 里面,数据提供方需要自己实现系统数据的聚合层,可能需要对接内部不 同的业务服务,具体根据各自平台的架构而定。 另外在 BeanAdapterServerSDK 内部会维护一个数据缓存,数据提供方的数据更新都会保存在缓存中, 同时上层的数据同步获取也会直接从缓存中读取,提高效率。此数据的状态准确性依赖数据提供者的状态及 时更新,对定义依赖的数据如有更新需要提供者及时向 SDK 进行更新,否则可能导致上层应用获取数据状 态不准确。

3.UML图

  • 工作流程
    Client 调用 Target.request()Adapter 实现该方法 → 内部调用 Adaptee.specificRequest() 并转换结果

4.语音项目的例子和没用设计模式的对比

在语音项目中需同时集成:

  1. 百度语音合成:支持在线REST API和离线SDK两种方式369
  2. 讯飞语音合成 :提供在线API及音频返回258
    核心问题:两厂商接口设计差异大,直接调用会导致代码臃肿、维护困难。
4.1 没有使用适配器模式
arduino 复制代码
public class PlayerControllerWithoutAdapter {
    public void play(String text, String playerType) {
        if ("baidu".equals(playerType)) {
            // 直接调用百度SDK
            BaiduPlayerAdapter.BaiduSdk baidu = new BaiduPlayerAdapter.BaiduSdk();
            baidu.startPlay(text);
        } else if ("xunfei".equals(playerType)) {
            // 直接调用讯飞SDK
            XunfeiPlayerAdapter.XunfeiSdk xunfei = new XunfeiPlayerAdapter.XunfeiSdk();
            xunfei.playAudio(text);
        }
        // 新增厂商需要修改此处代码
    }
    // 其他方法也需要重复if-else逻辑
}

没有使用适配器模式的缺陷:

  • 高耦合:业务层直接调用厂商SDK方法
  • 重复代码:相同控制逻辑(如暂停/停止)需为每个厂商重写
  • 方法爆炸 :控制器需要为每个厂商提供独立方法(playBaidu()/playXunfei()
  • 新增SDK的高成本
  • SDK接口修改
4.2 使用适配器模式
csharp 复制代码
// 1. 定义统一播放接口 (Target)
public interface AudioPlayer {
    void play(String text);
    void pause();
    void stop();
    void release();
}
typescript 复制代码
// 2. 百度播放器适配器
public class BaiduPlayerAdapter implements AudioPlayer {
    // 模拟百度SDK
    public static class BaiduSdk {
        public void startPlay(String content) {
            System.out.println("[百度] 播放中: " + content);
        }

        public void pausePlay() {
            System.out.println("[百度] 已暂停");
        }

        public void stopPlay() {
            System.out.println("[百度] 已停止");
        }

        public void release() {
            System.out.println("[百度] 资源已释放");
        }
    }

    private final BaiduSdk baiduPlayer;

    public BaiduPlayerAdapter() {
        this.baiduPlayer = new BaiduSdk();
        System.out.println("百度播放器适配器初始化完成");
    }

    @Override
    public void play(String text) {
        // 适配器将统一接口转换为百度SDK特定调用
        baiduPlayer.startPlay(text);
    }

    @Override
    public void pause() {
        baiduPlayer.pausePlay();
    }

    @Override
    public void stop() {
        baiduPlayer.stopPlay();
    }

    @Override
    public void release() {
        baiduPlayer.release();
    }
}
typescript 复制代码
// 3. 讯飞播放器适配器
public class XunfeiPlayerAdapter implements AudioPlayer {
    // 模拟讯飞SDK
    public static class XunfeiSdk {
        public void playAudio(String text) {
            System.out.println("[讯飞] 正在播放: " + text);
        }

        public void pauseAudio() {
            System.out.println("[讯飞] 暂停播放");
        }

        public void stopAudio() {
            System.out.println("[讯飞] 停止播放");
        }

        public void destroy() {
            System.out.println("[讯飞] 资源已销毁");
        }
    }

    private final XunfeiSdk xunfeiPlayer;

    public XunfeiPlayerAdapter() {
        this.xunfeiPlayer = new XunfeiSdk();
        System.out.println("讯飞播放器适配器初始化完成");
    }

    @Override
    public void play(String text) {
        // 适配器将统一接口转换为讯飞SDK特定调用
        xunfeiPlayer.playAudio(text);
    }

    @Override
    public void pause() {
        xunfeiPlayer.pauseAudio();
    }

    @Override
    public void stop() {
        xunfeiPlayer.stopAudio();
    }

    @Override
    public void release() {
        xunfeiPlayer.destroy();
    }
}
typescript 复制代码
// 4. 播放控制器(客户端)
public class PlayerController {
    private AudioPlayer currentPlayer;

    public void setPlayer(AudioPlayer player) {
        if (currentPlayer != null) {
            currentPlayer.release();
        }
        currentPlayer = player;
        System.out.println("播放器已切换");
    }

    public void playText(String text) {
        if (currentPlayer == null) {
            System.out.println("错误:未设置播放器");
            return;
        }
        currentPlayer.play(text);
    }

    public void pause() {
        if (currentPlayer != null) {
            currentPlayer.pause();
        }
    }

    public void stop() {
        if (currentPlayer != null) {
            currentPlayer.stop();
        }
    }

    public void release() {
        if (currentPlayer != null) {
            currentPlayer.release();
            currentPlayer = null;
        }
    }
}
csharp 复制代码
// 5. 音频设备管理器(可选扩展)
public class AudioDeviceManager {
    public void setOutputToSpeaker() {
        System.out.println("音频输出切换到扬声器");
    }

    public void setOutputToHeadphones() {
        System.out.println("音频输出切换到耳机");
    }
}

具体的调用:

scss 复制代码
// 6. 客户端使用示例
public class VoicePlayerDemo {
    public static void main(String[] args) {
        // 创建控制器
        PlayerController controller = new PlayerController();

        // 创建设备管理器
        AudioDeviceManager deviceManager = new AudioDeviceManager();

        // 使用百度播放器
        System.out.println("\n=== 使用百度播放器 ===");
        controller.setPlayer(new BaiduPlayerAdapter());
        deviceManager.setOutputToSpeaker();
        controller.playText("你好,欢迎使用百度语音服务");
        controller.pause();
        controller.playText("继续播放百度语音");
        controller.stop();

        // 切换到讯飞播放器
        System.out.println("\n=== 切换到讯飞播放器 ===");
        controller.setPlayer(new XunfeiPlayerAdapter());
        deviceManager.setOutputToHeadphones();
        controller.playText("科大讯飞为您服务");
        controller.pause();

        // 释放资源
        controller.release();
    }
}

5.优点

  1. 解耦性:分离客户端与被适配者
  2. 复用性:复用现有类(如不同厂商 SDK)
  3. 扩展性:轻松新增适配器(符合开闭原则)
  4. 灵活性:适配器可动态切换(如切换网络库)

6.和相似的设计模式的区别

6.1. 核心目的对比

适配器模式,策略模式,工厂模式的区别和协同使用场景! 策略模式更包含适配器模式!

arduino 复制代码
// 公共接口
interface AudioPlayer {
    void play(String text);
}

适配器模式和策略模式的对比

适配器模式实现

typescript 复制代码
// 百度SDK原生类(不兼容接口)
class BaiduSdk {
    void startBaiduPlay(String content) {
        System.out.println("百度播放: " + content);
    }
}

// 适配器:转换百度接口
class BaiduAdapter implements AudioPlayer {
    private BaiduSdk baidu = new BaiduSdk();
    
    @Override
    public void play(String text) {
        baidu.startBaiduPlay(text); // 接口转换
    }
}

// 客户端使用
AudioPlayer player = new BaiduAdapter();
player.play("Hello");

策略模式实现

arduino 复制代码
// 策略接口
interface PlayStrategy {
    void playAudio(String text);
}

// 具体策略:百度播放
class BaiduPlayStrategy implements PlayStrategy {
    @Override
    public void playAudio(String text) {
        System.out.println("百度策略播放: " + text);
    }
}

// 上下文
class PlayerContext {
    private PlayStrategy strategy;
    
    public void setStrategy(PlayStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void executePlay(String text) {
        strategy.playAudio(text);
    }
}

// 客户端使用
PlayerContext context = new PlayerContext();
context.setStrategy(new BaiduPlayStrategy()); // 设置策略
context.executePlay("Hello");

架构图:

模式 核心目的 设计原则
适配器模式 接口转换(解决不兼容问题) 接口隔离原则
工厂模式 对象创建(封装实例化过程) 单一职责原则 + 开闭原则
桥接模式 抽象/实现解耦(分离多维变化) 合成复用原则
  • 工厂模式 创建适配器对象
  • 适配器模式 统一不同厂商接口
  • 桥接模式 分离控制逻辑与播放实现
6.2 适配器模式和工厂模式的对比

适配器模式实现

kotlin 复制代码
// 适配器解决接口差异
class BaiduAdapter : AudioPlayer {
    private val baidu = BaiduSdk()
    
    override fun play(text: String) {
        baidu.startBaiduPlay(text) // 转换接口
    }
}

class XunfeiAdapter : AudioPlayer {
    private val xunfei = XunfeiSdk()
    
    override fun play(text: String) {
        xunfei.xfPlayAudio(text) // 转换接口
    }
}

// 客户端调用
val player: AudioPlayer = BaiduAdapter()
player.play("Hello")
6.3 工厂模式实现
kotlin 复制代码
// 工厂负责创建对象
object PlayerFactory {
    enum class PlayerType { BAIDU, XUNFEI }
    
    fun createPlayer(type: PlayerType): AudioPlayer {
        return when(type) {
            PlayerType.BAIDU -> BaiduSdkWrapper()
            PlayerType.XUNFEI -> XunfeiSdkWrapper()
        }
    }
}

// 包装类统一接口(需预先封装)
class BaiduSdkWrapper : AudioPlayer {
    private val baidu = BaiduSdk()
    override fun play(text: String) { 
        baidu.startBaiduPlay(text) 
    }
}

// 客户端调用
val player = PlayerFactory.createPlayer(PlayerType.BAIDU)
player.play("Hello")

6.4 协同使用场景架构图

  • 适配器模式 :关注接口转换
    "如何让百度SDK的startBaiduPlay()适配到统一的play()接口?"
  • 策略模式 :关注行为互换
    "如何在运行时切换百度播放策略和讯飞播放策略?"
  • 工厂模式 :关注对象创建
    "如何统一创建百度播放器和讯飞播放器对象?"

总结

适配器模式在 Android 开发中应用广泛,尤其在 UI 控件(RecyclerView)组件集成(音频/网络 SDK) 场景下,能有效解决接口不兼容问题,提升代码的灵活性和可维护性。通过合理使用该模式,可显著降低系统耦合度,为后续扩展留出空间。

项目的地址: github.com/pengcaihua1...

相关推荐
JohnYan7 分钟前
模板+数据的文档生成技术方案设计和实现
javascript·后端·架构
Da_秀21 分钟前
软件工程中耦合度
开发语言·后端·架构·软件工程
用户21960094442852 小时前
利用布隆过滤器设计亿级用户视频浏览历史过滤系统:方案详解与内存预估
架构
Kookoos2 小时前
ABP VNext + Tye:本地微服务编排与调试
微服务·云原生·架构·tye
秋千码途3 小时前
小架构step系列06:编译配置
架构
打好高远球4 小时前
如何用AI破解相亲信息不对称
架构
泊浮目5 小时前
未来数据库硬件-网络篇
数据库·架构·云计算
不骞5 小时前
5.solidity的数据结构
架构
星辰大海的精灵5 小时前
使用Docker和Kubernetes部署机器学习模型
人工智能·后端·架构