Skip to content
难度基础(★)
建议时长45分钟

12.5.3 例题3:适配器模式

本节例题是地址显示系统:系统已经有一个用于显示地址信息的 Address 类,现在要提供荷兰语地址显示接口,并考虑未来可能继续扩展其他语言接口。课程用这个例题训练适配器模式,同时重点讲构造函数赋值、成员属性反推、方法对应关系和父类引用指向子类对象。

适配器模式的本质

适配器不是为了创造新功能,而是为了让“已经存在但接口不匹配”的对象能被现有客户端使用。可以把它理解成翻译器:客户端想听荷兰语接口,已有类只会提供原地址接口,适配器负责把目标接口请求转成已有类的方法调用。

mermaid
flowchart LR
  Client["客户端需要 DutchAddress"] --> Target["目标接口/父类:DutchAddress"]
  Target --> Adapter["DutchAddressAdapter"]
  Adapter --> Adaptee["已有类:Address"]
  Adaptee --> M1["getStreet()"]
  Adaptee --> M2["getZip()"]
  Adaptee --> M3["getCity()"]

地址例题中的对应关系

课程强调,这道题的难点之一是先找出语言接口之间的对应关系。题干和代码中通常会给出若干相似名称:

荷兰语接口原有地址接口理解方式
straatstreet街道,拼写相近
zip / postcodepostCode邮编、邮政编码
plaatscity城市/地点

做这类题不能只盯着空,要先把这张映射表建出来。后面三个方法体空本质上都是“目标方法调用原有对象的对应方法”。

成员变量空:从构造函数反推

适配器通常需要持有被适配对象。课程中构造函数大致是:

java
public DutchAddressAdapter(Address addr) {
    address = addr;
}

如果上方缺了成员变量,就从赋值语句反推:

线索结论
等号左侧 address 被赋值address 是当前类成员变量
等号右侧 addr 来自参数列表addr 是构造函数参数
参数类型是 Address成员变量类型也应为 Address

因此成员变量通常是:

java
private Address address;

如果写成 this.address = addr; 也能表达当前对象成员,但前提是成员变量已经定义。

方法体空:只做接口转换

适配器方法不是自己凭空生成地址,而是调用已有 Address 对象的方法。

java
class DutchAddressAdapter extends DutchAddress {
    private Address address;

    public DutchAddressAdapter(Address addr) {
        address = addr;
    }

    public String straat() {
        return address.getStreet();
    }

    public String postcode() {
        return address.getPostCode();
    }

    public String plaats() {
        return address.getCity();
    }
}

填空时的推导口诀:

  1. 目标方法名告诉你“客户端想要什么”。
  2. 成员对象告诉你“当前能调用谁”。
  3. 映射表告诉你“应该调用哪个已有方法”。

实例化空:父类引用指向子类对象

课程最后强调的难点是主函数中的实例化。测试方法可能要求传入 DutchAddress 类型,但真正要创建的是 DutchAddressAdapter,因为只有适配器才能包住已有 Address 对象。

典型写法:

java
Address addr = new Address(...);
DutchAddress addrAdapter = new DutchAddressAdapter(addr);
test(addrAdapter);

这体现了面向对象中的多态:变量类型可以是父类/接口,实际对象可以是子类/实现类。

位置应该看什么例题结论
左侧类型test() 需要什么参数类型DutchAddress
对象名后文调用传入的变量名addrAdapter
new 后的类型真正能完成转换的是谁DutchAddressAdapter
构造参数适配器构造函数需要什么已有 Address addr

适配器模式的两种实现

类型结构优点局限
类适配器Adapter 继承 Adaptee,并实现 Target结构直接依赖继承,灵活性低;Java 单继承限制明显
对象适配器Adapter 持有 Adaptee 对象符合组合复用原则,可替换被适配对象多一层成员引用和委托

考试中更常见的是对象适配器,因为代码填空可以考成员变量、构造函数赋值、方法委托等多个点。

与桥接模式的区别

适配器和桥接都可能出现“一个对象调用另一个对象”的代码,但动机完全不同:

模式发生时间解决问题代码直觉
适配器常在已有系统之后补救已有接口与目标接口不兼容“把 A 翻译成 B”
桥接常在设计初期规划两个维度独立变化导致类爆炸“抽象层连接实现层”

如果题干出现“已经设计并实现了某类,现在要求提供另一种接口”,优先想到适配器。

例题

单选
适配器模式的核心意图是:

自查清单

  1. 能否从构造函数赋值语句反推出缺失成员变量?
  2. 能否把目标接口方法与已有类方法建立对应关系?
  3. 能否写出 父类类型 变量名 = new 子类类型(已有对象)
  4. 能否区分适配器模式和桥接模式的动机?