Adapter

如果想让额定工作电压是直流 12 伏特的笔记本电脑在交流 220 伏特的 AC(Alternating Current) 电源下工作,应该怎么做呢?通常,我们会使用 AC 适配器,将家庭用的交流 220 伏特电压转换成我们所需要的直流 12 伏特电压。这就是适配器的工作,它位于实际情况与需求之间,填补两者之间的差异。

接口适配器

电源适配器

适配器的角色

在程序世界中,经常会存在现有的程序无法直接使用,需要做适当的变换之后才能使用的情况。这种用于填补『现有的程序』和『所需的程序』之间差异的设计模式就是 Adapter 模式。在 Java 中,adapter 模式有如下两种实现。

  • 使用继承的适配器
  • 使用委托的适配器

示例程序

电源的比喻与示例程序的关系

电源的比喻 示例程序
实际情况 交流 220 伏特 AlternateCurrent 类(use220V)
转换装置 适配器 AlternateToDirect 类
需求 直流 12 伏特 DirectCurrent 类(use12V)

实际情况

不管是继承适配器还是委托适配器,实际情况都是不变的。

1
2
3
4
5
6
7
8
9
10
public class AlternateCurrent {
private int voltage;

public AlternateCurrent(int voltage) {
this.voltage = voltage;
}
public void use220V() {
System.out.println("From " + voltage + " V");
}
}

Main 类

1
2
3
4
5
6
7
public class Main {
public static void main(String[] args) {
DirectCurrent p = new AlternateToDirect(220);
p.use12V();
}
}

继承适配器

需求是一个接口。

需求

1
2
3
public interface DirectCurrent {
public abstract void use12V();
}

转换装置

1
2
3
4
5
6
7
8
9
10
public class AlternateToDirect extends AlternateCurrent implements DirectCurrent {
public AlternateToDirect(int v) {
super(v);
}
@Override
public void use12V() {
use220V();
}
}

类图

继承适配器

委托适配器

需求是一个类。

需求

1
2
3
public abstract class DirectCurrent {
public abstract void use12V();
}

转换装置

1
2
3
4
5
6
7
8
9
10
11
public class AlternateToDirect extends DirectCurrent {
private AlternateCurrent ac;

public AlternateToDirect(int v) {
this.ac = new AlternateCurrent(v);
}
@Override
public void use12V() {
ac.use220V();
}
}

类图

委托适配器

总结

Adapter 模式用于填补具有不同 API 的两个类之间的缝隙。

什么时候用 Adapter 模式

如果某个方法就是我们所需要的方法,那么直接在程序中使用不就可以了吗,为什么还要考虑使用 Adapter 模式呢?那么,究竟应当在什么时候使用 Adapter 模式呢?

  • 很多时候,我们并非从零开始编程,经常会用到现有的类。
    • 特别是当现有的类已经被充分测试过了,Bug 很少,而且已经被用于其他软件之中时,我们更愿意将这些类作为组件重复利用。
  • 版本升级与兼容性。
    • 软件的生命周期总是伴随着版本的升级,而在版本升级的时候经常会出现『与旧版本的兼容性问题』。如果能够完全抛弃旧版本,那么软件的维护工作将会轻松得多,但是现实中往往无法这样做。这时,可以使用 Adapter 模式使新旧版本兼容,帮助我们轻松地同时维护新版本和旧版本。

功能完全不同的类

当然,当『需求』角色和『实际情况』角色的功能完全不同时,Adapter 模式是无法使用的。就如同我们无法用交流 220 伏特电压让自来水管出水一样。

拓展

习题

在 java.util.Properties 类中,可以像下面这样管理键值对(属性)。

1
2
3
year=2018
month=12
day=10

java.util.Properties 类提供了以下方法,可以帮助我们方便地从流中取出属性或写入。

1
2
3
4
5
6
7
8
9
10
//从 InputStream 中取出属性集合。
public void load(InputStream inStream) throws IOException {
...
}

//向 OutputStream 写入属性集合。header 是注释文字。
public void store(OutputStream out, String header) throws IOException {
...
}

请使用 Adapter 模式编写一个将属性集合保存至文件的 FileProperties 类。

这里,我们假设在下面代码中的 FileIO 接口中声明了将属性集合保存至文件的方法,并假设 FileProperties 类会实现这个 FileIO 接口。

FileIO

1
2
3
4
5
6
public interface FileIO {
public void readFromFile(String fileName) throws IOException;
public void writeToFile(String fileName) throws IOException;
public void setValue(String key, String value);
public String getValue(String key);
}

Main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 实际情况=Properties
* 需求=FileIO
* 转换装置=FileProperties
*
*/
public class Main {
public static void main(String[] args) {
FileIO f = new FileProperties();
try {
f.readFromFile("./resource/adapter.txt");
f.setValue("year", "2018");
f.setValue("month", "12");
f.setValue("day", "10");
f.writeToFile("./resource/newAdapter.txt");
} catch (IOException e) {
e.printStackTrace();
}
}
}

输入文件(adapter.txt)

1
year=1999

输出文件(newAdapter.txt)

1
2
3
4
5
#written by FileProperties
#Mon Dec 10 18:33:18 CST 2018
day=10
year=2018
month=12

代码

github 地址

引用