将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由
于接口不兼容而不能一起工作的那些类可以一起工作。
- Client:客户端,调用自己需要的领域接口Target。
- Target:定义客户端需要的跟特定领域相关的接口。
- Adaptee:已经存在的接口,但与客户端要求的特定领域接口不一致,需要被适配。
- Adapter:适配器,把Adaptee适配成为Client需要的Target
package cn.javass.dp.adapter.example1;
/**
* 定义客户端使用的接口,与特定领域相关
*/
public interface Target {
/**
* 示意方法,客户端请求处理的方法
*/
public void request();
}
package cn.javass.dp.adapter.example1;
/**
* 已经存在的接口,这个接口需要被适配
*/
public class Adaptee {
/**
* 示意方法,原本已经存在,已经实现的方法
*/
public void specificRequest() {
//具体的功能处理
}
}
package cn.javass.dp.adapter.example1;
/**
* 适配器
*/
public class Adapter implements Target {
/**
* 持有需要被适配的接口对象
*/
private Adaptee adaptee;
/**
* 构造方法,传入需要被适配的对象
* @param adaptee 需要被适配的对象
*/
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
//可能转调已经实现了的方法,进行适配
adaptee.specificRequest();
}
}
package cn.javass.dp.adapter.example1;
/**
* 使用适配器的客户端
*/
public class Client {
public static void main(String[] args) {
//创建需被适配的对象
Adaptee adaptee = new Adaptee();
//创建客户端需要调用的接口对象
Target target = new Adapter(adaptee);
//请求处理
target.request();
}
}
如果把上面的问题抽象一下,用对象来描述,那就是:有一个电源类和旧
的硬盘类配合工作得很好,现在又有了一个新的硬盘类,现在想让新的硬盘类和
电源类也配合使用,但是发现它们的接口无法匹配,问题就产生了:如何让原有
的电源类的接口能够适应新的硬盘类的电源接口的需要呢?
解决方法是采用一个转接线类,转接线可以把电源的接口适配成为新的硬
盘所需要的接口,那么这个转接线类就类似本节的主角——适配器。
在第一版的时候,用户要求日志以文件的形式记录。
要采用数据库来管理日志
直接参看代码示例
package cn.javass.dp.adapter.example2;
import java.io.*;
/**
* 日志数据对象
*/
public class LogModel implements Serializable{
/**
* 日志编号
*/
private String logId;
/**
* 操作人员
*/
private String operateUser;
/**
* 操作时间,以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 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 String toString(){
return "logId="+logId+",operateUser="+operateUser+",operateTime="+operateTime+",logContent="+logContent;
}
}
package cn.javass.dp.adapter.example2;
import java.util.List;
/**
* 日志文件操作接口
*/
public interface LogFileOperateApi {
/**
* 读取日志文件,从文件里面获取存储的日志列表对象
* @return 存储的日志列表对象
*/
public List readLogFile();
/**
* 写日志文件,把日志列表写出到日志文件中去
* @param list 要写到日志文件的日志列表
*/
public void writeLogFile(List list);
}
package cn.javass.dp.adapter.example2;
import java.io.*;
import java.util.*;
/**
* 实现对日志文件的操作
*/
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;
}
}
public List readLogFile() {
List list = null;
ObjectInputStream oin = null;
try {
File f = new File(logFilePathName);
if(f.exists()){
/**
* 使用了装配模式
*/
oin = new ObjectInputStream(
new BufferedInputStream(new FileInputStream(f))
);
list = (List)oin.readObject();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(oin!=null){
oin.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return list;
}
public void writeLogFile(List list){
File f = new File(logFilePathName);
ObjectOutputStream oout = null;
try {
oout = new ObjectOutputStream(
new BufferedOutputStream(new FileOutputStream(f))
);
oout.writeObject(list);
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
oout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package cn.javass.dp.adapter.example2;
import java.util.ArrayList;
import java.util.List;
import cn.javass.dp.adapter.example3.LogDbOperateApi;
public class Client {
public static void main(String[] args) {
//准备日志内容,也就是测试的数据
LogModel lm1 = new LogModel();
lm1.setLogId("001");
lm1.setOperateUser("admin");
lm1.setOperateTime("2010-03-02 10:08:18");
lm1.setLogContent("这是一个测试");
List list = new ArrayList();
list.add(lm1);
//创建操作日志文件的对象
LogFileOperateApi api = new LogFileOperate("");
//保存日志文件
api.writeLogFile(list);
//读取日志文件的内容
List readLog = api.readLogFile();
System.out.println("readLog="+readLog);
LogDbOperateApi api2 = null;
}
}
package cn.javass.dp.adapter.example3;
import java.util.*;
/**
* 定义操作日志的应用接口,为了示例的简单,
* 只是简单的定义了增删改查的方法
*/
public interface LogDbOperateApi {
/**
* 新增日志
* @param lm 需要新增的日志对象
*/
public void createLog(LogModel lm);
/**
* 修改日志
* @param lm 需要修改的日志对象
*/
public void updateLog(LogModel lm);
/**
* 删除日志
* @param lm 需要删除的日志对象
*/
public void removeLog(LogModel lm);
/**
* 获取所有的日志
* @return 所有的日志对象
*/
public List getAllLog();
}
其他相同,添加了适配器对象
package cn.javass.dp.adapter.example3;
import java.util.List;
/**
* 适配器对象,把记录日志到文件的功能适配成第二版需要的增删改查的功能
*/
public class Adapter implements LogDbOperateApi{
//优先使用对象组合,而不是使用对象继承
/**
* 持有需要被适配的接口对象
*/
private LogFileOperateApi adaptee;
/**
* 构造方法,传入需要被适配的对象
* @param adaptee 需要被适配的对象
*/
public Adapter(LogFileOperateApi adaptee) {
this.adaptee = adaptee;
}
public void createLog(LogModel lm) {
//1:先读取文件的内容
List list = adaptee.readLogFile();
//2:加入新的日志对象
list.add(lm);
//3:重新写入文件
adaptee.writeLogFile(list);
}
public List getAllLog() {
return adaptee.readLogFile();
}
public void removeLog(LogModel lm) {
//1:先读取文件的内容
List list = adaptee.readLogFile();
//2:删除相应的日志对象
list.remove(lm);
//3:重新写入文件
adaptee.writeLogFile(list);
}
public void updateLog(LogModel lm) {
//1:先读取文件的内容
List list = adaptee.readLogFile();
//2:修改相应的日志对象
for(int i=0;iif(list.get(i).getLogId().equals(lm.getLogId())){
list.set(i, lm);
break;
}
}
//3:重新写入文件
adaptee.writeLogFile(list);
}
}
package cn.javass.dp.adapter.example3;
import java.util.*;
public class Client {
public static void main(String[] args) {
//准备日志内容,也就是测试的数据
LogModel lm1 = new LogModel();
lm1.setLogId("001");
lm1.setOperateUser("admin");
lm1.setOperateTime("2010-03-02 10:08:18");
lm1.setLogContent("这是一个测试");
List list = new ArrayList();
list.add(lm1);
//创建操作日志文件的对象
LogFileOperateApi logFileApi = new LogFileOperate("");
//创建新版的操作日志的接口对象
LogDbOperateApi api=new Adapter(logFileApi);
// LogDbOperateApi api = null;
/////////////////////////真正的操作
//保存日志文件
api.createLog(lm1);
//读取日志文件的内容
List allLog = api.getAllLog();
System.out.println("allLog44="+allLog);
}
}
来实现第二版的接口,然后在这个类里面去调用已有的功能实现,这个类就是适
配器
适配器模式的主要功能是进行转换匹配,目的是复用已有的功能,而不是
来实现新的接口。
适配器里面也可以实现功能,称这种适配器为智能适配器。
适配器模式中被适配的接口Adaptee和适配成为的接口Target是没有关联的
适配器的实现方式其实是依靠对象组合的方式。
适配器通常是一个类,一般会让适配器类去实现Target接口,然后在适配
器的具体实现里面调用Adaptee。
可以在适配器的实现里面,加入新功能的实现,这种适配器被称为智能适配器。
package cn.javass.dp.adapter.example3;
import java.util.List;
/**
* 适配器对象,把记录日志到文件的功能适配成第二版需要的增删改查的功能
*/
public class Adapter implements LogDbOperateApi{
//优先使用对象组合,而不是使用对象继承
/**
* 持有需要被适配的接口对象
*/
private LogFileOperateApi adaptee;
private TimeUtil adaptee2 = null;
/**
* 构造方法,传入需要被适配的对象
* @param adaptee 需要被适配的对象
*/
public Adapter(LogFileOperateApi adaptee) {
this.adaptee = adaptee;
}
public void createLog(LogModel lm) {
// 开始时间
Long t1=System.currentTimeMillis();
//1:先读取文件的内容
List list = adaptee.readLogFile();
//2:加入新的日志对象
list.add(lm);
//3:重新写入文件
adaptee.writeLogFile(list);
// 结束时间时间
Long t2=System.currentTimeMillis();
this.adaptee2.end();
System.out.println("操作文件时间:"+(t2-t1));
}
public List getAllLog() {
return adaptee.readLogFile();
}
public void removeLog(LogModel lm) {
//1:先读取文件的内容
List list = adaptee.readLogFile();
//2:删除相应的日志对象
list.remove(lm);
//3:重新写入文件
adaptee.writeLogFile(list);
}
public void updateLog(LogModel lm) {
//1:先读取文件的内容
List list = adaptee.readLogFile();
//2:修改相应的日志对象
for(int i=0;iif(list.get(i).getLogId().equals(lm.getLogId())){
list.set(i, lm);
break;
}
}
//3:重新写入文件
adaptee.writeLogFile(list);
}
}
其他不变,添加一个时间接口
package cn.javass.dp.adapter.example3;
public class TimeUtil {
private long a1;
private long a2;
public void begin(){
a1 = System.currentTimeMillis();
}
public void end(){
a2 = System.currentTimeMillis();
}
public void show(){
System.out.println("times11==="+(a2-a1));
}
}
package cn.javass.dp.adapter.example3;
import java.util.List;
/**
* 适配器对象,把记录日志到文件的功能适配成第二版需要的增删改查的功能
*/
public class Adapter implements LogDbOperateApi{
//优先使用对象组合,而不是使用对象继承
/**
* 持有需要被适配的接口对象
*/
private LogFileOperateApi adaptee;
private TimeUtil adaptee2 = null;
/**
* 构造方法,传入需要被适配的对象
* @param adaptee 需要被适配的对象
*/
public Adapter(LogFileOperateApi adaptee,TimeUtil times) {
this.adaptee = adaptee;
this.adaptee2 = times;
}
public void createLog(LogModel lm) {
this.adaptee2.begin();
//1:先读取文件的内容
List list = adaptee.readLogFile();
//2:加入新的日志对象
list.add(lm);
//3:重新写入文件
adaptee.writeLogFile(list);
this.adaptee2.end();
this.adaptee2.show();
}
public List getAllLog() {
return adaptee.readLogFile();
}
public void removeLog(LogModel lm) {
//1:先读取文件的内容
List list = adaptee.readLogFile();
//2:删除相应的日志对象
list.remove(lm);
//3:重新写入文件
adaptee.writeLogFile(list);
}
public void updateLog(LogModel lm) {
//1:先读取文件的内容
List list = adaptee.readLogFile();
//2:修改相应的日志对象
for(int i=0;iif(list.get(i).getLogId().equals(lm.getLogId())){
list.set(i, lm);
break;
}
}
//3:重新写入文件
adaptee.writeLogFile(list);
}
}
适配器Adapter实现的复杂程度,取决于Target和Adaptee的相似程度。
缺省适配的意思是:为一个接口提供缺省实现。
package cn.javass.dp.adapter.example3;
import java.util.List;
/**
* 默认缺省适配器为部分功能提供实现,
* 其他未实现功能交由子类适配器实现
*
*/
public class DefaultAdapter implements LogDbOperateApi{
@Override
public void createLog(LogModel lm) {
System.out.println("DB createLog===========");
}
@Override
public void updateLog(LogModel lm) {
System.out.println("DB updateLog===========");
}
@Override
public void removeLog(LogModel lm) {
}
@Override
public List getAllLog() {
return null;
}
}
package cn.javass.dp.adapter.example3;
import java.util.List;
/**
* 缺省适配器:
* 需要功能重新实现的可覆盖父适配器,
* 不覆盖则为使用父类适配器功能
*
*/
public class MyAdapter extends DefaultAdapter{
/**
* 持有需要被适配的接口对象
*/
private LogFileOperateApi adaptee;
private TimeUtil adaptee2 = null;
/**
* 构造方法,传入需要被适配的对象
* @param adaptee 需要被适配的对象
*/
public MyAdapter(LogFileOperateApi adaptee,TimeUtil times) {
this.adaptee = adaptee;
this.adaptee2 = times;
}
public List getAllLog() {
return adaptee.readLogFile();
}
public void removeLog(LogModel lm) {
//1:先读取文件的内容
List list = adaptee.readLogFile();
//2:删除相应的日志对象
list.remove(lm);
//3:重新写入文件
adaptee.writeLogFile(list);
}
}
适配器也可以实现双向的适配,前面我们讲的都是把Adaptee适配成为
Target,其实也可以把Target适配成为Adaptee,也就是说这个适配器可以同时
当作Target和Adaptee来使用。
package cn.javass.dp.adapter.example4;
import java.util.List;
/**
* 双向适配器对象
*/
public class TwoDirectAdapter implements LogDbOperateApi,LogFileOperateApi{
/**
* 持有需要被适配的文件存储日志的接口对象
*/
private LogFileOperateApi fileLog;
/**
* 持有需要被适配的DB存储日志的接口对象
*/
private LogDbOperateApi dbLog;
/**
* 构造方法,传入需要被适配的对象
* @param fileLog 需要被适配的文件存储日志的接口对象
* @param dbLog 需要被适配的DB存储日志的接口对象
*/
public TwoDirectAdapter(LogFileOperateApi fileLog,LogDbOperateApi dbLog) {
this.fileLog = fileLog;
this.dbLog = dbLog;
}
/*-----以下是把文件操作的方式适配成为DB实现方式的接口-----*/
public void createLog(LogModel lm) {
//1:先读取文件的内容
List list = fileLog.readLogFile();
//2:加入新的日志对象
list.add(lm);
//3:重新写入文件
fileLog.writeLogFile(list);
}
public List getAllLog() {
return fileLog.readLogFile();
}
public void removeLog(LogModel lm) {
//1:先读取文件的内容
List list = fileLog.readLogFile();
//2:删除相应的日志对象
list.remove(lm);
//3:重新写入文件
fileLog.writeLogFile(list);
}
public void updateLog(LogModel lm) {
//1:先读取文件的内容
List list = fileLog.readLogFile();
//2:修改相应的日志对象
for(int i=0;iif(list.get(i).getLogId().equals(lm.getLogId())){
list.set(i, lm);
break;
}
}
//3:重新写入文件
fileLog.writeLogFile(list);
}
public void removeAll(){
System.out.println("now in two direct remove all");
}
/*-----以下是把DB操作的方式适配成为文件实现方式的接口-----*/
public List readLogFile() {
return dbLog.getAllLog();
}
public void writeLogFile(List list) {
//1:最简单的实现思路,先删除数据库中的数据
dbLog.removeAll();
//2:然后循环把现在的数据加入到数据库中
for(LogModel lm : list){
dbLog.createLog(lm);
}
}
}
package cn.javass.dp.adapter.example4;
import java.util.*;
public class Client {
public static void main(String[] args) {
//准备日志内容,也就是测试的数据
LogModel lm1 = new LogModel();
lm1.setLogId("001");
lm1.setOperateUser("admin");
lm1.setOperateTime("2010-03-02 10:08:18");
lm1.setLogContent("这是一个测试");
List list = new ArrayList();
list.add(lm1);
//创建操作日志文件的对象
LogFileOperateApi fileLogApi = new LogFileOperate("");
LogDbOperateApi dbLogApi = new LogDbOperate();
//创建经过双向适配后的操作日志的接口对象
LogFileOperateApi fileLogApi2 = new TwoDirectAdapter(fileLogApi,dbLogApi);
LogDbOperateApi dbLogApi2 = new TwoDirectAdapter(fileLogApi,dbLogApi);
//先测试从文件操作适配到第二版,虽然调用的是第二版的接口,其实是文件操作在实现
dbLogApi2.createLog(lm1);
List allLog = dbLogApi2.getAllLog();
System.out.println("allLog555="+allLog);
//再测试从数据库存储适配成第一版的接口,也就是调用第一版的接口,其实是数据实现
System.out.println("--------------------------->File Api ");
fileLogApi2.writeLogFile(list);
fileLogApi2.readLogFile();
}
}
依赖于对象组合。就如同前面的实现示例,都是采用的对象
组合的方式,也就是前面讲述的都是对象适配器实现的方式。
采用多重继承对一个接口与另一个接口进行匹配。由于Java不
支持多重继承,所以到目前为止,还没有涉及到。
package cn.javass.dp.adapter.example5;
import java.util.List;
/**
* 类适配器对象
*/
public class ClassAdapter extends LogFileOperate implements LogDbOperateApi{
public ClassAdapter(String logFilePathName) {
super(logFilePathName);
}
public void createLog(LogModel lm) {
//1:先读取文件的内容
List list = this.readLogFile();
//2:加入新的日志对象
list.add(lm);
//3:重新写入文件
this.writeLogFile(list);
}
public List getAllLog() {
return this.readLogFile();
}
public void removeLog(LogModel lm) {
//1:先读取文件的内容
List list = this.readLogFile();
//2:删除相应的日志对象
list.remove(lm);
//3:重新写入文件
this.writeLogFile(list);
}
public void updateLog(LogModel lm) {
//1:先读取文件的内容
List list = this.readLogFile();
//2:修改相应的日志对象
for(int i=0;iif(list.get(i).getLogId().equals(lm.getLogId())){
list.set(i, lm);
break;
}
}
//3:重新写入文件
this.writeLogFile(list);
}
}
package cn.javass.dp.adapter.example5;
import java.util.*;
public class Client {
public static void main(String[] args) {
//准备日志内容,也就是测试的数据
LogModel lm1 = new LogModel();
lm1.setLogId("001");
lm1.setOperateUser("admin");
lm1.setOperateTime("2010-03-02 10:08:18");
lm1.setLogContent("这是一个测试");
List list = new ArrayList();
list.add(lm1);
//创建新版的操作日志的接口对象
LogDbOperateApi api = new ClassAdapter("");
//保存日志文件
api.createLog(lm1);
//读取日志文件的内容
List allLog = api.getAllLog();
System.out.println("allLog="+allLog);
}
}
类适配器使用对象继承的方式,是静态的定义方式;而对象适配器使用对象组
合的方式,是动态组合的方式
由于适配器直接继承了Adaptee,使得适配器不能和Adaptee的子类一起工
作,因为继承是个静态的关系,当适配器继承了Adaptee过后,就不可能再去处理Adaptee
的子类了。
允许一个Adapter和多个Adaptee,包括Adaptee和它所有的子类一
起工作。因为对象适配器采用的是对象组合的关系,只要对象类型正确,是不是子类都无
所谓。
适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方
法。
要重定义Adaptee的行为比较困难,这种情况下,需要定义
Adaptee的子类来实现重定义,然后让适配器组合子类。
仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。
对于对象适配器:需要额外的引用来间接得到Adaptee。
适配器模式的本质是:==转换匹配,复用功能==
1:如果你想要使用一个已经存在的类,但是它的接口不符合你的需求,这种情况可
以使用适配器模式,来把已有的实现转换成你需要的接口
2:如果你想创建一个可以复用的类,这个类可能和一些不兼容的类一起工作,这种
情况可以使用适配器模式,到时候需要什么就适配什么
3:如果你想使用一些已经存在的子类,但是不可能对每一个子类都进行适配,这种
情况可以选用对象适配器,直接适配这些子类的父类就可以了