类适配器和对象适配器有什么区别?
简化版
类适配器通过继承被适配者并实现目标接口来适配,对象适配器通过组合持有被适配者来适配。类适配器依赖继承,耦合更强且受语言单继承限制;对象适配器依赖组合,更灵活,工程中更常用。
详细版
类适配器的结构是:
Adapter extends Adaptee implements Target
它直接继承被适配者,所以可以复用父类方法,也可以重写部分行为。但在 Java 这类单继承语言中,如果 Adapter 已经继承了 Adaptee,就不能再继承其他类。
对象适配器的结构是:
Adapter implements Target
Adapter has-a Adaptee
它通过成员变量持有被适配者,把目标接口调用委托给被适配者。对象适配器更符合组合优于继承的原则,也更容易替换不同 Adaptee。
面试建议回答:类适配器适合被适配者稳定、需要复用或改写父类行为的场景;对象适配器适合集成第三方对象、多实现切换、运行时替换等场景,真实项目更推荐对象适配器。
完整版教学
一、类适配器靠继承完成转换
类适配器的写法大概是:
interface Target {
void request();
}
class Adaptee {
void specificRequest() {
System.out.println("已有能力");
}
}
class ClassAdapter extends Adaptee implements Target {
public void request() {
specificRequest();
}
}
客户端调用 Target.request(),实际执行的是 Adaptee.specificRequest()。
这种方式简单直接,但 Adapter 和 Adaptee 形成了继承关系。
二、对象适配器靠组合完成转换
对象适配器写法是:
class ObjectAdapter implements Target {
private final Adaptee adaptee;
ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
}
这种方式不继承 Adaptee,而是持有一个 Adaptee 对象。
好处是:只要对象实现了需要的能力,就可以传进来。适配器不和某个具体父类强绑定。
三、继承和组合带来的差异
类适配器的特点:
- 能直接使用父类方法;
- 可以重写父类部分行为;
- 对 Adaptee 耦合较强;
- 受单继承限制;
- 不方便运行时替换被适配者。
对象适配器的特点:
- 通过组合复用已有对象;
- 可以适配 Adaptee 的子类或不同实现;
- 更容易依赖注入;
- 运行时可以替换被适配对象;
- 更符合组合优于继承。
四、为什么工程中更常用对象适配器
真实项目中,被适配者经常是第三方 SDK 客户端、远程服务代理或已有组件。
这些对象通常由 Spring 容器、工厂或配置创建。适配器只需要把它注入进来:
@Component
class WechatPayAdapter implements PayService {
private final WechatPayClient client;
WechatPayAdapter(WechatPayClient client) {
this.client = client;
}
}
这种方式更符合依赖注入和单元测试习惯。测试时可以传入 mock 的 client,不需要真的继承第三方类。
五、类适配器也不是完全没用
如果被适配者是一个稳定基类,并且你确实需要重写它的部分行为,类适配器也有价值。
比如某些老系统基类已经提供了大量受保护方法,适配器继承它可以比较方便地复用内部能力。
但这类场景要谨慎,因为继承会让 Adapter 暴露在 Adaptee 的变化影响下。父类一变,适配器可能被动受到影响。
六、面试时怎么回答更稳
可以按这个逻辑答:
- 类适配器用继承;
- 对象适配器用组合;
- 类适配器耦合强,受单继承限制;
- 对象适配器灵活,可替换,可注入;
- 工程中更推荐对象适配器。
这比只背“一个继承一个组合”更完整。
七、继承适配与组合适配的约束
对象适配器可以在运行时接收 3 个不同 Adaptee 实现;类适配器编译期只能继承选定父类,Java 还受单继承限制。
class adapter: Adapter extends Adaptee; object adapter: Adapter -> adaptee field
适配器的验收标准不是“调用成功”,而是 Target 的业务语义在转换前后保持成立。参数、单位、时区、精度、空值、错误码和资源所有权都应逐项形成契约样例,否则最危险的错误会以“成功响应”的形式潜伏。
八、语义边界与契约样例验证
| 检查维度 | 应确认的内容 |
|---|---|
| 机制正确性 | 选择依据是复用方式和替换需求,不是简单背“对象一定优于类”。 |
| 适用边界 | 类适配器可覆写受保护行为但耦合更强;对象适配器更易替换、测试和组合,工程中通常优先。 |
| 测试证据 | 对同一业务样例验证转换前后的单位、精度、空值和异常语义,并覆盖不可逆转换 |
| 工程代价 | 重点统计字段映射与对象分配成本、外部模型变更影响面,以及多层版本适配造成的认知债务 |
易错点:选择依据是复用方式和替换需求,不是简单背“对象一定优于类”。
九、常见误区与追问
- 误区:类适配器在 Java 中依靠多重类继承。 Java 不支持多重类继承,通常通过继承 Adaptee、实现 Target 来完成。
- 误区:适配器只需要让方法签名能够编译。 真正的适配还包括单位、时区、错误码、空值、资源释放和幂等语义;签名一致不代表行为兼容。
- 追问:什么时候类适配器仍有价值? 需要利用 protected 扩展点且父类稳定、不会占用其他继承需求时可以考虑。
- 追问:适配器应该放在哪一层? 通常放在系统边界或防腐层,让业务层只依赖 Target,避免第三方模型和异常向内扩散。
- 追问:适配器测试为什么需要契约样例? 因为类型相同仍可能单位、精度或错误语义不同;契约样例能证明转换后的行为而不只是编译通过。
十、加强记忆
类适配器记成“继承旧接口,实现新接口”,对象适配器记成“实现新接口,持有旧对象”。类适配器贴得近,能改父类行为但耦合强;对象适配器隔得开,组合灵活,工程里更常用。