结构型模式

本贴最后更新于 471 天前,其中的信息可能已经事过景迁

结构型模式主要涉及如何组合各种对象以便获得更好、更灵活的结构。虽然面向对象的继承机制提供了最基本的子类扩展父类的功能,但结构型模式不仅仅简单地使用继承,而更多地通过组合与运行期的动态组合来实现更灵活的功能。

适配器


将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适配器模式是 Adapter,也称 Wrapper,是指如果一个接口需要 B 接口,但是待传入的对象却是 A 接口,怎么办?

我们举个例子。如果去美国,我们随身带的电器是无法直接使用的,因为美国的插座标准和中国不同,所以,我们需要一个适配器:

adapter

在程序设计中,适配器也是类似的。我们已经有一个 Task​类进行一个 1 到 num 的求和,实现了 Callable​接口:

public class Task implements Callable<Long> {
    private long num;
    public Task(long num) {
        this.num = num;
    }

    public Long call() throws Exception {
        long r = 0;
        for (long n = 1; n <= this.num; n++) {
            r = r + n;
        }
        System.out.println("Result: " + r);
        return r;
    }
}

现在,我们想通过一个线程去执行它:

Callable<Long> callable = new Task(123450000L);
Thread thread = new Thread(callable); // compile error!
thread.start();

发现编译不过!因为 Thread 接收 Runnable 接口,但不接收 Callable 接口,肿么办?

一个办法是改写 Task 类,把实现的 Callable 改为 Runnable,但这样做不好,因为 Task 很可能在其他地方作为 Callable 被引用,改写 Task 的接口,会导致其他正常工作的代码无法编译。

另一个办法不用改写 Task 类,而是用一个 Adapter,把这个 Callable 接口“变成”Runnable 接口,这样,就可以正常编译:

Callable<Long> callable = new Task(123450000L);
Thread thread = new Thread(new RunnableAdapter(callable));
thread.start();

这个 RunnableAdapter 类就是 Adapter,它接收一个 Callable,输出一个 Runnable。怎么实现这个 RunnableAdapter 呢?我们先看完整的代码:

public class RunnableAdapter implements Runnable {
    // 引用待转换接口:
    private Callable<?> callable;

    public RunnableAdapter(Callable<?> callable) {
        this.callable = callable;
    }

