Java设计模式系列八(适配器模式)

原创
2019/05/19 18:33
阅读数 47

Java设计模式系列文章:

  1. Java设计模式系列一(前言)

  2. Java设计模式系列二(单例模式)

  3. Java设计模式系列三(工厂方法模式)

  4. Java设计模式系列四(抽象工厂模式)

  5. Java设计模式系列五(原型模式)

  6. Java设计模式系列六(建造者模式)

  7. Java设计模式系列七(过滤器模式)

本篇将和大家详解Java设计模式之——适配器模式。


什么是适配器模式

适配器模式(Adapter Pattern):是作为两个不兼容的接口之间的桥梁,将一个接口转换成客户所希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。


适配器模式包括3种形式:类适配器模式、对象适配器模式、接口适配器模式(或又称作缺省适配器模式)


生活中我们有很多常见的例子也用到了类似适配器模式,比如Type-C转接头,连接mac电脑和外接显示屏,还有像插座的接口适配器,电压适配器,还有连接粗细不同的水管的连接头,也是一种适配器模式。


适配器模式三种角色

  • Target(目标抽象类):定义用户需要的相关接口,作为接口或者是抽象类存在。

  • Adaptee(源,需要适配者):定义了需要适配的接口,要被适配的角色,定义类一系列的接口,实现用户需要的一些业务功能。但是这部分并不属于新的系统,可能是在其他软件系统中的,没有源码。

  • Adapter(适配器):模式的核心类,作为转换器对Target和Adaptee进行适配。


以生活中常见的水管连接头为例,因为很多原因,我们经常需要把管口粗细不同的水管连接在一起通水,比如想把管径24mm的水管连接到16mm的水管上,但是因为管口直径不同,不能直接接在一起,怎么办呢?如图,我们会买一个水管接头,这样就可以把管径不同的水管连在一起。

这个水管接头就是一个适配器Adapter,通过接口就可以把24mm的水管(Adaptee)和16mm的水管(Target)连在一起,解决了管径不同连接的问题。三者之间的UML图如下:


下面用代码来实现讲解三种形式的适配器模式。


类适配器模式

1、编写源Adaptee,需要适配者(24mm水管)

/** * <p class="detail"> * 功能:源Adaptee * </p> * * @author Moore * @ClassName Waterpipe 24 adaptee. * @Version V1.0. * @date 2019.05.18 17:50:44 */public class Waterpipe24Adaptee {    /**     * <p class="detail">     * 功能:只能连接24mm管径水管     * </p>     *     * @author Moore     * @date 2019.05.18 17:50:44     */    public void connect24(){        System.out.println("连接24mm水管");    }}


2、编写目标Target(16mm水管)

/** * <p class="detail"> * 功能:目标Target * </p> * * @author Moore * @ClassName Waterpipe 16 target. * @Version V1.0. * @date 2019.05.18 17:53:08 */public interface Waterpipe16Target {    /**     * <p class="detail">     * 功能:只能连接16mm水管     * </p>     *     * @author Moore     * @date 2019.05.18 17:53:08     */    void connect16();}


3、编写适配器Adapter(将24mm水管和16mm水管连接起来,满足需求)

/** * <p class="detail"> * 功能: 适配器;连接24mm和16mm水管 * </p> * * @author Moore * @ClassName Adapter. * @Version V1.0. * @date 2019.05.18 17:56:01 */public class Adapter extends Waterpipe24Adaptee implements Waterpipe16Target {    @Override    public void connect16() {        System.out.println("连接16号水管");    }}


4、客户端使用

/** * <p class="detail"> * 功能:客户端使用 * </p> * * @author Moore * @ClassName Client. * @Version V1.0. * @date 2019.05.18 17:57:10 */public class Client {    public static void main(String[] args) {        Adapter adapter = new Adapter();        System.out.println("使用水管接头适配器...");        adapter.connect24();        adapter.connect16();    }}

运行程序,控制台输出:

使用水管接头适配器...连接24mm水管连接16号水管


类适配器使用的是继承的方式,直接继承了Adaptee,所以无法对Adaptee的子类进行适配。而对象适配器模式则可以解决这个问题,下面来看。


对象适配器模式

设计模式一书中提到,对象适配器模式是另外6种结构型设计模式的起源,这儿暂时按下不表,等后面再一一解释。先来看对象适配器模式怎么实现。


因为源和目标都不会改变,所以代码不重复贴出,只贴出适配器代码:

/** * <p class="detail"> * 功能:对象适配器 * </p> * * @author Moore * @ClassName Object adapter. * @Version V1.0. * @date 2019.05.18 18:30:23 */public class ObjectAdapter implements Waterpipe16Target {
private Waterpipe24Adaptee waterpipe24Adaptee;
public ObjectAdapter(Waterpipe24Adaptee waterpipe24Adaptee) { this.waterpipe24Adaptee = waterpipe24Adaptee; }
/** * <p class="detail"> * 功能:只能连接16mm水管 * </p> * * @author Moore * @date 2019.05.18 17:53:08 */ @Override public void connect16() { System.out.println("连接16mm水管"); }
/** * <p class="detail"> * 功能: 源类AdapteeWaterpipe24有方法connect24(), 因此适配器类直接委派即可 * </p> * * @author Moore * @date 2019.05.18 18:28:01 */ public void connect24() { waterpipe24Adaptee.connect24();    }}

