资讯专栏INFORMATION COLUMN

一起学设计模式 - 适配器模式

codeKK / 2531人阅读

摘要:建议尽量使用对象的适配器模式,少用继承。适配器模式也是一种包装模式,它与装饰模式同样具有包装的功能,此外,对象适配器模式还具有委托的意思。

适配器模式(Adapter Pattern)属于结构型模式的一种,把一个类的接口变成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作...

概述

当你想使用一个已经存在的类,而它的接口不符合你的需求,或者你想创建一个可重用的类(与不兼容接口无关的类),这时候可以考虑使用适配器模式。同时它也是一种包装模式,它与装饰模式同样具有包装的功能。

案例

笔者算是小米的忠实用户了,从大学期间起至今都是购买的小米,期间发现小米5在推出的时候,它会送一个type-c的转接口给我们,那会type-c数据线应该还不算普及,这种做法还是蛮好的,在使用转接口后Micro USB得以重复利用,这样一来即使原装的米5数据线丢了也没关系,只要有type-c转接口,一样可以用Micro USB充电/连接电脑

类适配器

1.首先定义M4DataLine 代表是Micro USB,我们目的就是通过适配器能够用米4数据线连接米5手机

class M4DataLine {
    public void connection() {
        System.out.println("使用小米4数据线连接...");
    }
}

2.定义客户端使用的接口,与业务相关

interface Target {
    void connection();
}

class M5DataLine implements Target {
    @Override
    public void connection() {
        System.out.println("使用小米5数据线连接...");
    }
}

3.创建适配器类,继承了被适配类,同时实现标准接口

class M5DataLineAdapter extends M4DataLine implements Target {

    @Override
    public void connection() {
        System.out.println("插入 type-c 转接头");
        super.connection();
    }
}

4.客户端代码,测试

public class AdapterMain {

    public static void main(String[] args) {
        Target target = new M5DataLine();
        target.connection();

        Target adapter = new M5DataLineAdapter();
        adapter.connection();
    }
}

5.结果

使用小米5数据线连接...
插入 type-c 转接头
使用小米4数据线连接...
对象适配器

创建适配器类,实现标准接口,将这个调用委托给实现新接口的对象来处理

class M5DataLineAdapter implements Target {

    private Target target;

    public M5DataLineAdapter(Target target) {
        this.target = target;
    }

    @Override
    public void connection() {
        System.out.println("插入 type-c 转接头");
        target.connection();
    }
}

public class AdapterMain {

    public static void main(String[] args) {
        // 使用特殊功能类,即适配类
        Target adapter = new M5DataLineAdapter(new M5DataLine());
        adapter.connection();
    }
}
区别

类适配器:对象继承的方式,静态的定义。

对象适配器:依赖于对象的组合,都是采用对象组合的方式,也就是对象适配器实现的方式。

JDK 中的适配器使用

使用适配器模式的类

java.util.Arrays#asList()
java.io.InputStreamReader(InputStream)
java.io.OutputStreamWriter(OutputStream)

Java I/O 库大量使用了适配器模式,如 ByteArrayInputStream 是一个适配器类,它继承了 InputStream 的接口,并且封装了一个 byte 数组。换言之,它将一个 byte 数组的接口适配成 InputStream 流处理器的接口。

OutputStream 类型中,所有的原始流处理器都是适配器类。ByteArrayOutputStream 继承了 OutputStream 类型,同时持有一个对 byte 数组的引用。它一个 byte 数组的接口适配成 OutputString 类型的接口,因此也是一个对象形式的适配器模式的应用。

FileOutputStream 继承了 OutputStream 类型,同时持有一个对 FileDiscriptor 对象的引用。这是一个将 FileDiscriptor 接口适配成 OutputStream 接口形式的对象型适配器模式。

Reader 类型的原始流处理器都是适配器模式的应用。StringReader 是一个适配器类,StringReader 类继承了 Reader 类型,持有一个对 String 对象的引用。它将 String 的接口适配成 Reader 类型的接口。

Spring 中使用适配器模式的典型应用

在 Spring 的 AOP 里通过使用的 Advice(通知)来增强被代理类的功能。Spring 实现这一 AOP 功能的原理就使用代理模式(1、JDK 动态代理。2、CGLib 字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类,并在代理类的方法前,设置拦截器,通过执行拦截器中的内容增强了代理方法的功能,实现的面向切面编程。

Advice(通知)的类型有:BeforeAdvice、AfterReturningAdvice、ThrowSadvice 等。每个类型 Advice(通知)都有对应的拦截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor。Spring 需要将每个 Advice(通知)都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice 进行转换。

public interface MethodBeforeAdvice extends BeforeAdvice {
    void before(Method method, Object[] args, Object target) throws Throwable;
}

public interface AdvisorAdapter {

    boolean supportsAdvice(Advice advice);

    MethodInterceptor getInterceptor(Advisor advisor);
}

class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable {

    @Override
    public boolean supportsAdvice(Advice advice) {
        return (advice instanceof MethodBeforeAdvice);
    }

    @Override
    public MethodInterceptor getInterceptor(Advisor advisor) {
        MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
        return new MethodBeforeAdviceInterceptor(advice);
    }

}

默认的适配器注册表

public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable {

    private final List adapters = new ArrayList(3);

    public DefaultAdvisorAdapterRegistry() {
        // 注册适配器 
        registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
        registerAdvisorAdapter(new AfterReturningAdviceAdapter());
        registerAdvisorAdapter(new ThrowsAdviceAdapter());
    }

    @Override
    public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException {
        if (adviceObject instanceof Advisor) {
            return (Advisor) adviceObject;
        }
        if (!(adviceObject instanceof Advice)) {
            throw new UnknownAdviceTypeException(adviceObject);
        }
        Advice advice = (Advice) adviceObject;
        if (advice instanceof MethodInterceptor) {
            // So well-known it doesn"t even need an adapter.
            return new DefaultPointcutAdvisor(advice);
        }
        for (AdvisorAdapter adapter : this.adapters) {
            // 检查是否支持,这里调用了适配器的方法 
            if (adapter.supportsAdvice(advice)) {
                return new DefaultPointcutAdvisor(advice);
            }
        }
        throw new UnknownAdviceTypeException(advice);
    }

    @Override
    public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
        List interceptors = new ArrayList(3);
        Advice advice = advisor.getAdvice();
        if (advice instanceof MethodInterceptor) {
            interceptors.add((MethodInterceptor) advice);
        }
        for (AdvisorAdapter adapter : this.adapters) {
            // 检查是否支持,这里调用了适配器的方法 
            if (adapter.supportsAdvice(advice)) {
                interceptors.add(adapter.getInterceptor(advisor));
            }
        }
        if (interceptors.isEmpty()) {
            throw new UnknownAdviceTypeException(advisor.getAdvice());
        }
        return interceptors.toArray(new MethodInterceptor[interceptors.size()]);
    }

    @Override
    public void registerAdvisorAdapter(AdvisorAdapter adapter) {
        this.adapters.add(adapter);
    }

}
总结

