1 解决的问题

生活中的电压,中国是 220V 的电压,其他国家也都不一定一样。而我们的手机不支持 220V 电压充电,那么就创建一个适配器,将其转换,使得可以供手机充电。同样,对于平时的代码中,也有类似的情况。例如,别人 jar 包中提供的对象不是很符合你需要的情况,比如接口不大好,那么自己就可以写一个适配器类来适配该接口。

两种方法,一是继承需要适配的类,重写方法,但是 Java 单继承,一般不使用。二是将被适配的类创建一个实例对象,将其作为适配类的成员变量。

解决事后补救的问题。
例如,家用电压 220V,手机充电 10V,充电器将 220V 转换到 10V。
例如,第三方库只兼容 Json 格式的数据,当前仅有 XML 格式的数据,修改第三方库一般不现实,因此添加适配器,从 XML 格式数据转换为 Json 格式数据。

2 适用场景

一般事后补救的时候用,其他时候尽可能设计完善的接口与程序。

  • 封装有缺陷的接口设计 (如依赖的外部系统在接口设计方面有缺陷,隔离缺陷,对接口进行二次封装);
  • 统一多个类的接口设计 (某功能的实现依赖多个外部系统,接口适配为统一的接口定义,多态复用);
  • 替换依赖的外部系统
  • 兼容老版本接口 (要废弃的接口不直接删除,暂时保留标注为的 deprecated,并将内部实现逻辑委托为新接口实现);
  • 适配不同格式的数据; (如 Java 中的 Arrays.asList()将数组类型数据转换为集合类型);
  • 不同接口协议转换

3 适配器模式

适配器模式的作用一般是 ** 事后补救 ** 。之前已有的内容无法进行修改,因此在已有内容的基础上进行适配。根据适配器类与适配器类的关系不同,分为 ** 类适配器 ** 与 ** 对象适配器 ** 两种,前者使用 ** 继承 ** 关系实现,后者使用 ** 组合 ** 关系实现。

3.1 类适配器

// 需要适配的接口 - 家电传入电压
public class HighVoltage {
    int getVoltage(int voltage) {
        System.out.println("传入" + voltage + "V 电压");
		return 220;
    }
}
 
// 目标接口 - 充电器
interface Charger {
    int getChargerVoltage(int voltage);
}
 
// 适配器角色 - 手机充电器
public class PhoneCharger extends HighVoltage implements Charger {
    @Override
    public int getChargerVoltage(int voltage) {
        // 可调用父类方法或直接进行重写
        int inVoltage = super.getVoltage(voltage);
        System.out.println("适配器工作中...");
        System.out.println("传入" + voltage + "V 电压");
		return 10;
    }
}
 
// 测试用例
public class AdapterTest {
    public static void main(String[] args) {
        Charger charger = new PhoneCharger();
        int voltage = charger.showChargerVoltage(220);
    }
}

角色如下:

  • Target (目标接口) → 客户所期待的接口,目标是接口;
  • Adaptee (需要适配的类) → 又称适配者类;
  • Adapter (适配器) → 并继承适配者类,实现目标接口,按需重写、调用方法;

3.2 对象适配器

对象适配器支持传入被适配器对象,可以灵活地做到多种被适配接口的适配,而类适配器直接继承,无法动态修改(Java 不支持多继承),所以对象适配器平时用得多一些。

public class ObjectCharger implements Charger {
    private HighVoltage highVoltage;
 
    public ObjectAdapter(HighVoltage highVoltage) {
        this.highVoltage = highVoltage;
    }
 
    @Override
    public int getChargerVoltage(int voltage) {
        highVoltage.getVoltage(voltage);
        System.out.println("适配器工作中...");
        System.out.println("传入" + voltage + "V 电压");
		return 10;
    }
}
 
// 测试用例
public class AdapterTest {
    public static void main(String[] args) {
        Charger charger = new PhoneCharger(new HighVoltage());
        charger.showChargerVoltage(220);
    }
}

角色如下:

  • Target (目标接口) → 客户所期待的接口,可以是具体或抽象的类,也可以是接口;
  • Adaptee (需要适配的类) → 又称适配者类;
  • Adapter (适配器) → 包装一个需要适配的对象,把原接口转换成目标接口;

3.3 单接口适配器模式

又称 缺省适配器模式,就是目标接口有多个方法,用一个抽象类实现接口,重写每个方法提供默认实现(空方法),子类按需重写父类方法。

比如上面的例子,中文也有很多类型啊,普通话、广州话、潮汕话、客家话、上海话等,但现在只需要转成普通话:

interface ChineseTarget {
    void speakChinese(String talk); // 普通话
    void speakCantonese(String talk);   // 广州话
    void speakChiuchow(String talk);    // 潮汕话
}
 
public abstract class BaseAdapter implements ChineseTarget {
    @Override public void speakChinese(String talk) { }
    @Override public void speakCantonese(String talk) { }
    @Override public void speakChiuchow(String talk) { }
}
 
public class CantoneseTranslator extends BaseAdapter {
    private English english;
 
    public CantoneseTranslator(English english) { this.english = english; }
 
    @Override public void speakCantonese(String talk) {
        english.speakEnglish(talk);
        System.out.println("假装请求了翻译接口,输出翻译结果:【广州话】玛卡巴卡");
    }
}
 
// 测试用例
public static void main(String[] args) {
    BaseAdapter translator = new CantoneseTranslator(new English());
    translator.speakCantonese("Ma ka ba ka");
}
复制代码

优点

  • 目标类与适配者类解耦,引用适配器类来重用现有待适配者类,无需修改原有结构;
  • 增加了类的透明性和复用性,将具体业务实现封装在适配器类中,适配者类改动只影响适配器类;
  • 灵活性和扩展性非常好,可以方便地替换适配器类,可将多个不同的适配者类和子类匹配到同一个目标类上;
  • 符合开闭(OCP)原则和里氏替换原则(LSP);

缺点

  • 过度嵌套会导致接口臃肿,一个目标类功能下先,会影响整条适配链;
  • 目标接口依赖太多适配接口,修改目标接口会导致所有适配接口都要定制修改;
  • Java 中,类适配器目标抽象类只能为接口,且适配者类不能为最终类(final,不能继承);

4 优缺点

优点

  • 单一职责原则。你可以将接口或数据转换代码从程序主要业务逻辑中分离。
  • 开闭原则。只要客户端代码通过客户端接口与适配器进行交互,你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。

缺点

  • 代码整体复杂度增加,因为你需要新增一系列接口和类。有时直接更改服务类使其与其他代码兼容会更简单。