【设计模式自习室】适配器模式

2020-01-14 16:04:51 浏览数 (1)

前言

《设计模式自习室》系列,顾名思义,本系列文章带你温习常见的设计模式。主要内容有:

  • 该模式的介绍,包括:
    • 引子、意图(大白话解释)
    • 类图、时序图(理论规范)
  • 该模式的代码示例:熟悉该模式的代码长什么样子
  • 该模式的优缺点:模式不是万金油,不可以滥用模式
  • 该模式的实际使用案例:了解它在哪些重要的源码中被使用

该系列会逐步更新于我的博客和公众号(博客见文章底部),也希望各位观众老爷能够关注我的个人公众号:后端技术漫谈,不会错过精彩好看的文章。

系列文章回顾

  • 【设计模式自习室】开篇:为什么我们要用设计模式?
  • 【设计模式自习室】建造者模式
  • 【设计模式自习室】原型模式
  • 【设计模式自习室】透彻理解单例模式
  • 【设计模式自习室】理解工厂模式的三种形式

结构型——适配器模式

引子

当你从中国到国外旅游时,经常会购买一个电源适配器,也就是电源转接头,因为国外的插座经常无法适用于国内的插头,这个电源转接头就是一个适配器,也就是下图中的Adapter,它让你的插头能够正常的在国外使用。

客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。

定义

适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作

适配器模式有类的适配器模式和对象的适配器模式两种不同的形式。

1. 类适配器

创建新类,继承源类,同时还要实现新接口

代码语言:javascript复制
class  adapter extends oldClass implements newFunc{}
2. 对象适配器

创建新类的实例,其中包含旧的类,并实现新接口

代码语言:javascript复制
class adapter implements newFunc { private oldClass oldInstance ;}
  • 类适配器使用对象继承的方式,是静态的定义方式
  • 对象适配器使用对象组合的方式,是动态组合的方式。

类图

如果看不懂UML类图,可以先粗略浏览下该图,想深入了解的话,可以继续谷歌,深入学习:

适配器模式包含如下角色:

  • Target:目标抽象类:这就是所期待得到的新接口类。
  • Adapter:适配器类:适配器类是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。
  • Adaptee:适配者类:等待适配的旧类
  • Client:客户端调用者
1. 类适配器
2. 对象适配器

时序图

时序图(Sequence Diagram)是显示对象之间交互的图,这些对象是按时间顺序排列的。时序图中显示的是参与交互的对象及其对象之间消息交互的顺序。

我们可以大致浏览下时序图,如果感兴趣的小伙伴可以去深究一下:

代码示例/使用场景举例

这次我将代码示例和使用场景两个章节合起来讲,是因为有一个十分典型的Java 源码可以学习

1. Java容器中的Enumeration老接口和Iterator新接口

JDK1.1 之前提供的容器有Arrays,Vector,Stack,Hashtable,Properties,BitSet,其中定义了一种访问群集内各元素的标准方式,称为 Enumeration(列举器)接口。

JDK1.2 版本中引入了 Iterator接口,新版本的集合对(HashSet,HashMap,WeakHeahMap,ArrayList,TreeSet,TreeMap, LinkedList)是通过 Iterator 接口访问集合元素。

这样,如果将老版本的程序运行在新的 Java 编译器上就会出错。因为 List 接口中已经没有 elements(),而只有 iterator() 了。那么如何将老版本的程序运行在新的 Java 编译器上呢? 如果不加修改,是肯定不行的,但是修改要遵循“开-闭”原则我们可以用 Java 设计模式中的适配器模式解决这个问题。

代码语言:javascript复制
public class EnumIter implements Iterator {//重点1:配器实现目标接口

   private Enumeration e;//重点2:适配器持有被适配接口(对象)的引用

   public EnumIter(Enumeration e){
       this.e = e;
   }


   //将对目标对象的调用转发给真正的被适配的对象
   public boolean hasNext() {
       return e.hasMoreElements();
   }
   public Object next() {
       return e.nextElement();
   }
   public void remove() {

   }
}

NewEnumeration 是一个适配器类,通过它实现了从 Iterator 接口到 Enumeration 接口的适配,这样我们就可以使用老版本的代码来使用新的集合对象了。

2. JDBC也是一种适配器模式

Sun公司在1996年公开了Java语言的数据库连接工具JDBC,JDBC使得Java语言程序能够与数据库连接,并使用SQL语言来查询和操作数据。JDBC给出一个客户端通用的抽象接口,每一个具体数据库引擎(如SQL Server、Oracle、MySQL等)的JDBC驱动软件都是一个介于JDBC接口和数据库引擎接口之间的适配器软件。抽象的JDBC接口和各个数据库引擎API之间都需要相应的适配器软件,这就是为各个不同数据库引擎准备的驱动程序。

模式优缺点

优点
  • 将目标类和适配者类解耦,而无须修改原有代码。
  • 对于客户端类来说是透明的,而且提高了适配者的复用性。

类适配器模式还具有如下优点:

由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。

对象适配器模式还具有如下优点:

一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

缺点

过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。

因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

总结

建议尽量使用对象适配器的实现方式,多用合成/聚合、少用继承。当然,具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的。

参考

  • https://www.jianshu.com/p/93821721bf08
  • https://github.com/CyC2018/CS-Notes/blob/master/notes/设计模式 - 适配器.md
  • 《HEAD FIRST 设计模式》
  • 《剑指offer》

关注我

我是一名后端开发工程师。

主要关注后端开发,数据安全,爬虫,物联网,边缘计算等方向,欢迎交流。

各大平台都可以找到我

  • 微信公众号:后端技术漫谈
  • Github:@qqxx6661
  • CSDN:@Rude3Knife
  • 知乎:@Zhendong
  • 简书:@蛮三刀把刀
  • 掘金:@蛮三刀把刀

原创博客主要内容

  • Java面试知识点复习全手册
  • 设计模式/数据结构 自习室
  • Leetcode/剑指offer 算法题解析
  • SpringBoot/SpringCloud菜鸟入门实战系列
  • 爬虫相关技术文章
  • 后端开发相关技术文章
  • 逸闻趣事/好书分享/个人兴趣

个人公众号:后端技术漫谈

0 人点赞