设计模式---------适配器模式
来源:互联网 发布:vv51软件下载 编辑:程序博客网 时间:2024/06/08 00:38
适配器模式解决的问题就是将一个类的接口转化为客户希望的另一个接口,使得原来由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式的本质是:转换匹配和复用。适配器通过转换调用已有的实现,从而把已有的实现匹配成需要的接口,使之能够满足客户端的需求。也就是说转换匹配时手段,复用已知的功能才是目的。
我们来考虑一个记录日志的应用,假设由于用户对日志记录的要求比较高,已经不能简单地采用一些已有的日志工具或日志框架来满足用户的需求,我们需要重新根据用户的要求重新开发一个新的日志管理系统。
一、 日志管理的第一版
用户要求日志以文件的形式记录,我们对日志文件的存取实现如下:
1、描述日志文件的对象模型。由于后面我们会采用ObjectInputStream类来实现将对象写入到文件中,因此需要对这个对象序列化。
/** * 日志数据对象 */public class LogInfo 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;}@Overridepublic String toString() {return "logId=" + logId + ", operateUser=" + operateUser + ", operateTime="+ operateTime + ", logContent=" + logContent ;}
2、定义一个操作日志文件的接口
public interface LogFileOperate {/** * 读取日志文件,从文件里面获取存储的日志列表对象 * @return 存储日志的列表对象 */public List<LogInfo> readLogFile();/** * 写日志文件,把日志列表写出到日志文件中去 * @param list */public void writeLogFile(List<LogInfo> list);}
3、实现日志文件的存取
public class LogFileOperateImpl implements LogFileOperate {/** * 默认日志文件的路径和名称,默认是当前项目的根目录下 */private String logFilePathName = "Log.log";/** * 构造方法,传入文件的路径和名称 * @param logFilePathName 文件路径和名称 */public LogFileOperateImpl(String logFilePathName) {/** * 若通过构造方法传入数据,且不为空字符串,则修改日志文件名称和路径 */if(logFilePathName != null && logFilePathName.trim().length() > 0)this.logFilePathName = logFilePathName;}@Overridepublic List<LogInfo> readLogFile() {List<LogInfo> list = null;ObjectInputStream objectInputStream = null;try {File file = new File(logFilePathName);if(file.exists()) {objectInputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file)));list = (List<LogInfo>) objectInputStream.readObject();}} catch (Exception e) {e.printStackTrace();} finally {try {if(objectInputStream != null) {objectInputStream.close();} }catch(IOException e) {e.printStackTrace();}}return list;}@Overridepublic void writeLogFile(List<LogInfo> list) {File file = new File(logFilePathName);ObjectOutputStream objectOutputStream = null;try {objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)));objectOutputStream.writeObject(list);} catch (Exception e) {e.printStackTrace();} finally {try {objectOutputStream.close();}catch(IOException e) {e.printStackTrace();}}}}
4、客户端测试
public class Client {public static void main(String[] args) {List<LogInfo> list = new ArrayList<LogInfo>();LogInfo info = new LogInfo();info.setLogId("0001");info.setOperateUser("Tom");info.setOperateTime("2014-11-03 15:09:18");info.setLogContent("这是一个测试!");list.add(info);LogFileOperate operate = new LogFileOperateImpl("");operate.writeLogFile(list);List<LogInfo> readLog = operate.readLogFile();for(LogInfo log : readLog) {System.out.println(log.toString());}}}
测试结果如下:
logId=0001, operateUser=Tom,operateTime=2014-11-03 15:09:18, logContent=这是一个测试!
至此简单实现了用户的功能,即把日志保存到文件中,并能够从文件中把日志内容读取出来,进行管理。
一、 日志管理的第二版
当用使用一段时间后,开始考虑实际系统,决定使用数据库来管理日志系统。这样我们定义了日志管理的操作接口,主要针对日志的增删改查方法。
/** * 定义操作日志的应用接口,为了实例的简单,只是简单地定义了增删改查 * @author Admin */public interface LogDbOperate {/** * 新增日志 * @param info 需要新增的日志对象 */public void createLog(LogInfo info);/** * 修改日志 * @param info需要修改的日志对象 */public void updateLog(LogInfo info);/** * 删除日志 * @param info需要删除的日志对象 */public void removeLog(LogInfo info);/** * 获取所有的日志 * @return 所有的日志对象 */public List<LogInfo> getAllLog();}
然后,实现上述接口:
public class LogDbOperateImpl implements LogDbOperate {private DB db;public LogDbOperateImpl() {db = DB.getInstance();db.update("CREATE TABLE IF NOT EXISTS logdata (LogId varchar(10) primary key, OperateUser varchar(20), OperateTime varchar(24), LogContent varchar(200))");}@Overridepublic void createLog(LogInfo info) {String sql = "insert into logdata(LogId, OperateUser, OperateTime, LogContent) values ('" +info.getLogId() + "', '" + info.getOperateUser() + "', '" +info.getOperateTime() + "', '" + info.getLogContent() + "')";db.update(sql);}@Overridepublic List<LogInfo> getAllLog() {String sql = "select LogId, OperateUser, OperateTime, LogContent from logdata";List<HashMap<String, String>> mapList = db.query(sql);List<LogInfo> logList = new ArrayList<LogInfo>();LogInfo logInfo = null;for (HashMap<String, String> map : mapList) {logInfo = new LogInfo();logInfo.setLogId(String.valueOf(map.get("LogId")));logInfo.setOperateUser(map.get("OperateUser"));logInfo.setOperateTime(map.get("OperateTime"));logInfo.setLogContent(map.get("LogContent"));logList.add(logInfo);}return logList;}@Overridepublic void removeLog(LogInfo info) {String sql = null;if (info.getLogId() != null) {sql = "delete from logdata where LogId = '" + info.getLogId() + "'";} else {return;}db.update(sql);}@Overridepublic void updateLog(LogInfo info) {StringBuffer sql = new StringBuffer("update logdata set ");int len = sql.length();if (info.getOperateUser() != null || "".equals(info.getOperateUser()))sql.append("OperateUser='" + info.getOperateUser() + "',");if (info.getOperateTime() != null || "".equals(info.getOperateTime()))sql.append("OperateTime='" + info.getOperateTime() + "',");if (info.getLogContent() != null || "".equals(info.getLogContent()))sql.append("LogContent='" + info.getLogContent() + "',");if (sql.length() > len && info.getLogId() != null) {sql.deleteCharAt(sql.length() - 1);sql.append(" where LogId='" + info.getLogId() + "'");} else {return;}db.update(sql.toString());}
那么问题来了,倘若客户提出了新的要求,能不能让日志管理第二版同时实现数据库的存储和文件存储两种方式,我们现在该肿么办?
一种很容易想到的方法就是直接修改第一版的代码,使其满足第二版的接口,显然这是不太友好的,况且我们有时是无法拿到第一版的源代码的。
按照适配器模式的方式实现,可以定义一个类来实现第二版的接口,然后在内部实现的时候转调第一版已经实现的功能即可,这样通过对象组合的方式,即复用了第一版已有的功能,同时又满足的第二版的调用要求。
我在这里使用结构图来描述一下适配器模式:
(1) Client:客户端,调用自己需要的某个功能接口Traget
(2)Traget:定义客户端需要的功能的接口
(3)Adaptee:已存在的类,通常能够满足客户端的功能要求,但该类的以客户端要求功能的接口不一致,需要适配
(4)Adapter:适配器,把Adaptee适配为Client需要 的Target
在这里我们那上面的例子,来写一个适配器的示例,将记录日志到文件的功能是配成第二版需要增删改查的功能:
/** * 适配器对象,将记录日志到文件的功能适配成使用数据库版的增删改查 * @author Admin */public class Adapter implements LogDbOperate {/** * 持有需要适配的接口对象 */private LogFileOperate adaptee;public Adapter(LogFileOperate adaptee) {this.adaptee = adaptee;}@Overridepublic void createLog(LogInfo info) {List<LogInfo> list = adaptee.readLogFile();if(list == null) {list = new ArrayList<LogInfo>();}System.out.println(list.size());list.add(info);adaptee.writeLogFile(list);}@Overridepublic List<LogInfo> getAllLog() {return adaptee.readLogFile();}@Overridepublic void removeLog(LogInfo info) {List<LogInfo> list = adaptee.readLogFile();list.remove(info);adaptee.writeLogFile(list);}@Overridepublic void updateLog(LogInfo info) {List<LogInfo> list = adaptee.readLogFile();for(int i=0; i<list.size(); i++) {if(list.get(i).getLogId().equals(info.getLogId())) {list.set(i, info);break;}}adaptee.writeLogFile(list);}}
此时客户端也需要一些改变来进行测试:
public class AdapterClient {public static void main(String[] args) {List<LogInfo> list = new ArrayList<LogInfo>();LogInfo info = new LogInfo();info.setLogId("0001");info.setOperateUser("Tom");info.setOperateTime("2014-11-03 15:09:18");info.setLogContent("这是一个测试!");list.add(info);LogFileOperate fileOperate = new LogFileOperateImpl("");LogDbOperate dbOperate = new ClassAdapter("");//dbOperate.createLog(info);List<LogInfo> allLog = dbOperate.getAllLog();for(LogInfo log : allLog) {System.out.println(log.toString());}}}
继续上述例子,如果说有某种原因,第一版和第二版会同时共存一段时间,也就说第二版不是太稳定,客户提出希望两版共存期间,主要使用第一版,同时希望第一版的数据也能够记录到数据库中,这样我们可以使用双向适配器,其代码示例为:
** * 双向适配器对象 */public class TwoDirectAdapter implements LogDbOperate, LogFileOperate {private LogFileOperate fileLog;private LogDbOperate dbLog;public TwoDirectAdapter(LogFileOperate fileLog, LogDbOperate dbLog) {this.fileLog = fileLog;this.dbLog = dbLog;}public TwoDirectAdapter(LogFileOperate fileLog) {this.fileLog = fileLog;}public TwoDirectAdapter(LogDbOperate dbLog) {this.dbLog = dbLog;}/** * 适配器对象,将记录日志到文件的功能适配成使用数据库版的增删改查 */@Overridepublic void createLog(LogInfo info) {List<LogInfo> list = fileLog.readLogFile();if (list == null) {list = new ArrayList<LogInfo>();}System.out.println(list.size());list.add(info);fileLog.writeLogFile(list);}@Overridepublic List<LogInfo> getAllLog() {return fileLog.readLogFile();}@Overridepublic void removeLog(LogInfo info) {List<LogInfo> list = fileLog.readLogFile();list.remove(info);fileLog.writeLogFile(list);}@Overridepublic void updateLog(LogInfo info) {List<LogInfo> list = fileLog.readLogFile();for (int i = 0; i < list.size(); i++) {if (list.get(i).getLogId().equals(info.getLogId())) {list.set(i, info);break;}}fileLog.writeLogFile(list);}/** * 以下是把DB操作的方式适配到成为文件实现方式的接口 */@Overridepublic List<LogInfo> readLogFile() {return dbLog.getAllLog();}@Overridepublic void writeLogFile(List<LogInfo> list) {for(LogInfo info : list) {dbLog.createLog(info);}}}
在标准适配器模式里面,根据适配器的实现方式,把适配器分为对象适配器和类适配器。
对象适配器的实现依赖于对象的组合,就如前面的实现示例。
类适配器的实现采用了多重继承对一个接口与另一个接口进行匹配,由于java不支持多继承,我们在这里以单继承为例来说明。
针对上述例子,我们可以写一个适配器类,让适配器去继承文件存储日志的实现,然后让适配器类实现第二版日志操作接口的要求。其示例代码如下:
public class ClassAdapter extends LogFileOperateImpl implements LogDbOperate {public ClassAdapter(String logFilePathName) {super(logFilePathName);}@Overridepublic void createLog(LogInfo info) {List<LogInfo> list = this.readLogFile();if(list == null) {list = new ArrayList<LogInfo>();}System.out.println(list.size());list.add(info);this.writeLogFile(list);}@Overridepublic List<LogInfo> getAllLog() {return this.readLogFile();}@Overridepublic void removeLog(LogInfo info) {List<LogInfo> list = this.readLogFile();list.remove(info);this.writeLogFile(list);}@Overridepublic void updateLog(LogInfo info) {List<LogInfo> list = this.readLogFile();for(int i=0; i<list.size(); i++) {if(list.get(i).getLogId().equals(info.getLogId())) {list.set(i, info);break;}}this.writeLogFile(list);}}
适配器模式具有更好的复用性和可扩展性,但是当过多的使用适配器模式,为让系统非常的凌乱,不容易整体的把握。
- 设计模式:适配器模式
- 设计模式------适配器模式
- 设计模式:适配器模式
- 设计模式---适配器模式
- 设计模式 适配器模式
- 设计模式 - 适配器模式
- 设计模式--适配器模式
- 设计模式-适配器模式
- 设计模式------适配器模式
- 设计模式---适配器模式
- 设计模式- 适配器模式
- 设计模式 - 适配器模式
- 设计模式:适配器模式
- 适配器模式--设计模式
- 设计模式 - 适配器模式
- 设计模式-适配器模式
- 设计模式---适配器模式
- 设计模式-适配器模式
- Makefile使用,Makefile的优化全过程
- 淘宝技术这十年 读后感 Day1
- MP3文件结构及解码概述
- 每日get一个新命令
- 优惠劵收集问题
- 设计模式---------适配器模式
- TCP/IP、Http、Socket的区别
- UVALive - 2911 Maximum
- C++函数指针的定义
- httpd: Could not reliably determine the server's fully qualified domain name
- Android 中 的adapter优化问题
- 数据结构&&平衡二叉树
- 【Leetcode】Reverse Words in a String JAVA
- QCA4004开发板之使用