一块儿学设计模式 - 适配器模式

适配器模式(Adapter Pattern)属于结构型模式的一种,把一个类的接口变成客户端所期待的另外一种接口,从而使本来接口不匹配而没法一块儿工做的两个类可以在一块儿工做...html

<!-- more -->java

概述

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

http://os71dlblu.bkt.clouddn.com/article/images/20171104/java/design-pattern/adapter-pattern/1.png

案例

笔者算是小米的忠实用户了,从大学期间起至今都是购买的小米,期间发现小米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.客户端代码,测试ide

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数据线链接...

对象适配器

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

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 功能的原理就使用代理模式(一、JDK 动态代理。二、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<AdvisorAdapter> adapters = new ArrayList<AdvisorAdapter>(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<MethodInterceptor> interceptors = new ArrayList<MethodInterceptor>(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(欢迎调戏)

相关文章
相关标签/搜索