前言
适配器模式是一种结构型设计模式, 它能将接口转换为客户期望的另一个接口,也就是说它能使接口不兼容的对象能够相互合作。
适配器模式
适配器模式(Adapter Pattern )的原始定义是:将类的接口转换为客户期望的另一个接口,适配器可以让不兼容的两个类一起协同工作。适配器模式通过封装对象将复杂的转换过程隐藏于幕后。 被封装的对象甚至察觉不到适配器的存在。
适配器结构
适配器模式是用来做适配,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
类适配器模式的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
适配器模式原理
适配器模式(Adapter)包含以下主要角色:
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 适配者(Adaptee)类:适配者即被适配的角色,它是被访问和适配的现存组件库中的组件接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
适配器模式示例
我们在日常生活中,在使用电脑的时候,往往需要各种适配器的,如TypeC转HDMI之类的,如果电脑上只有TypeC,而电脑上有HDMI、TypeC的接口,但如果这个时候,我们手里只有HDMI的时候,我们就需要使用适配器去适配转换成HDMI。
1、类适配器模式
上文我们说到适配器模式有类适配器和对象适配器两种,我们先来看看类适配器模式,它的类图如下:
Computer
/**
* @author Duansg
* @date 2022-12-19 11:24 下午
*/
public class Computer {
private String read(TypecMedia media) {
return media.read();
}
}
TypecMedia
/**
* @author Duansg
* @date 2022-12-19 4:01 下午
*/
public interface TypecMedia {
/**
* 读取方法
*
* @return
*/
String read();
}
TypecMediaImpl
/**
* @author Duansg
* @date 2022-12-19 4:02 下午
*/
public class TypecMediaImpl implements TypecMedia {
@Override
public String read() {
return "type-c reading media!";
}
}
TypecMediaAdapterHdmi
/**
* @author Duansg
* @date 2022-12-19 4:04 下午
*/
@Slf4j
public class TypecMediaAdapterHdmi extends TypecMediaImpl implements HdmiMedia {
@Override
public String read() {
String typecRead = super.read();
log.info("typecRead:{}", typecRead);
return "type-c convet hdmi reading media!";
}
}
HdmiMedia
/**
* @author Duansg
* @date 2022-12-19 4:03 下午
*/
public interface HdmiMedia {
/**
* 读取方法
*
* @return
*/
String read();
}
HdmiMediaImpl
/**
* @author Duansg
* @date 2022-12-19 4:03 下午
*/
public class HdmiMediaImpl implements HdmiMedia {
@Override
public String read() {
return "hdmi reading media!";
}
}
Test Unit
/**
* @author Duansg
* @date 2022-12-19 11:24 下午
*/
public class Computer {
public static void main(String[] args) {
Computer computer = new Computer();
TypecMedia typecMedia = new TypecMediaImpl();
System.out.println(computer.read(typecMedia));
TypecMedia hdmiMedia = new TypecMediaAdapterHdmi();
System.out.println(computer.read(hdmiMedia));
}
private String read(TypecMedia media) {
return media.read();
}
}
2、对象适配器模式
对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
我们只需要修改适配器类,如下:
TypecAdapterHdmi
/**
* @author Duansg
* @date 2022-12-19 11:33 下午
*/
@Slf4j
public class TypecAdapterHdmi implements TypecMedia {
private HdmiMedia hdmiMedia;
public TypecAdapterHdmi(HdmiMedia hdmiMedia) {
this.hdmiMedia = hdmiMedia;
}
@Override
public String read() {
final String hdmiRead = hdmiMedia.read();
log.info("hdmiRead:{}", hdmiRead);
return "type-c convet hdmi reading media!";
}
}
Test Unit
/**
* @author Duansg
* @date 2022-12-19 11:24 下午
*/
public class Computer {
public static void main(String[] args) {
Computer computer = new Computer();
TypecMedia typecMedia = new TypecMediaImpl();
System.out.println(computer.read(typecMedia));
HdmiMedia hdmiMedia = new HdmiMediaImpl();
TypecMedia convet = new TypecAdapterHdmi(hdmiMedia);
System.out.println(computer.read(convet));
}
private String read(TypecMedia media) {
return media.read();
}
}
适配器模式适用的场景
- 统一多个类的接口设计时
某个功能的实现依赖多个外部系统(或者说类)。通过适配器模式,将它们的接口适配为统一的接口定义。
- 需要依赖外部系统时
当我们把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动。
- 原有接口无法修改时或者原有接口功能太老旧但又需要兼容;
JDK1.0 Enumeration到Iterator的替换,适用适配器模式保留Enumeration类,并将其实现替换为直接调用 Itertor。
- 适配不同数据格式时;
Slf4j等日志框架,定义打印日志的统一接口,提供针对不同日志框架的适配器。
代理、桥接、装饰器、适配器的区别
这几种种模式是比较常用的结构型设计模式。它们的代码结构非常相似,但其各自的用意却不同,它们之间的关系如下:
- 代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
- 桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
- 装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
- 适配器模式:将一个类的接口转换为客户希望的另一个接口,适配器模式让那些不兼容的类可以一起工作。
总结
优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无序修改原有结构。
- 增加了类的透明性和复用性,将具体业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
- 灵活性和扩展性都非常好,通过使用配置文件可以很方便的更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,符合开闭原则。
缺点
类适配器的缺点
- 对于Java等不支持多重继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。
- 适配者类不能为最终类。
对象适配器的缺点
- 与类适配器模式相比较,在该模式下要在适配器中置换适配者类的某些方法比较麻烦。