    // 实现指定接口:
    public void run() {
        // 将指定接口调用委托给转换接口调用:
        try {
            callable.call();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

编写一个 Adapter 的步骤如下:

  1. 实现目标接口,这里是 Runnable
  2. 内部持有一个待转换接口的引用,这里是通过字段持有 Callable 接口;
  3. 在目标接口的实现方法内部,调用待转换接口的方法。

这样一来,Thread 就可以接收这个 RunnableAdapter,因为它实现了 Runnable 接口。Thread 作为调用方,它会调用 RunnableAdapterrun() 方法,在这个 run() 方法内部,又调用了 Callablecall() 方法,相当于 Thread 通过一层转换,间接调用了 Callablecall() 方法。

适配器模式在 Java 标准库中有广泛应用。比如我们持有数据类型是 String[],但是需要 List 接口时,可以用一个 Adapter:

String[] exist = new String[] {"Good", "morning", "Bob", "and", "Alice"};
Set<String> set = new HashSet<>(Arrays.asList(exist));

注意到 List<T> Arrays.asList(T[]) 就相当于一个转换器,它可以把数组转换为 List

我们再看一个例子:假设我们持有一个 InputStream,希望调用 readText(Reader) 方法,但它的参数类型是 Reader 而不是 InputStream,怎么办?

当然是使用适配器,把 InputStream“变成”Reader

InputStream input = Files.newInputStream(Paths.get("/path/to/file"));
Reader reader = new InputStreamReader(input, "UTF-8");
readText(reader);

InputStreamReader 就是 Java 标准库提供的 Adapter,它负责把一个 InputStream 适配为 Reader。类似的还有 OutputStreamWriter

如果我们把 readText(Reader) 方法参数从 Reader 改为 FileReader,会有什么问题?这个时候,因为我们需要一个 FileReader 类型,就必须把 InputStream 适配为 FileReader

FileReader reader = new InputStreamReader(input, "UTF-8"); // compile error!

直接使用 InputStreamReader 这个 Adapter 是不行的,因为它只能转换出 Reader 接口。事实上,要把 InputStream 转换为 FileReader 也不是不可能,但需要花费十倍以上的功夫。这时,面向抽象编程这一原则就体现出了威力:持有高层接口不但代码更灵活,而且把各种接口组合起来也更容易。一旦持有某个具体的子类类型,要想做一些改动就非常困难。

小结

Adapter 模式可以将一个 A 接口转换为 B 接口,使得新的对象符合 B 接口规范。

编写 Adapter 实际上就是编写一个实现了 B 接口,并且内部持有 A 接口的类:

public BAdapter implements B {
    private A a;
    public BAdapter(A a) {
        this.a = a;
    }
    public void b() {
        a.a();
    }
}

在 Adapter 内部将 B 接口的调用“转换”为对 A 接口的调用。

只有 A、B 接口均为抽象接口时,才能非常简单地实现 Adapter 模式。

  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Unity

    Unity 是由 Unity Technologies 开发的一个让开发者可以轻松创建诸如 2D、3D 多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎。

    25 引用 • 7 回帖 • 174 关注
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    325 引用 • 1395 回帖
  • HTML

    HTML5 是 HTML 下一个的主要修订版本,现在仍处于发展阶段。广义论及 HTML5 时,实际指的是包括 HTML、CSS 和 JavaScript 在内的一套技术组合。

    107 引用 • 295 回帖 • 1 关注
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    334 引用 • 323 回帖 • 1 关注
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 52 关注
  • 996
    13 引用 • 200 回帖 • 6 关注
  • Openfire

    Openfire 是开源的、基于可拓展通讯和表示协议 (XMPP)、采用 Java 编程语言开发的实时协作服务器。Openfire 的效率很高,单台服务器可支持上万并发用户。

    6 引用 • 7 回帖 • 95 关注
  • Log4j

    Log4j 是 Apache 开源的一款使用广泛的 Java 日志组件。

    20 引用 • 18 回帖 • 31 关注
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 384 关注
  • V2Ray
    1 引用 • 15 回帖
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    5 引用 • 26 回帖 • 529 关注
  • PWA

    PWA(Progressive Web App)是 Google 在 2015 年提出、2016 年 6 月开始推广的项目。它结合了一系列现代 Web 技术,在网页应用中实现和原生应用相近的用户体验。

    14 引用 • 69 回帖 • 154 关注
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 654 关注
  • DNSPod

    DNSPod 建立于 2006 年 3 月份,是一款免费智能 DNS 产品。 DNSPod 可以为同时有电信、网通、教育网服务器的网站提供智能的解析,让电信用户访问电信的服务器,网通的用户访问网通的服务器,教育网的用户访问教育网的服务器,达到互联互通的效果。

    6 引用 • 26 回帖 • 510 关注
  • 微软

    微软是一家美国跨国科技公司,也是世界 PC 软件开发的先导,由比尔·盖茨与保罗·艾伦创办于 1975 年,公司总部设立在华盛顿州的雷德蒙德(Redmond,邻近西雅图)。以研发、制造、授权和提供广泛的电脑软件服务业务为主。

    8 引用 • 44 回帖
  • Firefox

    Mozilla Firefox 中文俗称“火狐”(正式缩写为 Fx 或 fx,非正式缩写为 FF),是一个开源的网页浏览器,使用 Gecko 排版引擎,支持多种操作系统,如 Windows、OSX 及 Linux 等。

    8 引用 • 30 回帖 • 407 关注
  • HBase

    HBase 是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的 Google 论文 “Bigtable:一个结构化数据的分布式存储系统”。就像 Bigtable 利用了 Google 文件系统所提供的分布式数据存储一样,HBase 在 Hadoop 之上提供了类似于 Bigtable 的能力。

    17 引用 • 6 回帖 • 73 关注
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 211 关注
  • Gitea

    Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。

    4 引用 • 16 回帖 • 4 关注
  • DevOps

    DevOps(Development 和 Operations 的组合词)是一组过程、方法与系统的统称,用于促进开发(应用程序/软件工程)、技术运营和质量保障(QA)部门之间的沟通、协作与整合。

    47 引用 • 25 回帖
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 399 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 680 关注
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    142 引用 • 442 回帖
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    266 引用 • 665 回帖 • 1 关注
  • Wide

    Wide 是一款基于 Web 的 Go 语言 IDE。通过浏览器就可以进行 Go 开发,并有代码自动完成、查看表达式、编译反馈、Lint、实时结果输出等功能。

    欢迎访问我们运维的实例: https://wide.b3log.org

    30 引用 • 218 回帖 • 629 关注
  • 安全

    安全永远都不是一个小问题。

    199 引用 • 816 回帖
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    567 引用 • 3532 回帖