将一个类的接口转换成客户期望的另一个接口,适配器让原本接口不兼容的类可以相互合作。
核心作用
- 兼容性: 使得原本由于接口部匹配而无法一起工作的类能够协同工作。
- 复用性: 允许复用已有的类,即使它们不符合当前系统的统一结构标准。
- 解耦性: 避免修改已有代码的前提下实现新功能对接。
适用场景
- 系统需要使用一些已有类,但这些类的接口不符合系统要求
- 希望复用一些旧的组件或第三方库,但它们的接口与当前系统不一致
- 在不修改原有代码的基础上进行接口适配
模式结构
角色 |
描述 |
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
是“锦上添花”,实现增强。