什么是适配器模式
适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
生活中的例子:比如有一个电器的插头是三脚插头,但是现在插座只有两孔的,要使插头插上插座,我们需要一个插头转换器,这个转换器也就是适配器。
适配器模式涉及3个角色:
- 源(Adaptee):需要被适配的对象或类型,相当于插头。
- 适配器(Adapter):连接目标和源的中间对象,相当于插头转换器。
- 目标(Target):期待得到的目标,相当于插座的标准。
适配器模式包括3种形式:类适配器模式、对象适配器模式、接口适配器模式(或又称作缺省适配器模式)。
类适配器模式
从下面的结构图可以看出,Adaptee类并没有request()方法,而客户端则期待这个方法。为使客户端能够使用Adaptee类,我们把Adaptee与Target衔接起来。Adapter与Adaptee是继承关系,这决定了这是一个类适配器模式。
代码:
//源
public class Adaptee {
public void specificRequest() {
System.out.println("三脚插头...");
}
}
// 目标接口,或称为标准接口
public interface Target {
public void request();
public void specificRequest();
}
//适配器
public class Adapter extends Adaptee implements Target {
@Override
public void request() {
System.out.println("转换为二脚插头");
}
}
//测试类
public class AdapterTest {
public static void main(String[] args) {
Adapter adapter = new Adapter();
adapter.specificRequest();
adapter.request();
}
}
运行结果:
三脚插头…
转换为二脚插头
上面这种实现的适配器称为类适配器,因为 Adapter 类既继承了 Adaptee (被适配类),也实现了 Target 接口(因为 Java 不支持多继承,所以这样来实现)
对象适配器模式
对象适配器模式是另外6种结构型设计模式的起源。
对象适配器,它不是使用多继承或继承再实现的方式,而是使用直接关联,或者称为委托的方式。
从下面的结构图可以看出,Adaptee类并没有specificRequest()方法,而客户端则期待这个方法。与类适配器模式一样,为使客户端能够使用Adaptee类,我们把Adaptee与Target衔接起来。但这里我们不继承Adaptee,而是把Adaptee封装进Adapter里。这里Adaptee与Adapter是组合关系。
代码实现:
Target和Adaptee和上面的类适配器一样,不再贴出。
//适配器
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
System.out.println("转换为二脚插头");
}
@Override
public void specificRequest() {
adaptee.specificRequest();
}
}
//测试类
public class AdapterTest {
public static void main(String[] args) {
Adapter adapter = new Adapter(new Adaptee());
adapter.specificRequest();
adapter.request();
}
}
运行结果:
三脚插头…
转换为二脚插头
类适配器与对象适配器的区别
- 类适配器使用的是继承的方式,直接继承了Adaptee,所以无法对Adaptee的子类进行适配。
- 对象适配器使用的是组合的方式,·所以Adaptee及其子孙类都可以被适配。另外,对象适配器对于增加一些新行为非常方便,而且新增加的行为同时适用于所有的源。
- 基于组合/聚合优于继承的原则,使用对象适配器是更好的选择。但具体问题应该具体分析,某些情况可能使用类适配器会适合,最适合的才是最好的。
接口适配器模式
接口适配器模式(缺省适配模式)的思想是,为一个接口提供缺省实现,这样子类可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。
例如:java.awt.KeyListener是一个键盘监听器接口,我们把这个接口的实现类对象注册进容器后,这个容器就会对键盘行为进行监听,像这样:
public interface KeyListener extends EventListener {
public void keyTyped(KeyEvent e);
public void keyPressed(KeyEvent e);
public void keyReleased(KeyEvent e);
}
public class KeyListenerText {
public static void main(String[] args) {
JFrame jFrame = new JFrame();
jFrame.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
System.out.println("keyTyped....");
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
});
}
}
可以看到其实我们只使用到其中一个方法,但必须要把接口中所有方法都实现一遍,如果接口里方法非常多,那岂不是非常麻烦。于是我们引入一个默认适配器,让适配器把接口里的方法都实现一遍,使用时继承这个适配器,把需要的方法实现一遍就好了。JAVA里也为java.awt.KeyListener提供了这样一个适配器:java.awt.KeyAdapter。我们使用这个适配器来改改上面的代码:
public abstract class KeyAdapter implements KeyListener {
public void keyTyped(KeyEvent e) {}
public void keyPressed(KeyEvent e) {}
public void keyReleased(KeyEvent e) {}
}
public class KeyListenerText {
public static void main(String[] args) {
JFrame jFrame = new JFrame();
jFrame.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
System.out.println("keyTyped....");
}
});
}
}
这样不必再把每个方法都实现一遍,代码看起来简洁多了。在任何时候,如果不准备实现一个接口里的所有方法时,就可以使用“缺省适配模式”制造一个抽象类,实现所有方法,这样,从这个抽象类再继承下去的子类就不必实现所有的方法,只要重写需要的方法就可以了。
小结
优点
- 更好的复用性:系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
- 更好的扩展性:在实现适配器功能的时候,可以扩展自己源的行为(增加方法),从而自然地扩展系统的功能。
缺点
- 会导致系统紊乱:滥用适配器,会让系统变得非常零乱。例如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。