一. 适配器模式定义:
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原来由于接口不兼容而不能一起工作的那些类可以一起工作。
二. 结构和说明:
Client:客户端,调用自己需要的领域接口Target
Target:定义客户端需要的跟特定领域相关的接口
Adaptee:已经存在的接口,但与客户端要求的特定领域接口不一致,需要被适配
Adapter:适配器,把Adaptee适配成为Client需要的Target
三. 时序图:
四. 简单代码实现:
1. 使用适配器的客户端:
public class Client { public static void main(String[] args) { // 创建需被适配的对象 Adaptee adaptee = new Adaptee(); // 创建客户端需要调用的接口对象 Target target = new Adapter(adaptee); // 请求处理 target.request(); } }2. 定义客户端使用的接口,与特定领域相关
public interface Target { /** * 示意方法,客户端请求处理的方法 */ public void request(); }3. 已经存在的接口,这个接口需要被适配
public class Adaptee { /** * 示意方法,原本已经存在,已经实现的方法 */ public void specificRequest() { // 具体的功能处理 } }4. 适配器
public class Adapter implements Target { // 持有需要被适配的接口对象 private Adaptee adaptee; // 构造方法,传入需要被适配的对象 public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { // 可能转调已经实现了的方法,进行适配 adaptee.specificRequest(); } }
五. 真实的案例:
假设以前我们的系统使用文件存取日志,我们想改成数据库CRUD的形式,但是还想复用以前的function:
1. 日志数据对象:
public class LogModel implements Serializable { private static final long serialVersionUID = 1L; // 日志编号 private String logId; // 操作人员 private String operateUser; // 操作时间,以yyyy-MM-dd HH:mm:ss的格式记录 private String operateTime; // 日志内容 private String logContent; // setter and getter }2. 日志文件操作接口:
public interface LogFileOperateApi { /** * 读取日志文件,从文件里面获取存储的日志列表对象 */ public List<LogModel> readLogFile(); /** * 写日志文件,把日志列表写出到日志文件中去 */ public void writeLogFile(List<LogModel> list); }3. 实现对日志文件的操作:
public class LogFileOperate implements LogFileOperateApi { // 日志文件的路径和文件名称,默认是当前classpath下的AdapterLog.log private String logFilePathName = "AdapterLog.log"; /** * 构造方法,传入文件的路径和名称 */ public LogFileOperate(String logFilePathName) { // 先判断是否传入了文件的路径和名称,如果是就重新设置操作的日志文件的路径和名称 if (logFilePathName != null && logFilePathName.trim().length() > 0) { this.logFilePathName = logFilePathName; } } @SuppressWarnings("unchecked") public List<LogModel> readLogFile() { List<LogModel> list = null; ObjectInputStream ois = null; try { File file = new File(logFilePathName); if (file.exists()) { ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file))); list = (List<LogModel>) ois.readObject(); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (ois != null) { ois.close(); } } catch (IOException e) { e.printStackTrace(); } } return list; } public void writeLogFile(List<LogModel> list) { File file = new File(logFilePathName); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file))); oos.writeObject(list); } catch (IOException e) { e.printStackTrace(); } finally { try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } }4. 操作日志的应用接口:
public interface LogDbOperateApi { /** * 新增日志 */ public void createLog(LogModel logModel); /** * 修改日志 */ public void updateLog(LogModel logModel); /** * 删除日志 */ public void removeLog(LogModel logModel); /** * 获取所有的日志 */ public List<LogModel> getAllLog(); }5. 类适配器:
public class ClassAdapter extends LogFileOperate implements LogDbOperateApi { /* * 因为继承了LogFileOperate,所以就不需要再持有LogFileOperate * 这是类适配器 */ public ClassAdapter(String logFilePathName) { super(logFilePathName); } public void createLog(LogModel logModel) { // 1:先读取文件的内容 List<LogModel> list = this.readLogFile(); // 2:加入新的日志对象 list.add(logModel); // 3:重新写入文件 this.writeLogFile(list); } public void updateLog(LogModel logModel) { // 1:先读取文件的内容 List<LogModel> list = this.readLogFile(); // 2:修改相应的日志对象 for (int i = 0; i < list.size(); i++) { if (list.get(i).getLogId().equals(logModel.getLogId())) { list.set(i, logModel); break; } } // 3:重新写入文件 this.writeLogFile(list); } public void removeLog(LogModel logModel) { // 1:先读取文件的内容 List<LogModel> list = this.readLogFile(); // 2:删除相应的日志对象 list.remove(logModel); // 3:重新写入文件 this.writeLogFile(list); } public List<LogModel> getAllLog() { return this.readLogFile(); } }6. 客户端:
public class Client { public static void main(String[] args) { // 准备日志内容,也就是测试的数据 LogModel logModel = new LogModel(); logModel.setLogId("001"); logModel.setOperateUser("admin"); logModel.setOperateTime("2010-03-02 10:08:18"); logModel.setLogContent("这是一个测试"); List<LogModel> list = new ArrayList<LogModel>(); list.add(logModel); // 创建新版的操作日志的接口对象 LogDbOperateApi api = new ClassAdapter(""); // 保存日志文件 api.createLog(logModel); // 读取日志文件的内容 List<LogModel> allLog = api.getAllLog(); System.out.println("allLog=" + allLog); } }六. 何时使用适配器:
1. 如果你想要使用一个已经存在的类,但是它的接口不符合你的需求,这种情况下可以使用适配器模式,来把已有的实现转换成你需要的接口。
2. 如果你想使用一些已经存在的子类,但是不可能对每个子类都进行适配,这种情况下可以选用对象适配器(见四),直接适配这些子类的父类就可以了。
3. 如果是新开发一个功能,就不建议用适配器模式,它一般用于后期拓展、维护。
七. 适配器的优缺点:
1. 更好的复用性及可扩展性
2. 过多的使用适配器,会让系统非常凌乱,不容易整体进行把握