设计模式(七):适配器模式

Published on 2024-03-06 18:03 in 分类: 博客 with 狂盗一枝梅
分类: 博客

一、适配器模式的定义

适配器模式是一种结构型设计模式,它的定义如下:

Convert the interface of a class into another interface clients expect.Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.(将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。)

这个定义真是言简意赅,一句话就说明白了适配器模式的精髓。

适配器模式中有三个重要角色:被适配者Adaptee,适配器Adapter和目标对象Target

他们的关系如下图所示:

image-20240306152629575

A想使用B完成组合是不行的,但是引入了C就完美实现了,在这个过程中,A就是Target,B就是Adaptee,C就是Adapter

二、三种适配器

适配器模式大体上分为三种:类适配器对象适配器以及缺省适配器

1、类适配器

基本思路:Adapter 类,通过继承Adaptee类,实现 Target 类接口,完成 Apdaptee -> Target 的适配

生活中有很多适配器模式的例子,举个例子:电源适配器,笔记本电脑的电源适配器,它将220V交流电压转换为笔记本可用的20V直流电压。

现在我用类适配器模式模拟实现这一过程:AC表示220V的交流电,DC表示笔记本可用的20V的直流电,充电器扮演了适配器的角色,现在以笔记本电脑充电为例,类图如下:

适配器模式-类适配器模式.drawio

具体代码如下:

public class AC {
    public Integer outputAC() {
        return 220;
    }
}

public interface DC {
    public Integer outputDC();
}

public class LaptopAdapter extends AC implements DC {

    @Override
    public Integer outputDC() {
        Integer ac = this.outputAC();
        Integer dc = transToDc(ac);
        return dc;
    }

    /**
     * 转化交流电为直流电
     *
     * @param ac 交流电压
     * @return 转化后的直流电压
     */
    private Integer transToDc(Integer ac) {
        return 20;
    }
}

public class Laptop {

    public static void main(String[] args) {
        new Laptop().charging(new LaptopAdapter());
    }

    public void charging(DC dc) {
        System.out.println("正在使用" + dc.outputDC() + "V的电压充电");
    }
}

2、对象适配器

基本思路:和类适配器模式相同,但是对 Adapter 类做修改,不去继承被适配者Adaptee,而是持有Adaptee类的实例,实现 Target 类接口,完成 Apdaptee -> Target 的适配,以解决兼容性的问题:根据合成复用原则,在系统中尽量使用关联关系来替代继承关系

这时候类图变成如下所示

适配器模式-对象适配器模式.drawio

对比类适配器模式类图,这张图只有一点变化:AC和LaptopAdapter的类关系变成了聚合关系,这意味着AC会变成LaptopAdapter的成员变量,具体代码如下

public class AC {
    public Integer outputAC() {
        return 220;
    }
}

public interface DC {
    public Integer outputDC();
}

public class LaptopAdapter implements DC {
    private AC ac;
    
    public LaptopAdapter(AC ac) {
        this.ac = ac;
    }
    
    @Override
    public Integer outputDC() {
        Integer ac = this.outputAC();
        Integer dc = transToDc(ac);
        return dc;
    }

    /**
     * 转化交流电为直流电
     *
     * @param ac 交流电压
     * @return 转化后的直流电压
     */
    private Integer transToDc(Integer ac) {
        return 20;
    }
}

public class Laptop {

    public static void main(String[] args) {
        new Laptop().charging(new LaptopAdapter(new AC()));
    }

    public void charging(DC dc) {
        System.out.println("正在使用" + dc.outputDC() + "V的电压充电");
    }
}

对象适配器和类适配器其实算是同一种思想,只不过实现方式针对类适配器的局限性进行了优化,根据合成复用原则,用关联关系替代了继承。

3、缺省适配器

我觉得这个名字很符合它的精髓,它的另外一个名字“接口适配器”反而词不达意。它的基本思想是这样的:

当不需要全部实现接口提供的方法时,可以先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现,即(空方法),那么该抽象类的子类可以有选择的覆盖父类的某些方法来实现需求。

还是上面的电源适配器案例,假如现在DC有多个接口,能输出5V、12V、20V的电压,按照正常逻辑,设计一个适配器去实现这个接口,显然,需要实现该目标接口的所有方法,但是,现在我们实际情况中,只需要使用其中的20V直流电给笔记本进行充电,不需要使用这个接口的所有方法。

因此,可以设计一个中间类去把目标接口的所有方法空实现,然后适配器类再去继承这个中间类,选择性重写我所需要的方法,这样就OK了。类图如下所示

适配器模式-缺省适配器模式.drawio

具体代码如下

public class AC {
    public Integer outputAC() {
        return 220;
    }
}

public interface DC {
    Integer output5V();
    Integer output12V();
    Integer output20V();
}

public class DefaultDCAdapter implements DC{
    @Override
    public Integer output5V() {
        return null;
    }
    @Override
    public Integer output12V() {
        return null;
    }
    @Override
    public Integer output20V() {
        return null;
    }
}

public class LaptopAdapter extends DefaultDCAdapter{

    private AC ac;

    public LaptopAdapter(AC ac) {
        this.ac = ac;
    }

    @Override
    public Integer output20V() {
        Integer ac = this.ac.outputAC();
        Integer dc = transToDc(ac);
        return dc;
    }

    /**
     * 转化交流电为直流电
     *
     * @param ac 交流电压
     * @return 转化后的直流电压
     */
    private Integer transToDc(Integer ac) {
        return 20;
    }
}

public class Laptop {

    public static void main(String[] args) {
        new Laptop().charging(new LaptopAdapter(new AC()));
    }

    public void charging(DC dc) {
        System.out.println("正在使用" + dc.output5V() + "V的电压充电");
    }
}

三、源码案例

先看一段从文件中读取一行文字的代码示例:

BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("demo.txt")));
String str = br.readLine();

BufferedReader想从FileInputStream输入流中读取一行文本,但是它只能处理字符,不能处理字节,使用InputStreamReader将从FileInputStream读取的字节转换为字符,再做处理,这里很明显使用了适配器模式。

关键方法在于InputStreamReader中的read方法

/**
 * Reads a single character.
 *
 * @return The character read, or -1 if the end of the stream has been
 *         reached
 *
 * @exception  IOException  If an I/O error occurs
 */
public int read() throws IOException {
        return sd.read();
}

/**
 * Reads characters into a portion of an array.
 *
 * @param      cbuf     Destination buffer
 * @param      offset   Offset at which to start storing characters
 * @param      length   Maximum number of characters to read
 *
 * @return     The number of characters read, or -1 if the end of the
 *             stream has been reached
 *
 * @exception  IOException  If an I/O error occurs
 */
public int read(char cbuf[], int offset, int length) throws IOException {
    return sd.read(cbuf, offset, length);
}

read方法完成了从字节到字符的转换,InputStreamReader 将Reader 和InputStream 适配起来,在read() 方法中适配Reader类中字符读取操作,调用sd对象中的字节读取转化为了字符读取

  • Reader 类对应目标对象 Target
  • InputStreamReader 类对应适配器 Adapter
  • InputStream 对应被适配者 Adaptee

#设计模式
目录