适配器模式原来这么简单!

引言

我认为适配器模式应该是设计模式中最容易最简单的模式了,我只需要举一个例子,瞬间就能明白。

90后的同学一定知道SD卡,经常用它来存储音乐等,想添加音乐的话只能到电脑下载,但是电脑不能直接插SD卡,所以我们会使用读卡器,通过读卡器连接SD卡和电脑USB插口,就可以达到我们的目的。读卡器就是一个适配器,而这个模式就是适配器模式。

适配器模式

基本概念

适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主要目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。它属于结构性模式。

适配器模式分为三类:

  • 类适配器模式
  • 对象适配器模式
  • 接口适配器模式

主要角色

  • 目标接口(Target)

    客户所期待的接口,它可以是抽象类或接口。

  • 适配者类(Adaptee)

    需要被适配的类

  • 适配器类(Adapter)

    通过包装一个需要适配的对象,把原接口转换成目标接口。

类适配器模式

基本介绍

适配器类通过继承适配者类,实现目标接口。

代码实现

  1. 创建一个SD卡类(适配者类)
public class SDCard {
    // SD卡中的数据
    public void data(){
        System.out.println("SD卡中的数据");
    }
}
  1. 创建一个USB接口
public interface IUSB {
    // 输出信息
    void outputInfo();
}
  1. 再创建一个电脑的对象,使用USB接口提供读取数据的功能
public class Computer {
    // 读取sd卡中的数据
    public void readDataInfo(IUSB usb) {
        usb.outputInfo();
    }
}

电脑和SD卡都有了,接下来就需要一个读卡器来连接SD卡和电脑。

  1. 创建一个适配器类,继承适配者类,实现目标接口
public class ReadCardAdapter extends SDCard implements IUSB {
    @Override
    public void outputInfo() {
        // 获取SD卡中的数据,输出usb信息
        super.data();
    }
}
  1. 客户端验证
public class Client {
    public static void main(String[] args) {
        Computer computer = new Computer();
        // 电脑读取SD卡中的数据,需要接入读卡器
        computer.readDataInfo(new ReadCardAdapter());
    }
}

输出结果:

SD卡中的数据

可以看到,电脑并未直接与读卡器关联,却读取到了读卡器中的数据。

优缺点

优点:由于其继承了目标接口,所以它可以根据需求重写目标接口中的方法,使得Adapter的灵活性增强了。

缺点:

  • Java是单继承机制,由于类适配器需要继承被适配者类,所以要求目标类必须是接口,有一定局限性。
  • 被适配者类中的方法在Adapter中会暴露出来,也增加了使用的成本。

对象适配器模式

基本介绍

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

对象适配器模式是适配器模式常用的一种。

根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。

代码实现

根据上述概念,改造一下适配器类就完成了,非常简单!

  1. 修改适配器类
public class ReadCardAdapter implements IUSB {
    private SDCard sdCard;
  
    // 1.通过构造器传入SD卡对象,不再需要继承
    public ReadCardAdapter(SDCard sdCard) {
        this.sdCard = sdCard;
    }
  
    @Override
    public void outputInfo() {
        // 2. 修改获取SD卡数据的方法
        sdCard.data();
    }
}
  1. 客户端验证
public class Client {
    public static void main(String[] args) {
        // SD卡
        SDCard sdCard = new SDCard();
        // 读卡器
        ReadCardAdapter readCardAdapter = new ReadCardAdapter(sdCard);
        // 电脑接入
        Computer computer = new Computer();
        computer.readDataInfo(readCardAdapter);
    }
}

输出结果:

SD卡中的数据

电脑同样读到了读卡器中的数据。

小总结

对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。

根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承适配者类的局限性问题,也不再要求目标类必须是接口。使用成本更低,更灵活。

接口适配器模式

基本介绍

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

代码实现

如果你觉得上述两种模式简单,那么这个更简单。

举个例子,假设一个接口中有四个方法,如果我想实现这个接口,那么就必须实现这个接口的所有方法,那么如果我只想使用其中一个方法,怎么做呢?

  1. 创建一个接口,包含四个方法
public interface Interface {
    void method1();
    void method2();
    void method3();
    void method4();
}
  1. 创建一个抽象类实现上述接口,不需要提供具体的实现(空方法)
public abstract class InterfaceAdapter implements Interface {
    @Override
    public void method1() {}

    @Override
    public void method2() {}

    @Override
    public void method3() {}

    @Override
    public void method4() {}
}
  1. 客户端使用
public class Client {
    public static void main(String[] args) {
        InterfaceAdapter interfaceAdapter = new InterfaceAdapter(){
            // 只需重写我们需要使用的接口方法
            @Override
            public void method1() {
                System.out.println("使用了m1的方法");
            }
        };
        interfaceAdapter.method1();
    }
}

输出结果:

使用了m1的方法

可以看到我们使用这种方式实现了仅调用所需方法的需求。

小细节

Q:为什么要使用抽象类实现目标接口,而不使用普通类?

A:因为虽然都能实现接口中的所有方法,但都是空方法,没有具体的实现,不能让用户直接实例化去使用类中的方法,使用抽象类可以强制用户去重写需要使用的方法。

如何做到一个类不被实例化或者不被轻易实例化?

  • 把一个类定义为抽象类
  • 构造方法私有化

有态度的总结

  1. 对象适配器和类适配器思想基本一致,唯一区别就是对象适配器使用组合替代继承;
  2. 对象适配器是常使用的适配器模式;
  3. 接口适配器主要用于一个接口不想使用其所有方法的情况。