适配器模式(Adapter Pattern)是一种结构型设计模式,它作用于将一个类的接口转换成客户端所期望的另一种接口,从而使原本由于接口不兼容而无法一起工作的那些类可以在一起工作。它属于包装模式的一种。
适配器模式主要分为两种:
适配器模式可以用生活中的插座转换器来理解。我们都知道,不同国家或地区使用的插座类型不尽相同。当你从国内带着笔记本电脑出国旅行时,由于插座类型的不同,可能无法为笔记本供电。这时就需要使用一个插座转换器,将你的笔记本电源插头适配到当地的插座上。这个转换器,就是一个很好的适配器模式实例。
在这个例子中:
假设我们有一个第三方的老式圆孔接口 LegacyRoundHole
和一个新式方形接口 NewSquarePeg
。现在需要将新式方形接口适配到老式圆孔接口上。我们可以通过创建一个适配器类 SquarePegAdapter
来实现适配,代码如下:
// 老式圆孔接口
interface LegacyRoundHole {
void insertRoundPeg(RoundPeg peg);
}
// 新式方形接口
interface NewSquarePeg {
void insertSquarePeg();
}
// 适配器(对象适配器方式)
class SquarePegAdapter implements LegacyRoundHole {
private NewSquarePeg squarePeg;
public SquarePegAdapter(NewSquarePeg squarePeg) {
this.squarePeg = squarePeg;
}
@Override
public void insertRoundPeg(RoundPeg peg) {
// 通过一些算法将方形适配到圆孔
squarePeg.insertSquarePeg();
}
}
// 适配器(类适配器方式)
class SquarePegClassAdapter extends RoundPeg {
private NewSquarePeg squarePeg;
public SquarePegClassAdapter(NewSquarePeg squarePeg) {
this.squarePeg = squarePeg;
}
@Override
public void insertIntoHole(LegacyRoundHole hole) {
// 通过一些算法将方形适配到圆孔
squarePeg.insertSquarePeg();
}
}
在上面的代码中,我们分别使用对象适配器和类适配器的方式实现了适配器模式。
SquarePegAdapter
实现了旧接口 LegacyRoundHole
,同时在内部持有一个新接口 NewSquarePeg
对象。当客户端调用 insertRoundPeg
方法时,适配器就会将请求转发给新接口对象,并通过一些算法进行适配。SquarePegClassAdapter
继承了 RoundPeg
类,同时在内部持有一个新接口 NewSquarePeg
对象。当客户端调用 insertIntoHole
方法时,适配器就会将请求转发给新接口对象,并通过一些算法进行适配。这样就实现了新旧接口的兼容。
在Java源码中,适配器模式也有典型的应用,比如 java.util.Arrays#asList()
。
List<String> strs = Arrays.asList("a", "b", "c");
asList()
方法会返回一个包装好的 ArrayList
对象,里面装了传入的数组元素。这里 Arrays
类就相当于适配器,将基本的数组适配成了 List
类对象。
另一个例子是 java.io.InputStreamReader
和 java.io.OutputStreamWriter
:
Reader reader = new InputStreamReader(new FileInputStream("file.txt"), "UTF-8");
Writer writer = new OutputStreamWriter(new FileOutputStream("file.txt"), "UTF-8");
这两个类将低级的字节流适配成高级的字符流,屏蔽了字节与字符编码转换层的底层细节。InputStreamReader
和 OutputStreamWriter
起到了适配器的作用。
在 InputStreamReader
的源码中,我们可以看到它是如何将字节流适配成字符流的:
public class InputStreamReader extends Reader {
private final StreamDecoder sd;
public InputStreamReader(InputStream in, String charsetName) {
super(in);
try {
sd = StreamDecoder.forInputStreamReader(in, this, charsetName);
} catch (UnsupportedEncodingException uee) {
throw new UnsupportedCharsetException(uee.getMessage());
}
}
public int read() throws IOException {
return sd.read();
}
// 其他方法
}
可以看到,InputStreamReader
内部持有一个 StreamDecoder
对象,它负责将字节解码成字符。当我们调用 read()
方法时,实际上是委托给 StreamDecoder
去读取和解码字节流。
适配器模式非常实用,在实际开发中经常会遇到接口不兼容的情况,通过使用适配器模式就可以让这些原本不兼容的类一起工作。