客户端调用:

public class Client {    public static void main(String[] args) {//        Adapter adapter = new Adapter();//        System.out.println("使用水管接头适配器...");//        adapter.connect24();//        adapter.connect16();

ObjectAdapter objectAdapter = new ObjectAdapter(new Waterpipe24Adaptee()); System.out.println("使用水管接头适配器..."); objectAdapter.connect24(); objectAdapter.connect16(); }}

运行结果:


可以看出,对象适配器模式是将源注入进来,使用对象组合的方式,是动态组合的方式。


这样做有什么好处呢?


从实现方式可以看出,类适配器使用对象继承的方式,是静态的定义方式,所以无法对源类的子类进行适配。而对象适配器使用的是组合的方式,所以Adaptee及其子孙类都可以被适配。另外,对象适配器对于增加一些新行为非常方便,而且新增加的行为同时适用于所有的源。


缺省适配模式

  缺省适配(Default Adapter)模式为一个接口提供缺省实现,这样子类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。作为适配器模式的一个特例,缺省是适配模式在JAVA语言中有着特殊的应用。


举个简单的例子,老师,有教语文的、教英语的、教数学的、教体育教音乐的等等,把老师设成一个接口,那么学校每次找一个老师的时候,这个老师要会教所有的课程吗?显然是不可能的,在这里就要有一个缺省适配器,缺省适配器是个抽象类,仅仅implements而不实现。然后客户端直接使用Adapter即可选择需要实现的方法,而不用实现全部。


例如老师接口:

/** * <p class="detail"> * 功能: 老师接口 * </p> * * @author Moore * @ClassName Teacher. * @Version V1.0. * @date 2019.05.18 22:44:34 */public interface Teacher {    void teachChinese();
void teachMath();
void teachEnglish();
void teachSports();
void teachMusic(); //等等等等....}

老师的缺省适配器,仅仅implements但不做任何具体实现

/** * <p class="detail"> * 功能:缺省适配器 * </p> * * @author Moore * @ClassName Deafault teacher adapter. * @Version V1.0. * @date 2019.05.18 22:47:23 */public abstract class DeafaultTeacherAdapter implements Teacher{    @Override    public void teachChinese() {
}
@Override public void teachMath() {
}
@Override public void teachEnglish() {
}
@Override public void teachSports() {
}
@Override public void teachMusic() {
}}

这时候如果学校要招一个老师,只需要继承默认适配器,实现教某一个课程的老师的方法就好了。

/** * <p class="detail"> * 功能:学校类 * </p> * * @author Moore * @ClassName School. * @Version V1.0. * @date 2019.05.18 22:51:09 */public class School extends DeafaultTeacherAdapter{
@Override public void teachMusic() { System.out.println("找到一个音乐老师"); }
@Override public void teachChinese() { System.out.println("找到一个教语文的老师"); }}


测试代码

public class Client {    public static void main(String[] args) {        // 类适配器测试//        Adapter adapter = new Adapter();//        System.out.println("使用水管接头适配器...");//        adapter.connect24();//        adapter.connect16();
// 对象适配器测试// ObjectAdapter objectAdapter = new ObjectAdapter(new Waterpipe24Adaptee());// System.out.println("使用水管接头适配器...");// objectAdapter.connect24();// objectAdapter.connect16();
// 缺省适配器测试 School school = new School(); school.teachChinese(); school.teachMusic(); }}

运行结果:


适配器模式的用意是要改变源的接口,以便于目标接口相容。缺省适配器的用意稍有不同,它是为了方便建立一个不平庸的适配器类而提供的一种平庸实现。

在任何时候,如果不准备实现一个接口的所有方法时,就可以使用“缺省适配模式”制造一个抽象类,给出所有方法的平庸的具体实现。这样,从这个抽象类再继承下去的子类就不必实现所有的方法了。


总结

适配器模式优点:
  • 完美实现解耦,通过增加适配器类将适配者与目标接口联系起来,无需修改原有实现。

  • 提高复用性,适配器类可以在多个系统使用。

  • 提高可扩展性,在实现适配器功能的时候,可以扩展自己源的行为(增加方法),从而自然地扩展系统的功能。

  • 符合开闭原则。


缺点:

滥用适配器,会让系统变得非常零乱。例如,明明看到调用的是A接口,其实内部被适配成了B接口的实现。系统如果出现很多这样的情况,维护起来将非常麻烦。适配器一般只在需求调整时考虑使用,设计系统架构时不建议使用。


如果觉得本文有用,请推荐给更多有需要的人,谢谢!如果发现问题,欢迎留言,请随时批评改正,谢谢!


本文分享自微信公众号 - 码之初(ma_zhichu)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部