优点

可以让任何两个没有关联的类一起运行

提高了类的复用,想使用现有的类,而此类的接口标准又不符合现有系统的需要。通过适配器模式就可以让这些功能得到更好的复用。

增加了类的透明度,客户端只关注结果

使用适配器的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。

缺点

过多使用会导致系统凌乱,追溯困难(内部转发导致,调用A适配成B)

适用场景

系统需要使用一些现有的类,而这些类的接口(如方法名)不符合系统的需要,甚至没有这些类的源代码。

想创建一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

小故事

魏文王问名医扁鹊说:“你们家兄弟三人,都精于医术,到底哪一位最好呢?”
扁鹊答:“大哥最好,二哥次之,我最差。”
文王再问:“那么为什么你最出名呢?”
扁鹊答说:“我大哥治病,是治病于病情发作之前。由于一般人不知道他率先能铲除病因,所以他的名气无法传出去,只有我们家的人才知道。我二哥治病,是治病于病情初起之时。一般人以为他只能治轻微的小病,所以他的名气只及于本乡里。而我扁鹊治病,是治病于病情严重之时。一般人都看到我在经脉上穿针管来放血、在皮肤上敷药等大手术,所以以为我的医术高明,名气因此响遍全国。”
比较起来,能防范于未然是最高明的,但往往因防范在前,不会出现恶果,使事物保持了原态,没有“明显”的功绩而被忽略。正如不见防火英雄,只有救火英雄一样。高明者不见得一定名声显赫。

建议尽量使用对象的适配器模式,少用继承。适配器模式也是一种包装模式,它与装饰模式同样具有包装的功能,此外,对象适配器模式还具有委托的意思。总的来说,适配器模式属于补偿模式,专门用来在系统后期扩展、修改时使用,但要注意不要过度使用适配器模式。

参考文献:《大话设计模式》

IBM developerWorks:适配器模式原理及实例介绍

- 说点什么

全文代码:https://gitee.com/battcn/design-pattern/tree/master/Chapter5/battcn-adapter

个人QQ:1837307557

battcn开源群(适合新手):391619659

微信公众号:battcn(欢迎调戏)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67949.html

相关文章

  • 一起设计模式 - 外观模式

    摘要:适配器是将接口转换为不同接口,而外观模式是提供一个统一的接口来简化接口。 外观模式(Facade Pattern)属于结构型模式的一种,为子系统中的一组接口提供一个统一的入口,它通过引入一个外观角色来简化客户端与子系统之间的交互... 概述 外观模式是一种使用频率非常高的结构型设计模式,当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时...

    zone 评论0 收藏0
  • 互联网常用设计模式——通往架构师的第一步

    摘要:设计模式的分类经典应用框架中常见的设计模式分为三类创建型模式对类的实例化过程的抽象。对象的结构模式是动态的。对象的行为模式则使用对象的聚合来分配行为。设计模式是个好东西,以后肯定还要进一步的学习,并且在项目中多实践,提升自己的设计能力。 什么是设计模式? Christopher Alexander 说过:每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样...

    张红新 评论0 收藏0
  • java 基础 - 收藏集 - 掘金

    摘要:基础知识复习后端掘金的作用表示静态修饰符,使用修饰的变量,在中分配内存后一直存在,直到程序退出才释放空间。将对象编码为字节流称之为序列化,反之将字节流重建成对象称之为反序列化。 Java 学习过程|完整思维导图 - 后端 - 掘金JVM 1. 内存模型( 内存分为几部分? 堆溢出、栈溢出原因及实例?线上如何排查?) 2. 类加载机制 3. 垃圾回收 Java基础 什么是接口?什么是抽象...

    makeFoxPlay 评论0 收藏0
  • java篇 - 收藏集 - 掘金

    摘要:进阶多线程开发关键技术后端掘金原创文章,转载请务必将下面这段话置于文章开头处保留超链接。关于中间件入门教程后端掘金前言中间件 Java 开发人员最常犯的 10 个错误 - 后端 - 掘金一 、把数组转成ArrayList 为了将数组转换为ArrayList,开发者经常... Java 9 中的 9 个新特性 - 后端 - 掘金Java 8 发布三年多之后,即将快到2017年7月下一个版...

    OpenDigg 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<