设计模式之适配器模式
适配器模式(Adapter)是23种设计模式之一。DP中是这么定义外观模式的:
适配器模式将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能在一起工作的那些类可以在一起工作。
我们生活中就经常使用到适配器,适配器这个词最早应该是出现在电工学里。有些国家用110V电压,而我国使用的是220V电压,但我们的电器,例如手机、笔记本电脑、平板电脑等,是不能什么电压都能用的,于是我们就需要使用电源适配器,这样一来只要是电,不管多少伏都能适配成需要的电压。而适配器模式就是起到这种作用,将既有的,但是不能够直接使用的,也无法进行改造的,通过适配后,让它能够被使用。
在软件开发中,也就是系统的数据和行为都正确,但是接口不符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况,比如在需要对早期代码复用一些功能等应用上很有实际价值。
在GoF的设计模式中,对适配器模式讲了两种类型,类适配器模式和对象适配器模式,由于类适配器模式是通过多重继承对一个接口与另一个接口进行匹配,而Java语言不支持多继承,所以这里只介绍对象适配器模式。
适配器模式(Adapter)结构图:
使用简单的代码来实现这个模式的结构:
Target类:
package org.zero01.target;
/**
* 客户所期望的接口
*/
public class Target {
public void request() {
System.out.println("普通请求!");
}
}
Adaptee类:
package org.zero01.adapter;
/**
* 需要适配的类
*/
public class Adaptee {
public void specificRequest() {
System.out.println("特殊请求!");
}
}
Adapter类:
package org.zero01.adapter;
import org.zero01.target.Target;
/**
* 适配器类
*/
public class Adapter extends Target {
private Adaptee adaptee = new Adaptee();
// 把request方法的调用转换成specificRequest方法的调用
public void request() {
adaptee.specificRequest();
}
}
客户端:
package org.zero01.client;
import org.zero01.adapter.Adapter;
import org.zero01.target.Target;
public class Client {
public static void main(String[] args) {
Target target=new Adapter();
// 客户端调用target的request方法即可
target.request();
}
}
什么时候适合用适配器模式:
1.想使用一个已经存在的类,但是它的接口,也就是方法与你要求的不同时可以考虑使用适配器模式。也就是说两个类所做的事情相似或相同,但是具有不同的接口时可以使用适配器模式。使用了适配器模式后,类之间就会共享同一个接口,使得客户端代码只需要统一调用同一个接口即可,这样可以使客户端的代码更简单、直接、紧凑。
2.软件开发后期或维护期适合使用适配器模式,因为维护时会由于开发人员的不同,而造成功能类似,但接口不同的情况,此时就适合使用适配器模式。
3.设计一个系统时,如果使用了第三方的组件,而这个组件的接口与我们自己的系统接口不同,但是我们又没有必要为了迎合它而改动自己的接口,这种情况也可以考虑使用适配器模式来解决接口不同的问题。
什么时候不适合用适配器模式:
在设计初期时,就应该统一好接口的定义,尽量不要把接口设计成不同的,以及需要规范好类与方法的命名,所以在这种开发前期的情况下,如果出现接口不同,应该是将不同的接口重构成统一的接口,而不是考虑使用适配器模式。适配器模式应该用在双方都不太容易修改接口的时候再使用适配器模式进行适配。
所以我们开发时应该事先预防接口的不同的问题,这样不匹配的问题就不会发生。在有小的接口不统一而引起问题时,应该及时对接口进行重构,这样问题不至于扩大。只有碰到无法改变原有的设计和代码的情况时,才考虑适配。事后控制不如事中控制,事中控制不如事前控制。适配器模式虽好,但是如果无视它的应用场合而盲目使用,就是本末倒置了。
简单的适配器模式示例:
我这里之前写了一个数码产品充电的系统,但是我现在想要加入一个小灯泡,但是小灯泡与数码产品实现的方法不一样,但是它们同样需要使用电,所以这时候就可以使用适配器了。
结构图如下:
数码产品接口,定义一个充电方法:
package org.zero01.target;
public interface DigitalProducts {
public void charge();
}
手机、平板以及笔记本电脑都实现了这个接口:
public class Phone implements DigitalProducts{
public void charge() {
System.out.println("手机使用4v电压充电");
}
}
public class IPad implements DigitalProducts{
public void charge() {
System.out.println("平板使用6v电压充电");
}
}
public class MacBook implements DigitalProducts{
public void charge() {
System.out.println("笔记本电脑使用14v电压充电");
}
}
我现在要加入一个小灯泡,但是它们实现的方法不一样:
public class SmallBulb {
public void electrify() {
System.out.println("使用1V电压给小灯泡通电");
}
}
这时候就需要加上一个适配器类了:
/**
* 适配器类
*/
public class Adapter implements DigitalProducts {
private SmallBulb smallBulb = new SmallBulb();
public void charge() {
smallBulb.electrify();
}
}
这样在客户端上就可以使用统一的接口了:
package org.zero01.client;
public class Client {
public static void main(String[] args) {
DigitalProducts phone = new Phone();
DigitalProducts ipad = new IPad();
DigitalProducts macBook = new MacBook();
DigitalProducts samllBulb = new Adapter();
phone.charge();
ipad.charge();
macBook.charge();
samllBulb.charge();
}
}
运行结果:
手机使用4v电压充电
平板使用6v电压充电
笔记本电脑使用14v电压充电
使用1V电压给小灯泡通电
- objective-C中的Class(类类型),Selector(选择器SEL),函数指针(IMP)
- 数据中心进水了怎么办?数据中心如何防洪?
- 戴若犁:虚拟中的现实
- kvm虚拟化管理平台WebVirtMgr部署-完整记录(1)
- objective-C中如何判断一个类中有没有定义某个方法
- Steve Boswell:智能口罩让PM2.5滚蛋
- kvm虚拟化管理平台WebVirtMgr部署-完整记录(2)
- objective-C中的扩展方法与partial class
- 仿优酷Android客户端图片左右滑动(自动滑动)
- objective-C: NSString应该用initWithFormat? 还是 stringWithFormat?
- objective-C 的内存管理之-实例分析
- Tim Berners-Lee:网络的自由和开放
- android防止内存溢出浅析
- objective-C 的内存管理之-自动释放池(autorelease pool)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- via the 'serverTimezone' configuration property
- 急速 debug 实战一(浏览器-基础篇)
- MongoDB系列一: Replica Set 集群搭建实战
- 函数式编程看React Hooks(一)简单React Hooks实现
- 函数式编程看React Hooks(二)事件绑定副作用深度剖析
- Vue 开发必须知道的 36 个技巧【近1W字】
- 吃透 Vue 项目开发实践|16个方面深入前端工程化开发技巧《上》
- 【漫游Github】无编译/无服务器,实现浏览器的 CommonJS 模块化
- 《秋风日常第一期》白板协作工具 LeanBoard
- 《秋风日常第二期》一个快速找出待SEO图片的技巧
- 《模块化系列》snowpack,提高10倍打包速度。
- 《秋风日常第三期》11个前端开发者必备的网站
- 专为程序员定制的垃圾清理工具(Node Cli实现)
- CodePen vue SFC 、flutter 在线玩耍来袭
- [重拾CSS]一道面试题来看伪元素、包含块和高度坍塌