结构篇-适配器模式

适配器模式通过提供一个中间转换层,解决不同接口之间的兼容性问题。本文通过电源插头与插孔的冲突实例,介绍了对象适配器和类适配器两种模式,展示了如何在不修改原有代码的基础上实现接口匹配,从而达到系统扩展和复用的目的。适配器模式降低了组件间的耦合,提高了系统的灵活性。
摘要由CSDN通过智能技术生成


适配器模式(Adapter)通常也被称为转换器,顾名思义,它一定是进行适应与匹配工作的物件。当一个对象或类的接口不能匹配用户所期待的接口时,适配器就充当中间转换的角色,以达到兼容用户接口的目的,同时适配器也实现了客户端与接口的解耦,提高了组件的可复用性。


提示:以下是本篇文章正文内容,下面案例可供参考

一、跨越鸿沟靠适配

对象是多样化的,对象之间通过信息交换,也就是互动、沟通,世界才充满生机,否则就是死水一潭。人类最常用的沟通方式就是语言,两个人对话时,一方通过嘴巴发出声音,另一方则通过耳朵接收这些语言信息,所以嘴巴和耳朵(接口)必须兼容同一种语言(参数)才能达到沟通的目的。试想,我们跟不懂中文的人讲中文一定是徒劳的,因为对方根本无法理解我们在讲什么,更不要说人类和动物对话了,接口不兼容的结果就是对牛弹琴。

对象是多样化的,对象之间通过信息交换,也就是互动、沟通,世界才充满生机,否则就是死水一潭。人类最常用的沟通方式就是语言,两个人对话时,一方通过嘴巴发出声音,另一方则通过耳朵接收这些语言信息,所以嘴巴和耳朵(接口)必须兼容同一种语言(参数)才能达到沟通的目的。试想,我们跟不懂中文的人讲中文一定是徒劳的,因为对方根本无法理解我们在讲什么,更不要说人类和动物对话了,接口不兼容的结果就是对牛弹琴,如图10-1所示。

二、插头与插孔的冲突

举一个生活中常见的实例,我们新买了一台电视机,其电源插头是两相的,不巧的是墙上的插孔却是三相的,这时电视机便无法通电使用。我们以代码来重现这个场景。

1.三相插孔接口

public interface TriplePin {
    // 参数分别是火线、零线、地线
    public void electrify(int l, int n, int e);
}

说明:

  1. 我们为三相插孔接口TriplePin定义了一个三插通电标准electrify(),其中3个参数l、n、e分别对应火线(live)、零线(null)和地线(earth)。

2.两相插孔接口

public interface DualPin {
    // 这里没有地线
    public void electrify(int l, int n);
}

说明:

  1. 与三相插孔接口所不同的是,两相插孔接口DualPin定义的是2个参数的通电标准,可以看到electrify()的参数中缺少了地线e。

3.电视机机类TV

客户端类代码干净、利落,我们将构造出来的女生类实例作为参数传给化妆品装饰器类的构造方法,这就好像为女生外表包裹了一层化妆品一样,对象结构非常生动、形象。接着,我们调用的是化妆品的展示方法show(),运行结果立竿见影,除女生自己的素颜展示结果之外还加上了额外的化妆效果。

public class TV implements DualPin {
    @Override
    public void electrify(int l, int n) {
        System.out.println("火线通电:" + l + ",零线通电:" + n);
        System.out.println("电视开机");
    }
}

说明:

  1. 电视机类TV实现了两相插孔接口DualPin,所以代码第4行的通电方法electrify()只接通火线与零线,然后开机。

4.客户端类

代码很简单,而目前我们面临的问题是,墙上的接口是三相插孔,而电视机实现的是两相插孔,二者无法匹配,如代码所示,客户端无法将两相插头与三相插孔完成接驳。

public class Client {
    public static void main(String[] args) {
        TriplePin triplePinDevice = new TV(); //接口不兼容,此处保持类型不匹配
    }
}

二、通用适配

针对接口不兼容的情况,可能有人会提出比较极端的解决方案,就是把插头掰弯强行适配,若是三相插头接两相插孔的话,就把零线插针拔掉。虽然目的达到了,但经过这么一番暴力修改,插头也无法再兼容其原生接口了,这显然是违背设计模式原则的。为了不破坏现有的电视机插头,我们需要一个适配器来做电源转换,有了它我们便可以顺利地把电视机两相插头转接到墙上的三相插孔中了。

1. 适配器

public class Adapter implements TriplePin {

    private DualPin dualPinDevice;

    //创建适配器时,需要把两插设备接入进来
    public Adapter(DualPin dualPinDevice) {
        this.dualPinDevice = dualPinDevice;
    }

    //适配器实现的是目标接口
    @Override
    public void electrify(int l, int n, int e) {
        //调用被适配设备的两插通电方法,忽略地线参数e
        dualPinDevice.electrify(l, n);
    }
}

说明:

  1. 与电视机类不同的是,适配器类Adapter实现的是三相插孔接口,这意味着它能够兼容墙上的三相插孔了。
  2. 注意代码定义的两相插孔的引用,我们在构造方法中对其进行初始化,也就是说,适配器中嵌入一个两相插孔,任何此规格的设备都是可以接入进来的。
  3. 最后,在实现的三相插孔通电方法中,适配器转去调用了接入的两插设备,并且丢弃了地线参数e,这就完成了三相转两相的调制过程,最终达到适配效果。至此,这个适配器就可以将任意两插设备匹配到三相插孔上了。

