适配器模式的定义
将一个类的接口,转换成客户所期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
适配器模式.png这里有四个名词,需要理解一下
Client: 客户端,调用自己需要领域的接口Target
Target: 定义客户端需要的跟特定领域相关的接口
Adaptee: 已经存在的接口,但是与客户端所需领域的接口要求不一致,需要被适配(例如是方法名字不一致)
Adapter: 适配器,把Adaptee适配成Client需要的Target
Example1
Adaptee.java
package com.lhsjohn.adapter.example1;
/**
* 已经存在的接口,这个接口需要被适配
* @author lihuashuo
*
*/
public class Adaptee {
/**
* 示意方法,原本已经存在,已经实现了的方法
*/
public void specificRequest() {
//具体的功能处理
}
}
Target.java
package com.lhsjohn.adapter.example1;
/**
* 定义客户端使用的接口,与特定领域相关
* @author lihuashuo
*
*/
public interface Target {
/**
* 示意方法,客户端请求处理的方法
*/
public void request();
}
Adapter.java
package com.lhsjohn.adapter.example1;
/*
* 适配器
*/
public class Adapter implements Target{
//持有需要被适配的接口对象
private Adaptee adaptee;
/**
* 构造方法,传入需要被适配的对象
* @param adaptee
*/
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
//可能转调已经实现了的方法,进行适配
adaptee.specificRequest();
}
}
Client.java
package com.lhsjohn.adapter.example1;
/**
* 使用适配器的客户端
* @author lihuashuo
*
*/
public class Client {
public static void main(String[] args) {
//创建需要被适配的对象
Adaptee adaptee=new Adaptee();
//创建客户端需要调用的接口对象。
Target target=new Adapter(adaptee);
//请求处理
target.request();
}
}
下面是对该模式的一个扩展
有这样一个应用场景:
同时支持数据库和文件的日志管理
1:日志管理第一版:
在第一版的时候,用户要求日志以文件的形式记录
2:日志管理第二版:
要采用数据可以来管理日志,并且已有新的基于数据库的实现
3:现在想在第二版的实现里面,能够同时兼容第一版的功能,那么就应该有一个类来实现第二版的接口,然后在这个类里面去调用已有的功能实现,这个类就是适配器
Example2
LogModel.java
package com.lhsjohn.adapter.example2;
import java.io.Serializable;
/**
* 日志数据对象
* @author lihuashuo
*
*/
public class LogModel implements Serializable {
//日志编号
private String logId;
//操作人员
private String operaterUser;
//操作时间,以yyyy-MM-dd HH:mm:ss的格式记录
private String operateTime;
//日志内容
private String logContent;
public String getLogId() {
return logId;
}
public void setLogId(String logId) {
this.logId = logId;
}
public String getOperaterUser() {
return operaterUser;
}
public void setOperaterUser(String operaterUser) {
this.operaterUser = operaterUser;
}
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;
}
@Override
public String toString() {
return "LogModel [logId=" + logId + ", operaterUser=" + operaterUser + ", operateTime=" + operateTime
+ ", logContent=" + logContent + "]";
}
}
LogFileOperateApi.java
package com.lhsjohn.adapter.example2;
import java.util.List;
/**
* 日志文件操作接口
* @author lihuashuo
*
*/
public interface LogFileOperateApi {
/**
* 读取文件,从文件里面获取存储的日志列表
* @return 存储的日志操作对象
*/
public List<LogModel> readLogFile();
/**
* 写日志文件,把日志列表写到日志文件中去
* @param list 要写到日志文件的日志列表
*/
public void writeLogFile(List<LogModel> list);
}
LogFileOperate.java
package com.lhsjohn.adapter.example2;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
/**
* 实现对日志文件的操作
* @author lihuashuo
*
*/
public class LogFileOperate implements LogFileOperateApi {
//日志文件的路径和文件名称,默认是当前classpath下的AdapterLog.log
private String logFilePathName="AdapterLog.log";
/**
* 构造方法,传入文件的路径和名称
* @param logFilePathName 文件的路径和名称
*/
public LogFileOperate(String logFilePathName) {
//先判断是否传入了文件的路径和名称
//如果是的话,就重新设置操作的日志文件的路径和名称
if(logFilePathName!=null&& logFilePathName.trim().length()>0) {
this.logFilePathName = logFilePathName;
}
}
@Override
public List<LogModel> readLogFile() {
List<LogModel> list=null;
ObjectInputStream oin=null;
try {
File f=new File(logFilePathName);
if(f.exists()) {
oin=new ObjectInputStream(new BufferedInputStream(new FileInputStream(f)));
list=(List<LogModel>) oin.readObject();
}
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(oin!=null) {
oin.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return list;
}
@Override
public void writeLogFile(List<LogModel> list) {
File f=new File(logFilePathName);
ObjectOutputStream oout=null;
try {
oout=new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
oout.writeObject(list);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(oout!=null) {
oout.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
LogDbOperator.java
package com.lhsjohn.adapter.example2;
import java.util.List;
/**
* 定义操作日志的应用接口
* 简单定义了增删改查的方法
* @author lihuashuo
*
*/
public interface LogDbOperaterApi {
/**
* 新增日志
* @param lm 需要新增的日志对象
*/
public void createLog(LogModel lm);
/**
* 删除日志
* @param lm 需要修改的日志对象
*/
public void updateLog(LogModel lm);
/**
* 删除日志
* @param lm 需要删除的日志对象
*/
public void removeLog(LogModel lm) ;
/**
* 获取所有的日志
* @return 所有的日志对象
*/
public List<LogModel> getAllLog();
}
Adapter.java
package com.lhsjohn.adapter.example2;
import java.util.List;
/**
* 适配器对象,把记录日志到文件的功能适配成第二版需要的增删改查的功能
* @author lihuashuo
*
*/
public class Adapter implements LogDbOperaterApi {
/**
* 持有需要被适配的对象
*/
private LogFileOperateApi adaptee;
/**
* 构造方法,传入需要被适配的对象
* @param adaptee 需要被是配的对象
*/
public Adapter(LogFileOperateApi adaptee) {
this.adaptee = adaptee;
}
@Override
public void createLog(LogModel lm) {
//1:先读取文件的内容
List<LogModel> list = adaptee.readLogFile();
//2:加入新的日志对象
list.add(lm);
//3.重新写入文件
adaptee.writeLogFile(list);
}
@Override
public void updateLog(LogModel lm) {
//1:先读取文件的内容
List<LogModel> list = adaptee.readLogFile();
//2:修改相应的日志对象
for(int i=0;i<list.size();i++) {
if(list.get(i).getLogId().equals(lm.getLogId())) {
list.set(i, lm);
break;
}
}
}
@Override
public void removeLog(LogModel lm) {
//1:先读取文件的内容
List<LogModel> list = adaptee.readLogFile();
//2:删除对应的日志对象
list.remove(lm);
//3:重新写入文件
adaptee.writeLogFile(list);
}
@Override
public List<LogModel> getAllLog() {
return adaptee.readLogFile();
}
}
Client.java
package com.lhsjohn.adapter.example2;
import java.util.ArrayList;
import java.util.List;
public class Client {
public static void main(String[] args) {
//准备日志内容,也就是测试的数据
LogModel lm1=new LogModel();
lm1.setLogId("001");
lm1.setOperaterUser("lhsjohn");
lm1.setOperateTime("2018-08-24 00:30:21");
lm1.setLogContent("这是一个测试");
List<LogModel> list=new ArrayList<LogModel>();
list.add(lm1);
//创建操作日志文件的对象
LogFileOperateApi logFileApi=new LogFileOperate("");
//创建新版的操作日志的接口对象
LogDbOperaterApi api=new Adapter(logFileApi);
//保存日志文件
api.createLog(lm1);
//未使用适配器之前的:api.writeLogFile(list);
//读取日志文件的内容
//未使用适配器的:List<LogModel> list2 = api.readLogFile();
//使用适配器的
List<LogModel> list2 = api.getAllLog();
System.out.println("readLog= "+list2);
}
}
再此基础上,再重新认识一下适配器模式
1:适配器模式的功能
主要是进行转换匹配,目的是复用已有的功能,而不是实现新的接口。
适配器里面也可以实现功能,称这种为智能适配器。
2:Adaptee和Target的关系
适配器模式中被适配的接口Adaptee和适配成为的接口Target是没有关联的。
3:对象组合
适配器的实现方式其实是依靠的对象组合的方式
其实也可以用对象继承的方式,但是不推荐,因为Java是单继承的,只能继承一个类,如果要使用多个类的特性,就无法用继承完成
4.适配器模式调用的时序图
1:调用客户端需要的功能-->1:1这个功能会由适配器来实现-->1:1:1适配器会转调被适配对象的功能
适配器的实现
1.适配器的常见实现
适配器通常是一个类,一般会让适配器去实现Target接口,然后在适配器的具体实现里面调用Adaptee.
2:智能适配器
可以在适配器的实现里面,加入新的功能实现,这种适配器被称为智能适配器
- 适配多个Adaptee
- 适配器Adapter实现的复杂程度
适配器Adapter实现的复杂程度取决于Adaptee和Target的相似程度
5.缺省适配
为一个接口提供缺省实现:继承缺省适配器
双向适配器.pngexample3
LogModel.java
package com.lhsjohn.adapter.example3;
import java.io.Serializable;
/**
* 日志数据对象
* @author lihuashuo
*
*/
public class LogModel implements Serializable {
//日志编号
private String logId;
//操作人员
private String operaterUser;
//操作时间,以yyyy-MM-dd HH:mm:ss的格式记录
private String operateTime;
//日志内容
private String logContent;
public String getLogId() {
return logId;
}
public void setLogId(String logId) {
this.logId = logId;
}
public String getOperaterUser() {
return operaterUser;
}
public void setOperaterUser(String operaterUser) {
this.operaterUser = operaterUser;
}
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;
}
@Override
public String toString() {
return "LogModel [logId=" + logId + ", operaterUser=" + operaterUser + ", operateTime=" + operateTime
+ ", logContent=" + logContent + "]";
}
}
LogFileOperateApi.java
package com.lhsjohn.adapter.example3;
import java.util.List;
/**
* 日志文件操作接口
* @author lihuashuo
*
*/
public interface LogFileOperateApi {
/**
* 读取文件,从文件里面获取存储的日志列表
* @return 存储的日志操作对象
*/
public List<LogModel> readLogFile();
/**
* 写日志文件,把日志列表写到日志文件中去
* @param list 要写到日志文件的日志列表
*/
public void writeLogFile(List<LogModel> list);
}
LogFileOperate.java
package com.lhsjohn.adapter.example3;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;
/**
* 实现对日志文件的操作
* @author lihuashuo
*
*/
public class LogFileOperate implements LogFileOperateApi {
//日志文件的路径和文件名称,默认是当前classpath下的AdapterLog.log
private String logFilePathName="AdapterLog.log";
/**
* 构造方法,传入文件的路径和名称
* @param logFilePathName 文件的路径和名称
*/
public LogFileOperate(String logFilePathName) {
//先判断是否传入了文件的路径和名称
//如果是的话,就重新设置操作的日志文件的路径和名称
if(logFilePathName!=null&& logFilePathName.trim().length()>0) {
this.logFilePathName = logFilePathName;
}
}
@Override
public List<LogModel> readLogFile() {
List<LogModel> list=null;
ObjectInputStream oin=null;
try {
File f=new File(logFilePathName);
if(f.exists()) {
oin=new ObjectInputStream(new BufferedInputStream(new FileInputStream(f)));
list=(List<LogModel>) oin.readObject();
}
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(oin!=null) {
oin.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return list;
}
@Override
public void writeLogFile(List<LogModel> list) {
File f=new File(logFilePathName);
ObjectOutputStream oout=null;
try {
oout=new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
oout.writeObject(list);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(oout!=null) {
oout.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
LogDbOperaterApi.java
package com.lhsjohn.adapter.example3;
import java.util.List;
/**
* 定义操作日志的应用接口
* 简单定义了增删改查的方法
* @author lihuashuo
*
*/
public interface LogDbOperaterApi {
/**
* 新增日志
* @param lm 需要新增的日志对象
*/
public void createLog(LogModel lm);
/**
* 删除日志
* @param lm 需要修改的日志对象
*/
public void updateLog(LogModel lm);
/**
* 删除日志
* @param lm 需要删除的日志对象
*/
public void removeLog(LogModel lm) ;
/**
* 获取所有的日志
* @return 所有的日志对象
*/
public List<LogModel> getAllLog();
/**
* 删除所有的日志
*/
public void removeAll();
}
LogDbOperater.java
package com.lhsjohn.adapter.example3;
import java.util.List;
import com.lhsjohn.adapter.example3.LogDbOperaterApi;
import com.lhsjohn.adapter.example3.LogModel;
/**
* DB存储日志的实现,这里是模拟
* @author lihuashuo
*
*/
public class LogDbOperate implements LogDbOperaterApi {
@Override
public void createLog(LogModel lm) {
System.out.println("now in LogDbOperate createLog,lm="+lm);
}
@Override
public void updateLog(LogModel lm) {
System.out.println("now in LogDbOperate updateLog,lm="+lm);
}
@Override
public void removeLog(LogModel lm) {
System.out.println("now in LogDbOperate removeLog,lm="+lm);
}
@Override
public List<LogModel> getAllLog() {
System.out.println("now in LogDbOperate getAllLog");
return null;
}
@Override
public void removeAll() {
System.out.println("now in LogDbOperate removeAll");
}
}
TwoDirectAdapter
package com.lhsjohn.adapter.example3;
import java.util.List;
import com.lhsjohn.adapter.example3.LogModel;
/**
* 双向适配器对象
*
* @author lihuashuo
*
*/
public class TwoDirectAdapter implements LogDbOperaterApi, LogFileOperateApi {
/**
* 持有需要被适配的文件存储日志的对象
*/
private LogFileOperateApi fileLog;
/**
* 持有需要被适配的DB存储日志的接口对象
*/
private LogDbOperaterApi dbLog;
/**
* 构造方法,传入需要被适配的对象
*
* @param fileLog
* 需要被适配的文件存储日志的对象
* @param dbLog
* 需要被是配的DB存储日志的对象
*/
public TwoDirectAdapter(LogFileOperateApi fileLog, LogDbOperaterApi dbLog) {
this.fileLog = fileLog;
this.dbLog = dbLog;
}
/*---------以下是把稳健操作的方式适配成DB实现方式的接口-------------*/
@Override
public void createLog(LogModel lm) {
// 1:先读取文件的内容
List<LogModel> list = fileLog.readLogFile();
// 2:加入新的日志对象
list.add(lm);
// 3.重新写入文件
fileLog.writeLogFile(list);
}
@Override
public void updateLog(LogModel lm) {
// 1:先读取文件的内容
List<LogModel> list = fileLog.readLogFile();
// 2:修改相应的日志对象
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getLogId().equals(lm.getLogId())) {
list.set(i, lm);
break;
}
}
}
@Override
public void removeLog(LogModel lm) {
// 1:先读取文件的内容
List<LogModel> list = fileLog.readLogFile();
// 2:删除对应的日志对象
list.remove(lm);
// 3:重新写入文件
fileLog.writeLogFile(list);
}
@Override
public List<LogModel> getAllLog() {
return fileLog.readLogFile();
}
/* ----以下是把DB操作的方式适配成为文件实现的方式的接口*/
@Override
public List<LogModel> readLogFile() {
return dbLog.getAllLog();
}
@Override
public void writeLogFile(List<LogModel> list) {
//1:最简单的实现思路,先删除数据库中的数据
dbLog.removeLog(null);
//2:然后循环把现在的数据加入到数据库中
for (LogModel lm : list) {
dbLog.createLog(lm);
}
}
@Override
public void removeAll() {
System.err.println("now in two driect remove All");
}
}
Client.java
package com.lhsjohn.adapter.example3;
import java.util.ArrayList;
import java.util.List;
public class Client {
public static void main(String[] args) {
//准备日志内容,也就是测试的数据
LogModel lm1=new LogModel();
lm1.setLogId("001");
lm1.setOperaterUser("lhsjohn");
lm1.setOperateTime("2018-08-24 00:30:21");
lm1.setLogContent("这是一个测试");
List<LogModel> list=new ArrayList<LogModel>();
list.add(lm1);
//创建操作日志文件的对象
LogFileOperateApi fileLogApi=new LogFileOperate("");
LogDbOperaterApi dbLogApi=new LogDbOperate();
//创建经过双向适配后的操作接口日志的接口对象
LogFileOperateApi fileLogApi2=new TwoDirectAdapter(fileLogApi, dbLogApi);
LogDbOperaterApi dbLogApi2=new TwoDirectAdapter(fileLogApi, dbLogApi);
//先测试从文件操作适配器第二版,虽然调用第二版的接口,实际上是文件操作在实现
dbLogApi2.createLog(lm1);
List<LogModel> allLog = dbLogApi2.getAllLog();
System.out.println("allLog="+allLog);
//再测试从数据库存储适配成第一版的接口,调用第一版的接口,实际上是数据库相关在实现
System.out.println("------------------------File Api");
fileLogApi2.writeLogFile(list);
fileLogApi2.readLogFile();
}
}
控制台输出
allLog=[LogModel [logId=001, operaterUser=lhsjohn, operateTime=2018-08-24 00:30:21, logContent=这是一个测试], LogModel [logId=001, operaterUser=lhsjohn, operateTime=2018-08-24 00:30:21, logContent=这是一个测试]]
------------------------File Api
now in LogDbOperate removeLog,lm=null
now in LogDbOperate createLog,lm=LogModel [logId=001, operaterUser=lhsjohn, operateTime=2018-08-24 00:30:21, logContent=这是一个测试]
now in LogDbOperate getAllLog
然后再深入理解一下适配器模式
对象适配器和类适配器
1:对象适配器的实现:依赖于对象组合,前面示例都是对象适配器的实现方式
2:类适配器的实现:采用多重继承对一个接口与另一个接口匹配。由于java不支持多重继承,所以到目前没涉及到。
但是java可以类似去实现类适配器,采用继承加实现接口的方式
3.png最后来一个总结吧
适配器模式的本质
转换匹配,复用功能
何时使用适配器模式
1:如果你想使用一个已经存在的类,但它的接口不符合你的需求,这种情况可以使用适配器模式,来把已有的实现转换成你需要的接口。
2:如果你想创建一个可以复用的类,这个类可能和一些不兼容的类一起工作,这种情况下可以使用适配器模式,到时候需要什么就适配什么
3:如果你想使用一些已经存在的子类,但是不可能对每一个子类都进行适配,这种情况下就可以选用对象适配器,直接适配这些子类的父类就可以了。
作者:lhsjohn
网友评论