适配器设计模式
适配器设计模式是一种结构型设计模式,用于将一个类的接口转换成客户端所期望的另一个接口,从而使得原本由于接口不兼容而不能一起工作的类能够一起工作。适配器模式通常用于以下场景:
-
现有接口与需求不匹配:当需要使用的类的接口与当前系统的接口不匹配时,可以创建一个适配器来进行转换。
-
类的功能需要增强:有时候,为了增强现有类的功能而不修改原有代码,可以使用适配器模式。
概述
如果去欧洲国家去旅游的话,他们的插座如下图最左边,是欧洲标准。而我们使用的插头如下图最右边的。因此我们的笔记本电脑,手机在当地不能直接充电。所以就需要一个插座转换器,转换器第1面插入当地的插座,第2面供我们充电,这样使得我们的插头在当地能使用。生活中这样的例子很多,手机充电器(将220v转换为5v的电压),读卡器等,其实就是使用到了适配器模式。
定义:
将一个类的接口转换成客户端希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为类适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
结构
适配器模式(Adapter)包含以下主要角色:
- 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
- 被适配者(Adaptee):需要被适配的类。它定义了适配器所需的原始接口。
- 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
适配器模式的实现可以分为两种方式:
-
类适配器模式:通过继承被适配类和实现目标接口来实现适配器。这种方式需要多重继承或接口实现,不过在一些编程语言中并不支持多重继承,因此并不常用。
-
对象适配器模式:通过在适配器类中组合或聚合被适配类的实例来实现适配器。这种方式更加灵活,因为它可以适配多个类而不仅限于单一类。
适配器设计模式能够很好地解决不同接口之间的兼容性问题,使得原本不兼容的类能够协同工作,提高了代码的复用性和灵活性。
类适配器模式
实现方式:定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
【例】读卡器
现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。
代码如下:
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 电脑类
*/
public class Computer {
// 向SD卡中写数据(目标接口只能是SDCard)
public void writeSD(SDCard sdCard, String msg) {
sdCard.writeMsg(msg);
}
// 从SD卡中读取数据(目标接口只能是SDCard)
public String readSD(SDCard sdCard) {
return sdCard.readMsg();
}
}
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 目标接口 - SD卡接口
*/
public interface SDCard {
// 向SD卡中写数据
void writeMsg(String msg);
// 从SD卡中读数据
String readMsg();
}
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 目标接口实现类 - SDCard实现类
*/
public class SDCardImpl implements SDCard{
@Override
public void writeMsg(String msg) {
System.out.println("write msg to sd card"+msg);
}
@Override
public String readMsg() {
return "read msg from sd card";
}
}
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 适配者 - TFCard
*/
public interface TFCard {
// 向TF卡中写数据
void writeMsg(String msg);
// 从TF卡中读数据
String readMsg();
}
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 具体适配者 - TFCard具体实现类
*/
public class TFCardImpl implements TFCard{
@Override
public void writeMsg(String msg) {
System.out.println("write msg to tf card"+msg);
}
@Override
public String readMsg() {
return "read msg from tf card";
}
}
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 适配器类 继承被适配者类 实现目标接口
*/
public class SDAdapterTF extends TFCardImpl implements SDCard{
@Override
public void writeMsg(String msg) {
super.writeMsg(msg);
}
@Override
public String readMsg() {
return super.readMsg();
}
}
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 客户端 - 测试类
*/
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
String s = computer.readSD(sdCard);
System.out.println(s);
System.out.println("=============");
// 通过适配器,将TF卡转换为实现SD接口的适配器
SDAdapterTF adapterTF = new SDAdapterTF();
// 读取适配器中的数据
String s1 = computer.readSD(adapterTF);
System.out.println(s1);
}
}
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用。
对象适配器模式
实现方式:对象适配器模式可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。
【例】读卡器
我们使用对象适配器模式将读卡器的案例进行改写。
类适配器模式的代码,我们只需要修改适配器类(SDAdapterTF)和测试类。
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 适配器类 继承适配者类 实现目标接口
*/
public class SDAdapterTF implements SDCard {
private TFCard tfCard;
public SDAdapterTF(TFCard tfCard) {
this.tfCard = tfCard;
}
@Override
public void writeMsg(String msg) {
tfCard.writeMsg(msg);
}
@Override
public String readMsg() {
return tfCard.readMsg();
}
}
/**
* @author OldGj 2024/02/23
* @version v1.0
* @apiNote 客户端 - 测试类
*/
public class Client {
public static void main(String[] args) {
Computer computer = new Computer();
SDCard sdCard = new SDCardImpl();
String s = computer.readSD(sdCard);
System.out.println(s);
System.out.println("=============");
// 将TF卡传入适配器中
SDAdapterTF sdAdapterTF = new SDAdapterTF(new TFCardImpl());
String s1 = computer.readSD(sdAdapterTF);
System.out.println(s1);
}
}
注意:还有一个适配器模式是接口适配器模式。当不希望实现一个接口中所有的方法时,可以创建一个抽象类Adapter ,实现所有方法。而此时我们只需要继承该抽象类即可。
应用场景
- 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
- 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
JDK中的适配器模式:
当涉及字符流(Reader
)和字节流(InputStream
)之间的适配时,通常会使用适配器模式。这种适配器模式的目的是让字符流和字节流能够协同工作,尽管它们的接口不同。
-
Reader(字符流):
Reader
是 Java 中用于读取字符流的抽象基类。它定义了读取字符数据的一系列方法,如read()
、close()
等。字符流是以字符为单位进行读取和写入的,对文本数据的处理更为方便。 -
InputStream(字节流):
InputStream
是 Java 中用于读取字节流的抽象基类。它定义了读取字节数据的一系列方法,如read()
、close()
等。字节流是以字节为单位进行读取和写入的,适用于处理二进制数据。 -
InputStreamReader(适配器):
InputStreamReader
是 Java 中用于将字节流转换为字符流的适配器类。它实现了Reader
接口,并包装了一个InputStream
对象。InputStreamReader
通过在字节流和字符流之间进行转换,使得字符流能够读取字节流中的数据。它的作用就是将字节流适配成字符流,使得原本不兼容的字符流和字节流能够一起工作。
在使用 InputStreamReader
时,它会接受一个 InputStream
对象作为参数,并将该对象转换为字符流,因此它充当了字符流和字节流之间的适配器。这样一来,当我们需要使用字符流操作时,可以直接使用 Reader
接口及其实现类,而不必直接操作字节流。InputStreamReader
负责将底层的字节流适配成字符流,从而实现了字符流和字节流之间的适配。
总而言之,Reader
和 InputStream
之间的适配器模式的典型应用就是通过 InputStreamReader
将字节流适配成字符流,使得字符流和字节流能够协同工作,这是适配器模式在 Java IO 中的一个典型应用。