2.客户端类

public class Client {
    public static void main(String[] args) {
        DualPin dualPinDevice = new TV();  //构造两插电视机
        TriplePin triplePinDevice = new Adapter(dualPinDevice);  //适配器接驳两端
        triplePinDevice.electrify(1, 0, -1); //此处调用的是三插通电的标准
    }
}
输出结果:
火线通电:1,零线通电:0
电视开机

注意:

  1. 适配器并不关心接入的设备是电视机、洗衣机还是电冰箱,只要是两相插头的设备均可以进行适配,所以说它是一种通用的适配器。

三、专属适配

除了上面所讲的“对象适配器”,我们还可以用“类适配器”实现接口的匹配,这是实现适配器模式的另一种方式。顾名思义,既然是类适配器,那么一定是属于某个类的“专属适配器”,也就是在编码阶段已经将被匹配的设备与目标接口进行对接了。我们继续之前的例子,请参看代码。

1.电视机专属适配器

//I/O流处理类的应用
File file = new File("/压缩包.zip");
ZipInputStream zipInputStream = new ZipInputStream(
     new BufferedInputStream(
         new FileInputStream(file)
    )
);

说明:

  1. 类适配器模式实现起来更简单,如代码所示,电视机专属适配器类中并未包含被适配对象(如电视机)的引用,而是在开始定义类的时候就直接继承自电视机了,此外还一并实现了三相插孔接口。接着在三插通电方法中,我们利用“super”关键字调用父类(电视机类TV)定义的两插通电方法,以实现适配。

2.客户端类

public class Client {
    public static void main(String[] args) {
        TriplePin tvAdapter = new TVAdapter(); //电视机专属三插适配器插入三相插孔
        tvAdapter.electrify(1, 0, -1);
    }
}
输出结果:
火线通电:1,零线通电:0
电视开机

注意:

  1. 如代码所示,我们直接将实例化后的适配器对象接入墙上的三相插孔,接着直接通电使用即可。如输出结果所示,类适配器模式不但使用起来更加简单,而且其效果与对象适配器模式毫无二致。
  2. 这个类适配器是继承自电视机的子类,在类定义的时候就已经与电视机完成了接驳,也就是说,类适配器与电视机的继承关系让它固化为一种专属适配器,这就造成了继承耦合,倘若我们需要适配其他两插设备,它就显得无能为力了。例如要适配两相插头的洗衣机,我们就不得不再写一个“洗衣机专属适配器”,这显然是一种代码冗余,说明适配器兼容性差。
  3. 事物没有绝对的好与坏,对象适配器与类适配器各有各的适用场景。假如我们只需要匹配电视机这一种设备,并且未来也没有任何其他的设备扩展需求,那么类适配器使用起来可能更加简便,所以具体用什么、怎么用还要视具体情况而定,切不要有过分偏执、非黑即白的思想。

总结

众所周知,反复修改代码的代价是巨大的,因为所有依赖关系都要受到牵连,这不但会引入更多没有必要的重构与测试工作,而且其波及范围难以估量,可能会带来不可预知的风险,结果得不偿失。适配器模式让兼容性问题在不必修改任何代码的情况下得以解决,其中适配器类是核心

1. 对象适配器

  1. 对象适配器模式的各角色定义如下。
  • Target(目标接口):客户端要使用的目标接口标准,对应本章例程中的三相插孔接口TriplePin。
  • Adapter(适配器):实现了目标接口,负责适配(转换)被适配者的接口specificRequest()为目标接口request(),对应本章例程中适配器类Adapter。
  • Adaptee(被适配者):被适配者的接口标准,目前不能兼容目标接口的问题接口,可以有多种实现类,对应本章例程中的两相插孔接口DualPin。
  • Client(客户端):目标接口的使用者。

2. 类适配器

在这里插入图片描述

  1. 类适配器模式的各角色定义如下。
  • Target(目标接口):客户端要使用的目标接口标准,对应本章例程中的三相插孔接口TriplePin。
  • Adapter(适配器):实现了目标接口,负责适配(转换)被适配者的接口specificRequest()为目标接口request(),对应本章例程中的电视机专属适配器类TVAdapter。
  • Adaptee(被适配者):被适配者的类实现,目前不能兼容目标接口的问题类,对应本章例程中的电视机类TV。
  • Client(客户端):目标接口的使用者。

总结

  1. 对象适配器模式与类适配器模式基本相同,二者的区别在于前者的Adaptee(被适配者)以接口形式出现并被Adapter(适配器)引用,而后者则以父类的角色出现并被Adapter(适配器)继承,所以前者更加灵活,后者则更为简便。其实不管何种模式,从本质上看适配器至少都应该具备模块两侧的接口特性,如此才能承上启下,促成双方的顺利对接与互动。
  2. 成功利用适配器模式对系统进行扩展后,我们就不必再为解决兼容性问题去暴力修改类接口了,转而通过适配器,以更为优雅、巧妙的方式将两侧“对立”的接口“整合”在一起,顺利化解双方难以调和的矛盾,最终使它们顺利接通。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhixuChen200

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值