一、适配器模式
适配器模式是一种可以将不具有某些接口的类或者具有某些接口但需要对其功能做一些修饰和扩展的类转为具有目标接口或功能的类的一种设计模式。
最常见的场景就是一个业务类A依赖具有B方法的对象。类C具有业务类A所需要依赖的功能,但由于类C没有B方法,类A就无法使用类C。
这种情况下,A如果要使用C的功能就需要一个适配器将 C 的 format()方法转成 formatData()方法。
二、适配器模式的实现方式
适配器模式的类图如下:
适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
1. 类适配器(继承)
可以看到,在适配之前 Adaptee 类是不满足 ITarget 接口的。适配了之后,Adaptor类就能够满足ITarget接口了。
2. 对象适配器(组合)
什么情况下使用继承方式的适配器,什么情况使用组合方式的适配器呢?
继承方式和组合方式的区别在于,组合方式的适配器必须重新实现原始类的所有方法,而继承方式则可以复用父类的部分代码,无需所有方法都重新实现(例如上述代码中的fc())。
因此判断使用继承还是组合的标准主要有两个,一个是 Adaptee 类的接口个数(即要适配的方法个数),另一个是 Adaptee 和 ITarget 的契合程度(两者之间的已适配的方法个数)。
如果 Adaptee 接口并不多,那两种实现方式都可以。
如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都相同,那我们推荐使用类适配器(继承),因为 Adaptor 作为子类复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量(编码的工作量)要少很多。
如果 Adaptee 接口很多,而且 Adaptee 和 ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。
三、适配器模式的使用场景
首先声明,适配器模式本质是一种“补偿模式”用来补救设计上的缺陷。如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。
适配器模式的应用场景是“接口不兼容”,而接口不兼容大致来说包含以下场景:
1、依赖的外部类在接口设计方面有缺陷
所谓的缺陷具体是指,如包含大量的静态方法(而目标接口规定的是对象方法),方法命名不规范、方法的参数过多、方法的性能有问题等。
2、多个类具有同一类型的功能,但是方法名各异,需要适配器将方法名进行统一
假设我们要对用户输入的文本内容做敏感词过滤,我们引入了多款第三方敏感词过滤系统,依次对用户输入的内容进行过滤,过滤掉尽可能多的敏感词。
但是,每个系统提供的过滤接口都是不同的。这就意味着我们没法复用一套逻辑来调用各个系统。这个时候,我们就可以使用适配器模式,将所有系统的接口适配为统一的接口定义,这样我们上层调用就可以通过适配器的接口用到不同的敏感词过滤类。
未使用适配器:
使用了适配器后:
3、将外部依赖从一个类替换为另一个类
当我们把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动。
最后需要提一点: 适配器模式是一个无法偷懒的模式,如果你有10个原始类要适配某个接口,且适配的逻辑各不相同,你不得不写10个适配器类(无论你使用继承还是组合方式的适配器)。
代理、桥接、装饰器、适配器,这 4 种模式的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。
尽管代码结构相似,但这 4 种设计模式要解决的问题、应用场景不同,这也是它们的主要区别。
代理模式:代理模式在不改变原始类接口的条件下,给原始类增加和原始类业务无关的功能,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。
桥接模式:桥接模式的目的是将抽象部分(框架部分)和具体实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。
装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。
适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。