适配器模式

1、定义

  • 将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
  • 系统数据和行为都正确,但接口不符合时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。

属于结构型模式。

主要分为三类:类适配器模式、对象的适配器模式、接口的适配器模式。
由三部分组成:

  • 需要适配的类,接口,对象,简称src
  • 最终输出的,简称dst
  • 适配器,adapter

src-->adapter-->dst

2、类适配器模式

最简单的适配器例子就是充电器转换插头。

充电器转换头本身相当于Adapter,220V交流电相当于src,我们的目dst标是5V直流电。

2.1 UML图

2.2 示例

将 220V 的插头转换成 5V 的

f

1)src类

/**
 * src类: 我们有的220V电压
 */

public class Voltage220 {
    public int output220V() {
        int src = 220;
        System.out.println("我是" + src + "V");
        return src;
    }
}

2)dst接口

/**
 * dst接口:客户需要的5V电压
 */
public interface Voltage5 {
    int output5V();
}

3)适配器类

/**
 * Adapter类:完成220V-5V的转变
 */

public class VoltageAdapter extends Voltage220 implements Voltage5 {
    @Override
    public int output5V() {
        int src = output220V();
        System.out.println("适配器工作开始适配电压");
        int dst = src / 44;
        System.out.println("适配完成后输出电压:" + dst);
        return dst;
    }
}

调用类:

/**
 * Client类:手机 .需要5V电压
 */

public class Mobile {
    /**
     * 充电方法
     *
     * @param voltage5
     */
    public void charging(Voltage5 voltage5) {
        if (voltage5.output5V() == 5) {
            System.out.println("电压刚刚好5V,开始充电");
        } else if (voltage5.output5V() > 5) {
            System.out.println("电压超过5V,都闪开 我要变成note7了");
        }
    }
    public static void main(String[] args) {
        System.out.println("===============类适配器==============");
        Mobile mobile = new Mobile();
        mobile.charging(new VoltageAdapter());
    }
}

输出:

===============类适配器==============
我是220V
适配器工作开始适配电压
适配完成后输出电压:5
电压刚刚好5V,开始充电

缺点:

  • 类适配器需要继承src类这一点算是一个缺点
  • 因为这要求dst必须是接口,有一定局限性
  • 且src类的方法在Adapter中都会暴露出来,也增加了使用的成本

优点:

  • 但同样由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。

3、对象适配器模式

基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承src类,而是持有src类的实例,以解决兼容性的问题。

  • 持有 src类,实现 dst 类接口,完成src->dst的适配

3.1 UML图

3.2 示例

Adapter类:

/**
 * 对象适配器模式:
 * 持有 src类,实现 dst 类接口,完成src->dst的适配。 。以达到解决**兼容性**的问题。
 */

public class VoltageAdapter2 implements Voltage5 {
    //持有了src类
    private Voltage220 mVoltage220;

    public VoltageAdapter2(Voltage220 voltage220) {
        mVoltage220 = voltage220;
    }

    @Override
    public int output5V() {
        int dst = 0;
        if (null != mVoltage220) {
            int src = mVoltage220.output220V();
            System.out.println("对象适配器工作,开始适配电压");
            dst = src / 44;
            System.out.println("适配完成后输出电压:" + dst);
        }
        return dst;
    }
}

调用代码:

System.out.println("\n===============对象适配器==============");
        VoltageAdapter2 voltageAdapter2 = new VoltageAdapter2(new Voltage220());
        Mobile mobile2 = new Mobile();
        mobile2.charging(voltageAdapter2);

输出:

===============对象适配器==============
我是220V
对象适配器工作,开始适配电压
适配完成后输出电压:5
电压刚刚好5V,开始充电

特点:

  • 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 
  • 根据合成复用原则,组合大于继承,  所以它解决了类适配器必须继承src的局限性问题,也不再强求dst必须是接口。
  •   同样的它使用成本更低,更灵活。

(和装饰者模式初学时可能会弄混,这里要搞清,装饰者是对src的装饰,使用者毫无察觉到src已经被装饰了(使用者用法不变)。 这里对象适配以后,使用者的用法还是变的。
即,装饰者用法: setSrc->setSrc,对象适配器用法:setSrc->setAdapter.)

4、接口适配器模式

当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。

4.1 UML图

4.2 示例

还是拿上面的转换接头来举例。比如我们现在有一个万能充的接口,可以输出各种不同的电压:

public interface UniversalCharge {
    int output5V();
    int output9V();
    int output12V();
    int output24V();
}

但是现在我们只需要 5V 的电压,所以要有个5V 的电压适配器,但是如果直接implement 这个接口的话,我们的适配器类中就会有所有的方法,太冗余了。

因此我们需要一个中间适配器,实现所有的方法,然后让不同的电压适配器去选择性的实现:

public abstract class PowerAdapter implements UniversalCharge{


    @Override
    public int output5V() {
        return 0;
    }

    @Override
    public int output9V() {
        return 0;
    }

    @Override
    public int output12V() {
         return 0;
    }

    @Override
    public int output24V() {
         return 0;
    }
}

这样一来,无论我们需要是5V 的还是 12V 的转接头,只要继承这个中间类就可以了:

public class Power5VAdapter extends PowerAdapter {

      //持有了src类
    private Voltage220 mVoltage220;

    public VoltageAdapter2(Voltage220 voltage220) {
        mVoltage220 = voltage220;

    @Override
    public int output5V() {
        int output = 0;
        if (mAC220 != null) {
            output = voltage220.output220V() / 44;
        }
        return output;
    }
}

5、JDK中的适配器模式

​ 在JDK中,适配器是一个非常重要的设计模式。尤其是在Java IO包的设计上,使用很广泛。在Java的IO包中有一对类,分别叫做InputStreamReader和OutputStreamWriter。他们负责对字节流和字符流进行转换,因此有的资料称他们为桥梁类。尽管名字叫桥梁类,但是他们并不是那个桥梁模式的应用。而是典型的适配器模式的应用。

​ 对于InputStreamReader来说,它一个字节流到字符流的适配器,它的实现主要采用的是象适配器模式,如图所示:

对于OutputStreamWriter来说也是类似的。

6、适配器模式的使用场景

​ 1、在想使用一个已经存在的类,但如果它的接口也就是它的方法和你的要求不相同时,就应该考虑适配器模式。即有动机地修改一个正常运行的系统的接口时。

​ 2、在设计之初就考虑适配器模式:比如公司设计一个系统时考虑使用第三方开发组件,而这个组件的接口与我们自己的系统接口是不相同的,而我们也完全没有必要为了迎合它而改动自己的接口,此时尽管在开发的设计阶段,也是可以考虑用适配器模式来解决接口不同的问题。