五一假期,在深圳图书馆的知识海洋中遨游

需要的东西就在面前,但却不能使用,而短时间又无法改造它,于是我们就想办法适配它

适配器模式
【适配器模式(Adapter Pattern) 】
适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况
适配器模式有两种类型:
- 类适配器模式
- 对象适配器模式
下图为对象适配器模式,通过组合的方式来适配,个人觉得这种方式比类适配器模式好很多,可能是出于Java单继承父类的限制吧。

鸭子与火鸡场景
在一个动物园管理系统中,我们原本只有鸭子(Duck)的接口和实现。但是有一天,动物园引进了一批火鸡(Turkey),而系统的其他部分都是基于 Duck 接口设计的。
问题: 如何让火鸡能够像鸭子一样被系统使用,而不需要修改现有的客户端代码?
解决方案: 使用适配器模式,创建一个"火鸡适配器",让火鸡可以伪装成鸭子。
代码实现
目标接口:Duck(鸭子)
这是客户端期望使用的接口:
java
public interface Duck {
/**
* 呱呱叫
*/
void quack();
void fly();
}
具体实现:MallardDuck(绿头鸭)
这是原有的鸭子实现:
java
/**
* 绿头鸭
*/
public class MallardDuck implements Duck{
@Override
public void quack() {
System.out.println("呱呱呱...");
}
@Override
public void fly() {
System.out.println("绿头鸭起飞了...");
}
}
被适配者接口:Turkey(火鸡)
这是新引进的火鸡接口,与 Duck 接口不兼容:
java
/**
* 火鸡
*/
public interface Turkey {
/**
* 咯咯叫
*/
void gobble();
void fly();
}
被适配者实现:WildTurkey(野火鸡)
java
public class WildTurkey implements Turkey{
@Override
public void gobble() {
System.out.println("咯咯咯...");
}
@Override
public void fly() {
System.out.println("...");
}
}
⭐适配器:TurkeyAdapter(火鸡适配器)⭐核心
这是适配器模式的关键:让火鸡适配器实现 Duck 接口,内部持有 Turkey 对象,将 Duck 的方法调用转换为 Turkey 的方法调用:
java
@RequiredArgsConstructor
public class TurkeyAdapter implements Duck{
private final Turkey turkey;
@Override
public void quack() {
turkey.gobble(); // 将鸭子的quack()转换为火鸡的gobble()
}
@Override
public void fly() {
turkey.fly(); // 飞行方法直接委托
}
}
关键点:
TurkeyAdapter实现了Duck接口,所以它可以被当作鸭子使用- 内部持有一个
Turkey对象(组合关系) - 将
Duck接口的方法调用委托给内部的Turkey对象
测试代码
java
public class DuckTestDrive {
public static void main(String[] args) {
System.out.println("""
鸭子不够了,临时拿一只火鸡来充数
""");
List.of(new MallardDuck(), new TurkeyAdapter(new WildTurkey()))
.forEach(duck -> {
duck.quack();
duck.fly();
System.out.println("__".repeat(20));
});
}
}
运行结果
sh
鸭子不够了,临时拿一只火鸡来充数
呱呱呱...
绿头鸭起飞了...
________________________________________
咯咯咯...
野火鸡只能短暂的飞行...
________________________________________
模式分析
参与者
- Target(目标接口) :
Duck- 客户端期望使用的接口 - Adaptee(被适配者) :
Turkey- 需要被适配的现有接口 - Adapter(适配器) :
TurkeyAdapter- 将 Adaptee 转换为 Target - Client(客户端) :
DuckTestDrive- 使用 Target 接口的代码
工作流程
txt
客户端调用 Duck.quack()
↓
TurkeyAdapter.quack() 接收调用
↓
内部委托给 Turkey.gobble()
↓
WildTurkey 执行实际的咯咯叫
小结 适配器模式结构图
这是一个
对象适配器模式
sh
┌─────────────────────────────────────────────────────────────┐
│ 适配器模式结构图 │
└─────────────────────────────────────────────────────────────┘
Client (客户端)
┌──────────────────┐
│ DuckTestDrive │
│ │
│ 使用 Duck 接口 │
└────────┬─────────┘
│ uses
▼
┌─────────────────────┐
│ <<interface>> │
│ Duck │ ◄── Target (目标接口)
│ │
│ + quack(): void │
│ + fly(): void │
└─────┬──────────┬────┘
│ │
implements│ │ implements
▼ ▼
┌────────────────┐ ┌──────────────────┐
│ MallardDuck │ │ TurkeyAdapter │ ◄── Adapter (适配器)
│ (绿头鸭) │ │ (火鸡适配器) │
│ │ │ │
│ + quack() │ │ - turkey: Turkey │ ◄── 组合关系
│ + fly() │ │ │
└────────────────┘ │ + quack() │
│ + fly() │
└────────┬─────────┘
│ delegates
▼
┌─────────────────────┐
│ <<interface>> │
│ Turkey │ ◄── Adaptee (被适配者)
│ │
│ + gobble(): void │
│ + fly(): void │
└────────┬────────────┘
│
implements│
▼
┌──────────────────┐
│ WildTurkey │
│ (野火鸡) │
│ │
│ + gobble() │
│ + fly() │
└──────────────────┘
说明:
Client只依赖Target接口(Duck),不知道Adaptee(Turkey)的存在Adapter实现了Target接口,同时持有Adaptee的引用Adapter将Target的方法调用转换为Adaptee的方法调用- 这样可以在不修改客户端代码的情况下,让
Adaptee像Target一样工作
与其他模式的区别
| 模式 | 目的 | 特点 |
|---|---|---|
| 适配器 | 转换接口 | 让不兼容的接口能一起工作 |
| 装饰器 | 增强功能 | 动态地给对象添加职责 |
| 代理 | 控制访问 | 为另一个对象提供代理以控制访问 |