软件构造:适配器模式
本文是软件构造系列的第三篇文章。其余文章在:
https://ruanx.net/tag/software-construction/
适配器(Adapter)模式用于解决这样的问题:有一个已经封装好的类,实现了各种功能;现在客户端想要调用这个类,但接口不匹配。
为了尽量少改动客户端和被调用者的代码,遵循开闭原则,我们倾向于用一个新的类,专门适配两种接口,使得:
- 无需改动被调用者的代码。
- 对于调用者而言,代码只有很微小的改动。
适配器模式还有另一个优点:如果后端(被调用者)改动,也无须改动前端。只需要把适配器与后端通讯的部分改掉即可,从而适配器模式有高度的可扩展性。
适配器模式分为类适配器、对象适配器,分别对应了继承、委托两种方式。前者需要了解被调用者的代码细节,所以使用较少;后者只需要委托被调用者类,应用广泛。
类适配器
我们接下来考虑一个任务。手上已经有了绘图器 Drawer
的实现:指定 G、R、B,画出对应颜色的图。但客户端的颜色保存方式是一个 int 型数组,顺序为 R、G、B,不能直接调用 Drawer
的接口。
// client
public class Main {
public static void main(String[] args) {
int[] c = new int[]{0x12, 0x34, 0x56};
// TODO: c 是 RGB 格式,想画出对应颜色
}
}
// Drawer
class Drawer {
int G, R, B;
public Drawer(int G, int R, int B) {
this.G = G;
this.R = R;
this.B = B;
}
public void drawGRB() {
System.out.printf("Draw GRB #%x%x%x\n", G, R, B);
}
}
于是添加一个适配器类。它继承了 Drawer
,但可以通过 RGB 格式数组来指定颜色,可以正确调用父对象的绘图方法。最终代码如下:
package adapter;
public class Main {
public static void main(String[] args) {
int[] c = new int[]{0x12, 0x34, 0x56};
new DrawerAdapter(c).draw();
// 输出:Draw GRB #341256 (正确)
}
}
class DrawerAdapter extends Drawer {
public DrawerAdapter(int[] Colors) {
super(Colors[1], Colors[0], Colors[2]);
}
public void draw() {
super.drawGRB();
}
}
class Drawer {
int G, R, B;
public Drawer(int G, int R, int B) {
this.G = G;
this.R = R;
this.B = B;
}
public void drawGRB() {
System.out.printf("Draw GRB #%x%x%x\n", G, R, B);
}
}
对象适配器
对象适配器不采取继承,而采取委托。此时无需知道 Drawer
的代码细节,只需要知道其接口。
package adapter;
public class Main {
public static void main(String[] args) {
int[] c = new int[]{0x12, 0x34, 0x56};
new DrawerAdapter().draw(c);
// 输出:Draw GRB #341256 (正确)
}
}
class DrawerAdapter {
public void draw(int[] Colors) {
new Drawer(Colors[1], Colors[0], Colors[2]).drawGRB();
}
}
class Drawer {
int G, R, B;
public Drawer(int G, int R, int B) {
this.G = G;
this.R = R;
this.B = B;
}
public void drawGRB() {
System.out.printf("Draw GRB #%x%x%x\n", G, R, B);
}
}
对象适配器相比于类适配器,还有另外一层好处:避免了不必要的接口暴露。在类适配器中,因为 DrawerAdapter
是 Drawer
的子类,故 Drawer
的其他方法也可以调用,这可能是有害的。而对象适配器采取了委托的方式,封闭了 Drawer
,只调用其 drawGRB
一个接口。
结论
Adapter 模式可以尽可能地遵循开闭原则(对扩展开放、对修改封闭),无需更改被调用者的代码,就能换一种接口来调用。Adapter 模式分为类适配器和对象适配器,建议采用对象适配器。