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()"]地址例题中的对应关系
课程强调,这道题的难点之一是先找出语言接口之间的对应关系。题干和代码中通常会给出若干相似名称:
| 荷兰语接口 | 原有地址接口 | 理解方式 |
|---|---|---|
straat | street | 街道,拼写相近 |
zip / postcode | postCode | 邮编、邮政编码 |
plaats | city | 城市/地点 |
做这类题不能只盯着空,要先把这张映射表建出来。后面三个方法体空本质上都是“目标方法调用原有对象的对应方法”。
成员变量空:从构造函数反推
适配器通常需要持有被适配对象。课程中构造函数大致是:
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();
}
}填空时的推导口诀:
- 目标方法名告诉你“客户端想要什么”。
- 成员对象告诉你“当前能调用谁”。
- 映射表告诉你“应该调用哪个已有方法”。
实例化空:父类引用指向子类对象
课程最后强调的难点是主函数中的实例化。测试方法可能要求传入 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” |
| 桥接 | 常在设计初期规划 | 两个维度独立变化导致类爆炸 | “抽象层连接实现层” |
如果题干出现“已经设计并实现了某类,现在要求提供另一种接口”,优先想到适配器。
例题
适配器模式的核心意图是:
自查清单
- 能否从构造函数赋值语句反推出缺失成员变量?
- 能否把目标接口方法与已有类方法建立对应关系?
- 能否写出
父类类型 变量名 = new 子类类型(已有对象)? - 能否区分适配器模式和桥接模式的动机?