首先我们举个例子:我们笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?答案是引入一个电源适配器(AC Adapter),俗称充电器或变压器,有了这个电源适配器,生活用电和笔记本电脑即可兼容,如图1所示:
在软件开发中,有时也存在类似这种不兼容的情况,我们也可以像引入一个电源适配器一样引入一个称之为适配器的角色来协调这些存在不兼容的结构,这种设计方案即为适配器模式。
1.适配器模式概述
与电源适配器相似,在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。
适配器模式可以将一个类的接口和另一个类的接口匹配起来,而无须修改原来的适配者接口和抽象目标类接口。适配器模式定义如下:
|
适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。 |
【注:在适配器模式定义中所提及的接口是指广义的接口,它可以表示一个方法或者方法的集合。】
在适配器模式中,我们通过增加一个新的适配器类来解决接口不兼容的问题,使得原本没有任何关系的类可以协同工作。根据适配器类与适配者类的关系不同,适配器模式可分为对象适配器和类适配器两种,在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系。在实际开发中,对象适配器的使用频率更高,对象适配器模式结构如图2所示:
在对象适配器模式结构图中包含如下几个角色:
Client(客户端类):客户端,调用自己需要的领域接口Target
Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
● Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。即把Adaptee适配成为Client需要的Target。
根据对象适配器模式结构图,在对象适配器中,客户端需要调用被适配者类Adaptee的specificRequest()方法,但是客户端只能调用Target类request()方法,无法直接访问Adaptee的specificRequest()方法。为了使客户端能够使用Adaptee的specificRequest()方法,需要提供一个包装类Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者的specificRequest()方法(即将Adaptee类的specificRequest()方法适配成Target类request()的被客户端调用的方法)。因为适配器类与适配者类是关联关系(也可称之为委派关系),所以这种适配器模式称为对象适配器模式。典型的对象适配器代码如下所示:
//已经存在的接口,这个接口需要被适配
public class Adaptee {
//示意方法,原本已经存在,已经实现的业务方法
public void specificRequest(){
System.out.println("我是实现的业务Adaptee类的业务方法...");
}
}
//定义客户端使用的接口,与特定领域相关
public interface Target {
//示意方法,客户端请求处理的方法
public void request();
}
//适配器
public class Adapter implements Target {
//持有需要被适配的接口对象
private Adaptee adaptee;
//构造方法,传入需要被适配的对象
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
//适配器
public class Adapter implements Target {
//持有需要被适配的接口对象
private Adaptee adaptee;
//构造方法,传入需要被适配的对象
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
输出结果:
我是实现的业务Adaptee类的业务方法...
2.对象适配器
(第一版)假设在我们的应用系统中,日志管理用户要求是以文件的形式记录的。
案例代码(不需要用模式的解决方案):
//日志数据对象
public class LogModel {
//日志编号
private String logId;
//操作人员
private String operateUser;
//操作时间
private String operateTime;
//日志内容
private String logContent;
public String getLogId() {
return logId;
}
public void setLogId(String logId) {
this.logId = logId;
}
public String getOperateUser() {
return operateUser;
}
public void setOperateUser(String operateUser) {
this.operateUser = operateUser;
}
public String getOperateTime() {
return operateTime;
}
public void setOperateTime(String operateTime) {
this.operateTime = operateTime;
}
public String getLogContent() {
return logContent;
}
public void setLogContent(String logContent) {
this.logContent = logContent;
}
}
//日志文件操作接口
public interface LogFileoperateApi {
//读取日志文件,从文件里面获取存储的日志信息,返回的是日志列表对象
public List<LogModel> readLogFile();
//写日志文件,把日志信息写到日志文件中去
public void writeLogFile(List<LogModel> list);
}
//日志文件操作接口的实现类
public class LogFileOperate implements LogFileoperateApi{
@Override
public List<LogModel> readLogFile() {
System.out.println("日志文件:系统已经从日志文件中,获取日志列表信息...");
return null;
}
@Override
public void writeLogFile(List<LogModel> list) {
System.out.println("日志文件:系统已经将日志信息,写入到日志文件中去...");
}
}
//客户端
public class Client {
public static void main(String[] args) {
LogFileoperateApi api = new LogFileOperate();
//将日志信息写入到日志文件
api.writeLogFile(new ArrayList<LogModel>());
//将日志信息从日志文件中读取出来
List<LogModel> readLog = api.readLogFile();
}
}
输出结果:
日志文件:系统已经将日志信息,写入到日志文件中去...
日志文件:系统已经从日志文件中,获取日志列表信息...
(第二版)假设在我们的应用系统中,突然间有一天用户要求日志信息是以数据库增删改查的形式记录。
案例代码(不需要用模式的解决方案):
//日志数据对象
public class LogModel {
//日志编号
private String logId;
//操作人员
private String operateUser;
//操作时间
private String operateTime;
//日志内容
private String logContent;
public String getLogId() {
return logId;
}
public void setLogId(String logId) {
this.logId = logId;
}
public String getOperateUser() {
return operateUser;
}
public void setOperateUser(String operateUser) {
this.operateUser = operateUser;
}
public String getOperateTime() {
return operateTime;
}
public void setOperateTime(String operateTime) {
this.operateTime = operateTime;
}
public String getLogContent() {
return logContent;
}
public void setLogContent(String logContent) {
this.logContent = logContent;
}
}
//定义操作日志的应用接口,为了示例简单,我们只定义了简单的增删改查的方法
public interface LogDbOperateApi {
//新增日志
public void createLog(LogModel logModel);
//修改日志
public void updateLog(LogModel logModel);
//删除日志
public void removeLog(LogModel logModel);
//获取所有的日志信息
public void getAllLog(LogModel logModel);
}
//实现对日志信息的入库操作
public class LogDbOperate implements LogDbOperateApi{
@Override
public void createLog(LogModel logModel) {
System.out.println("数据库:正在新增日志信息...");
}
@Override
public void updateLog(LogModel logModel) {
System.out.println("数据库:正在修改日志信息...");
}
@Override
public void removeLog(LogModel logModel) {
System.out.println("数据库:正在删除日志信息...");
}
@Override
public void getAllLog(LogModel logModel) {
System.out.println("数据库:正在获取日志信息...");
}
}
//客户端
public class Client {
public static void main(String[] args) {
LogDbOperateApi api = new LogDbOperate();
api.createLog(new LogModel());//日志新增入库
api.updateLog(new LogModel());//日志修改
api.removeLog(new LogModel());//日志删除
api.getAllLog(new LogModel());//日志获取
}
}
输出结果:
数据库:正在新增日志信息...
数据库:正在修改日志信息...
数据库:正在删除日志信息...
数据库:正在获取日志信息...
(第三版)假设在我们的应用系统中,突然间有一天项目经理发现第一版(文件记录的日志信息)记录的日志信息比较稳定但是操作性比较差,而第二版(数据库记录的日志信息可增删改查)日志记录的稳定性比较差但是操作性比较高,项目经理提出能不能以第一版的形式记录第二版的形式操作,提升用户的可操作性和系统的稳定性。
我们在第一版和第二版功能编码都完成的情况下,开发人员该怎么修改比较好?
首先我们看一下第一版和第二版放在一起的示意图,如下:
我们会发现第一版的日志管理(文件操作)接口不能直接兼容到第二版(数据库的增删改查)的实现,我们需要将第一版实现类适配成第二版接口。这时我们就需要一个适配器帮我们解决该问题。
使用的适配器模式解决方案:
结构图:
从结构图可以看出Client是客户端,LogDbOperateApi充当抽象目标,LogFileoperate充当适配者,Adapter充当适配器。
案例代码:
//日志数据对象
public class LogModel {
//日志编号
private String logId;
//操作人员
private String operateUser;
//操作时间
private String operateTime;
//日志内容
private String logContent;
public String getLogId() {
return logId;
}
public void setLogId(String logId) {
this.logId = logId;
}
public String getOperateUser() {
return operateUser;
}
public void setOperateUser(String operateUser) {
this.operateUser = operateUser;
}
public String getOperateTime() {
return operateTime;
}
public void setOperateTime(String operateTime) {
this.operateTime = operateTime;
}
public String getLogContent() {
return logContent;
}
public void setLogContent(String logContent) {
this.logContent = logContent;
}
}
//第一版日志文件操作接口
public interface LogFileoperateApi {
//读取日志文件,从文件里面获取存储的日志信息,返回的是日志列表对象
public List<LogModel> readLogFile();
//写日志文件,把日志信息写到日志文件中去
public void writeLogFile(List<LogModel> list);
}
//第一版日志文件操作接口实现类
public class LogFileOperate implements LogFileoperateApi{
@Override
public List<LogModel> readLogFile() {
System.out.println("日志文件:系统已经从日志文件中,获取日志列表信息...");
return new ArrayList<LogModel>();
}
@Override
public void writeLogFile(List<LogModel> list) {
System.out.println("日志文件:系统已经将日志信息,写入到日志文件中去...");
}
}
//第二版的接口。定义操作日志的应用接口,为了示例简单,我们只定义了简单的增删改查的方法
public interface LogDbOperateApi {
//新增日志
public void createLog(LogModel logModel);
//修改日志
public void updateLog(LogModel logModel);
//删除日志
public void removeLog(LogModel logModel);
//获取所有的日志信息
public void getAllLog(LogModel logModel);
}
//适配器对象,把记录日志到文件的功能适配成第二版需要增删改查的功能
public class Adapter implements LogDbOperateApi{
//持有需要被适配的接口对象
private LogFileoperateApi adaptee;
//构造方法,传入需要被适配的对象
public Adapter(LogFileoperateApi adaptee) {
this.adaptee = adaptee;
}
@Override
public void createLog(LogModel logModel) {
//1.先从日志文件读取日志的内容
List<LogModel> list = adaptee.readLogFile();
//2.再添加对应的日志文件信息
list.add(logModel);
//3.最后将新的日志信息写入到日志文件
adaptee.writeLogFile(list);
}
@Override
public void updateLog(LogModel logModel) {
//1.先从日志文件读取日志的内容
List<LogModel> list = adaptee.readLogFile();
//2.再修改相应的日志对象信息
for(int i=0;i<list.size();i++){
if(list.get(i).getLogId().equals(logModel.getLogId())){
list.set(i, logModel);
break;
}
}
//3.最后将新的日志信息写入到日志文件
adaptee.writeLogFile(list);
}
@Override
public void removeLog(LogModel logModel) {
//1.先从日志文件读取日志的内容
List<LogModel> list = adaptee.readLogFile();
//2.再删除对应的日志文件信息
list.remove(logModel);
//3.最后将新的日志信息写入到日志文件
adaptee.writeLogFile(list);
}
@Override
public void getAllLog(LogModel logModel) {
//从日志文件读取日志的内容
List<LogModel> list = adaptee.readLogFile();
}
}
//客户端
public class Client {
public static void main(String[] args) {
//创建操作日志文件的对象
LogFileoperateApi logFileApi = new LogFileOperate();
//创建新版的操作日志接口对象
LogDbOperateApi dbApi = new Adapter(logFileApi);
dbApi.createLog(new LogModel());//保存日志信息
dbApi.getAllLog(new LogModel());//获取全部的日志信息
}
}
输出结果:
日志文件:系统已经从日志文件中,获取日志列表信息...
日志文件:系统已经将日志信息,写入到日志文件中去...
日志文件:系统已经从日志文件中,获取日志列表信息...
我们需要将第一版实现类适配成第二版接口。这时我们就需要一个适配器帮我们解决该问题。
从上面代码可看出,适配器类实际上是目标接口的类,因为持有待适配类的实例,所以可以在适配器类的目标接口被调用时,调用待适配对象的接口,而客户端并不需要知道二者接口的不同。通过这种方式,客户端可以使用统一的接口使用不同接口的类。我们也可以总结出:
1.客户端需要调用哪个接口,适配器(Adapter类)就需要实现(implements)哪个接口(适配哪个接口)。
2.适配器需要将哪个实现类适配成接口,我们需要在适配器(Adapter类)的构造方法将被适配的对象传入进来
3.客户端调用,被适配的实现类可以直接用接口new出对象,但是适配的接口(客户端需调用的接口)可以用适配器+被适配(new Adapter(logFileApi))的实现类创建。
//创建操作日志文件的对象 LogFileoperateApi logFileApi = new LogFileOperate(); //创建新版的操作日志接口对象 LogDbOperateApi dbApi = new Adapter(logFileApi); dbApi.createLog(new LogModel());//保存日志信息 dbApi.getAllLog(new LogModel());//获取全部的日志信息
关于日志管理的思路总结:
1.第一版,原有文件存取日志的方式,运行的很好
2.第二版,基于数据库实现的日志管理,操作性好,稳定性差。与第一版相比有了新的实现新的接口
3.现在想要在第二版的实现里面,能够同时兼容第一版的动能,那么就应有一个类来实现第二版的接口,然后在这个类里面去调用已有的功能实现,这个类就是适配器。
*************************************************************************************************************************************************
不好意思哈!能力有限外观模式目前只介绍这么多哈,如有问题可以相互交流哈!QQ:1814859090
*************************************************************************************************************************************************