漫话:如何给女朋友解释什么是适配器模式?

5,355 阅读7分钟

周末窝在家里面打王者荣耀,女朋友在旁边玩我的电脑,我嫌她播放的综艺节目声音比较大,于是建议她戴耳机。

适配器模式

Adapter Pattern,通常被翻译成适配器模式,有时候也叫做包装模式(wrapper pattern),是GOF 23种设计模式之一。主要作用是将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

《Design Patterns: Elements of Reusable Object-Oriented Software》(《设计模式》),由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为"Gang of Four),简称GOF。

GOF中将适配器模式分为类适配器模式和对象适配器模式。

对象适配器模式

在这种适配器模式中,适配器容纳一个它包裹的类的实例。在这种情况下,适配器调用被包裹对象的物理实体。

类适配器模式

这种适配器模式下,适配器继承自已实现的类(一般多重继承)。

二者区别仅在于适配器角色对于被适配角色的适配是通过继承还是组合来实现的,由于Java中不支持多继承,而且类适配器模式有破坏封装之嫌,而且我们也提倡多用组合少用继承。所以本文主要介绍对象适配器

适配器模式用途

我们生活中经常需要用到插口转换器,比如现在很多手机都只有一个插口,这个口可以直接用来充电和听音乐。但是前提是我们使用的充电器和耳机的插口要和这个设备适配的。

目前市面上很多手机的插口都是type-c或者Lightning型号:

但是,我们常用的耳机型号却是2.5mm和3.5mm的圆形接口:

所以,当我们想要把自己的3.5mm圆形接口的耳机插入Lightning或者type-c接口的时候,就需要一个转换器:
同理,在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足。如以下类似的场景:

1、系统需要使用现有的类,而此类的接口不符合系统的需要。

2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。

3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)

适配器模式,就可以解决以上的问题。

适配器模式实现方式

下面我们就使用适配器模式,模拟一种场景:使用一个安卓的type-c充电器给只支持 Lightning接口的苹果手机充电(假设可以完美支持)。

已知,我们有一个type-c充电器、一个Lightning插口的苹果手机。无论是type-c还是Lightning,都是一种标准,在代码中,标准即接口。所以我们先定义两个接口:

/**
 * Lightning充电接口
 */
public interface LightningInterface {
    public void chargeWithLightning();
}

/**
 * TypeC充电接口
 */
public interface TypeCInterface {
    public void chargeWithTypeC();
}

接下来定义我们的苹果手机,他只支持使用 Lightning插口充电:

public class IphoneX {

    private LightningInterface lightningInterface;

    public IphoneX() {
    }

    public IphoneX(LightningInterface lightningInterface) {
        this.lightningInterface = lightningInterface;
    }

    public void charge() {
        System.out.println("开始给我的IphoneX手机充电...");
        lightningInterface.chargeWithLightning();
        System.out.println("结束给我的IphoneX手机充电...");
    }
    //setter/getter
}

然后再来看看我们的安卓充电器应该如何定义:

/**
 * 安卓设备的充电器
 */
public class AndroidCharger implements TypeCInterface {
    @Override
    public void chargeWithTypeC() {
        System.out.println("使用Type-C型号的充电器充电...");
    }
}

有了安卓充电器和苹果手机。接下来,我们就要定义一个适配器了,希望通过这个适配器,我们可以实现使用安卓设备的充电器给苹果手机充电:

public class Adapter implements LightningInterface {
    private TypeCInterface typeCInterface;

    public Adapter() {
    }

    public Adapter(TypeCInterface typeCInterface) {
        typeCInterface = typeCInterface;
    }

    @Override
    public void chargeWithLightning() {
        typeCInterface.chargeWithTypeC();
    }

    //setter/getter
}

这个适配器实现了LightningInterface,并组合了TypeCInterface,当外部调用chargeWithLightning方法的时候,实际上调用的是typeCInterface.chargeWithTypeC方法。

就像电源适配器,他实现的是一个Lightning的规范,自身是一个Lightning的插头,但实际充电的时候,他是通过typc-c的电源进行的,他起到的是一个中间转换的作用。

接着我们定义客户端,实现我们想要的充电功能:

public class Main {
    public static void main(String[] args) {

            Adapter adapter  = new Adapter(new AndroidCharger());
            IphoneX iphoneX = new IphoneX();
            iphoneX.setLightningInterface(adapter);
            iphoneX.charge();
        }
}

输出结果如下:

开始给我的IphoneX手机充电...
使用Type-C型号的充电器充电...
结束给我的IphoneX手机充电...

上面的例子通过适配器,我们使用一个安卓的type-c充电器给一个只支持Lightning接口的苹果手机充电。

上面的代码,就是一个适配器模式的例子,这个例子中,共出现了四种角色:

以上四个角色中,目标抽象类(Lightning接口)、适配者类(安卓充电器)、客户端(苹果手机)都是原来代码中就有的,我们完全不需要对他们进行修改。只需要引入一个适配器(接口转换器)即可。

优缺点

优点

适配器模式(对象适配器模式),是一种组合优于集成的思想的实现。通过使用适配器模式,我们可以最大程度的复用已有的了类和代码。他主要有以下有点:

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。

  • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。

  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

缺点

当然,适配器模式并不是完美的,过度使用还是会带来一些问题的。缺点如下:

  • 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

使用场景

关于适配器模式的使用场景,一般主要是当我们需要修改一些正在运行着的代码,并且希望可以复用原有代码实现新的功能的时候,就要考虑适配器模式。

在Spring框架中,就大量的使用了适配器模式,读者可以打开自己的IDE,尝试着以关键字"Adapter"全局搜索下,一定会有很多的实际应用。

当你遇到的问题,和你想用安卓充电器给苹果手机充电类似的时候,就一定要想到适配器模式哦!

这是关于设计模式的第三篇,前两篇分别是:《漫话:如何给女朋友解释什么是策略模式?》《漫话:如何给女朋友解释什么是单例模式?》还想学习哪种设计模式,欢迎留言哦。