前面我们介绍了一种通过包装拓展功能的方法——装饰器模式,这里我们来了解另外一种使用包装思想的设计模式——Adapter Pattern适配器模式,其依然是结构型模式的一种。一般情况下,两个类如果接口不兼容是无法一起工作的。而如果通过Adapter Pattern适配器模式就可以很好的解决这个问题了
现实世界的指引
现实生活中大家经常会遇到这样的一个麻烦,好不容易买了一个喜欢的台灯,结果回来一看,台灯是二脚插头而书桌旁边的插座却是三脚的。显然不能靠暴力直接插进去啊,这怎么办?有经验的小伙伴知道有一种神器可以完美解决这个尴尬——二脚转三脚的插头转换器。将台灯的二脚插头插在这个转换器上,此时台灯的插头就变成三脚的了,现在我们就可以将其插到三脚插座中
这个生活中的小例子,就很好地反应了我们今天所要介绍的设计模式的内在精神。在软件开发中,我们经常会遇到两个类因为接口不一样而导致无法配合使用的场景。有人可能会说,那为啥不去修改其中的一个类来保证接口一致呢?首先,一般不推荐对已有的类进行修改;其次,很多时候你使用的是第三方库所提供的类,没有源码无法直接修改。这个时候就可以利用我们从生活中获得的指引来解决这个问题——Adapter Pattern适配器模式。在该设计模式中,一般有三个角色:目标角色、被适配角色及适配器。其基本思想就是通过适配器的包装使得被适配角色与目标角色的接口一致,实现最终的 目标角色 与 被适配角色 的配合工作。具体地,Adapter Pattern适配器模式有类适配器、对象适配器两种实现方式
实现
这里我们来结合代码实现来具体解释下该模式中的思想与实践。目标接口是client使用的接口,一般会要求被适配角色最终要满足该接口,这样才能实现配合工作。这里我们定义了一个三脚插头的接口类ThreePlug
1 | /** |
正常情况下,只要其它电器类实现了该接口,就可以通过use3Plug方法从三脚插座中取电工作了。比如下面的PC计算机类,其实现了该接口,即使用的是三脚插头
1 | /** |
现在,我们利用下面的代码进行测试看看
1 | /** |
显然我们的计算机是可以正常工作的
基于类适配器的实现
现在我们添置了一个新电器——DeskLamp台灯,其定义如下。由于其使用的不是三脚插头,显然无法和ThreePlug接口配合工作
1 | /** |
这个时候,就需要引入了我们的适配器角色了。通过它来将这里的DeskLamp台灯(即,被适配角色)适配ThreePlug接口。既然DeskLampAdapter台灯适配器需要满足适配ThreePlug接口,那么第一步自然是需要让其实现该接口;然后还需要让适配器将请求能够转发给真正的DeskLamp台灯,这里我们选择通过继承的方式实现。即所谓的类适配器
1 | /** |
现在,我们就可以让ThreePlug接口与DeskLamp共同配合工作了
1 | /** |
可以看到通过适配器让原本两个不兼容的类可以配合工作了
基于对象适配器的实现
通过继承实现的类适配器,其缺点也是显而易见的。首先,由于Java的单继承特性,所以其只能应用于适配目标为接口的场景;其次继承这一静态的方式会导致耦合性高、灵活度低。为此一般情况较为常见的是对象适配器,其是通过内部持有被适配对象的引用来完成请求的转发。显然通过这一动态的方式使之具备了低耦合、高灵活的优点
这里,我们继续添加一个需要被适配的电器——Fan电扇
1 | /** |
然后实现一个电扇的对象适配器
1 | /** |
现在,我们就来看看Fan如何与ThreePlug配合工作
1 | /** |
可以看到测试结果符合预期
Note
文章的最后还是需要提醒下,对于Adapter Pattern适配器模式而言。其更多是对于系统后期的维护拓展时的一种补救措施。不应该在系统设计之初就被滥用,此时如果出现接口不统一问题,应该首先考虑修改接口的设计。而且即使是在后期拓展维护阶段,过多的使用适配器也会导致系统整体非常凌乱、不利于宏观的把握,此时就应该考虑重构
参考文献
- Head First 设计模式 弗里曼著