适配器模式(Adapter Pattern)

将一个类的接口转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以相互合作

核心作用

  • 兼容性: 使得原本由于接口部匹配而无法一起工作的类能够协同工作。
  • 复用性: 允许复用已有的类,即使它们不符合当前系统的统一结构标准。
  • 解耦性: 避免修改已有代码的前提下实现新功能对接。

适用场景

  1. 系统需要使用一些已有类,但这些类的接口不符合系统要求
  2. 希望复用一些旧的组件或第三方库,但它们的接口与当前系统不一致
  3. 在不修改原有代码的基础上进行接口适配

模式结构

角色 描述
Target 客户端使用的接口,是期望的目标接口
Adaptee 已有的类,其接口与目标接口不兼容
Adapter 实现目标接口,并持有Adaptee对象,通过组合/继承的方式完成适配

示例

最容易理解该模式的莫过于我们常用的电源适配器(adapter),手机充电器一般都是5v左右,中国家用交流电压220v,日本100v,手机充电器代工方富士康需要生产一个适配器(降压器)来对手机进行充电,不然会过热起火。

角色 实例 作用
Target 一部手机Mobile 给手机充电
Adaptee 220v的交流电 用交流电给手机充电,但电压不兼容
Adapter 手机充电器 富士康需要制作可提供5电压的充电器
ConcreteAdapter1 220v -> 5v充电器 大陆用充电器
ConcreteAdapter2 100v -> 5v充电器 日本美国标准用充电器
ConcreteAdapter3 230v -> 5v充电器 欧洲标准用充电器

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public interface PowerTarget {  // 
void charge();
}

public class ChinaAdaptee { //
void v220Charge(PowerTarget target);
}

public class EuropeAdaptee {
void v230Charge(PowerTarget target);
}

public class JapanAdaptee {
void v100Charge(PowerTarget target);
}

public interface MobileAdapter {
void powerOn(PowerTarget target); //
}

public class ChinaMobileAdapter implements MobileAdapter {
private ChinaAdaptee adaptee;

public ChinaMobileAdapter(ChinaAdaptee adaptee) {
this.adaptee = adaptee;
}

@Override
public powerOn(PowerTarget target) {
adaptee.v220Charget(target); //
}
}

public class EuropeMobileAdapter implements MobileAdapter {
private EuropeAdaptee adaptee;

public EuropeMobileAdapter(EuropeAdaptee adaptee) {
this.adaptee = adaptee;
}

@Override
public powerOn(PowerTarget target) {
adaptee.v230Charget(target); //
}
}

public class JapanMobileAdapter implements MobileAdapter {
private JapanAdaptee adaptee;

public JapanMobileAdapter(JapanAdaptee adaptee) {
this.adaptee = adaptee;
}

@Override
public powerOn(PowerTarget target) {
adaptee.v100Charget(target); //
}
}

目标接口,这里为给手机充电
被适配器
适配器
用220v的交流电,给目标手机充电
用230v的交流电,给目标手机充电
用100v的交流电,给目标手机充电

适配器模式+工厂模式

上面的例子中明显不符合“开闭原则”的,往往需要配合工厂模式(Factory Pattern)一起使用,即可以用工厂方法,也可以用抽象工厂。依具体情况而定:

下面是一种改进办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public interface PowerTarget {
void charge();
}

public interface MobileAdaptee {
void charge(PowerTarget target);
}

public class ChinaAdaptee implements MobileAdaptee {
void charge(PowerTarget target);
}

public class EuropeAdaptee implements MobileAdaptee {
void charge(PowerTarget target);
}

public class JapanAdaptee implements MobileAdaptee {
void charge(PowerTarget target);
}

public abstract class MobileAdapter {
private MobileAdaptee adaptee;

public MobileAdapter(MobileAdaptee adaptee) {
this.adaptee = adaptee;
}

abstract void powerOn(PowerTarget target); //
}

public class ChinaMobileAdapter extends MobileAdapter {
public ChinaMobileAdapter(ChinaAdaptee adaptee) {
super(adaptee);
}

@Override
public powerOn(PowerTarget target) {
adaptee.v220Charge(target);
}
}

public class EuropeMobileAdapter extends MobileAdapter {
public EuropeMobileAdapter(EuropeAdaptee adaptee) {
super(adaptee);
}

@Override
public powerOn(PowerTarget target) {
adaptee.v230Charge(target);
}
}

public class JapanMobileAdapter extends MobileAdapter {
public JapanMobileAdapter(JapanAdaptee adaptee) {
super(adaptee);
}

@Override
public powerOn(PowerTarget target) {
adaptee.v100Charge(target);
}
}

如果不希望扩展,可以直接使用switch进行模式匹配变更为简单工厂.

适配器模式+策略模式

策略模式中通常会结合适配器进行动态的上下文逻辑切换。例如:

  • 不同支付渠道(微信、支付宝、Paypal)通过统一接口调用,但内部使用各自适配器。
  • 文件导入导出: CSV、Excel、JSON使用统一格式,但地层使用不同解析库。
1
2
3
4
5
6
7
8
9
10
11
12
public interface PaymentStrategy {
void pay(double amount);
}

public class WeChatPayAdapter implements PamentStrategy {
private WeChatPay weChatPay;

@Override
public void pay(double amount) {
weChatPay.charge(amount);
}
}

适配器+外观模式

外观模式就是为一组实现提供了“一键”处理,结合适配器模式可以隐藏adapter自身的复杂性。

1
2
3
4
5
6
7
8
9
10
11
12
public class NotificationFacade {
private SmsAdapter smsAdapter;
private EmailAdapter emailAdapter;

public void sendNotification(String type, String message) {
if ("sms".equals(type)) {
smsAdapter.send(message);
} else if ("email".equals(type)) {
emailAdapter.send(message);
}
}
}

和上面的简单工厂不同,外观模式的实现是确定的,所以不会违反“开闭原则”。

适配器+模板方法

场景:

在固定流程中嵌入适配器逻辑,完成特定步骤的适配工作。适用于独立开发流程,避免相互干扰,提高开发效率。

示例:

  • 数据处理流程中的数据源适配(数据库、文件、API),每种数据源都需适配成统一格式。
  • 报表生成框架中适配不同数据来源。

与装饰模式的区别

适配器模式和装饰器模式的结构非常相似,都是通过持有已有对象的一种实现:

1
2
3
4
5
6
7
8
public class Owner {
private final Internal internal;

public Owner(Internal in) {
....
}
...
}

但其核心目标不同:

模式 目的
Adapter 将一个类的接口转换成客户端期望的另一个兼容接口,解决接口不兼容问题
Decorator 动态地给对象添加职责或功能,提供比继承更灵活的扩展方式。
  • 区别口诀
  • Adapter是“改头换面”,为了兼容;
  • Decorator是“锦上添花”,实现增强。