设计模式的应用场景(7)--适配器模式

来源:互联网 发布:黎东方知乎 编辑:程序博客网 时间:2024/06/11 11:42

适配器模式

定义:将一个系统的接口转换成另外一种形式,从而使原来不能直接调用的接口变得可以调用。
优点
适配器模式也是一种包装模式,它与装饰模式同样具有包装的功能,此外,对象适配器模式还具有委托的意思。总的来说,适配器模式属于补偿模式,专用来在系统后期扩展、修改时使用。

缺点
过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

适配器模式应用场景
在软件开发中,也就是系统的数据和行为都正确,但接口不相符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。比如在需要对早期代码复用一些功能等应用上很有实际价值。适用场景大致包含三类:
1、已经存在的类的接口不符合我们的需求;
2、创建一个可以复用的类,使得该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作;
3、在不对每一个都进行子类化以匹配它们的接口的情况下,使用一些已经存在的子类。

下面举例说明适配器模式的使用:
接着上面的例子,内网的负责人写的代码是接收Map类型的

import java.util.HashMap;import java.util.Map;public class Application {    public static void execute(HashMap map) {    for (int i = 0; i < map.size(); i++) {        System.out.println(map.get(i+""));        }    }}

小巩基于外观模式写的接口是提供list

import java.util.List;import java.util.ArrayList;import java.util.Map;import java.util.HashMap;public class Facade {    public List getEmpByOrgan(String orgId) {        List list = new ArrayList();        list.add("张三");        list.add("李四");        list.add("王五");        return list;    }}

那么问题来了,两边接口不一样,需要适配器

import java.util.Map;import java.util.List;import java.util.HashMap;public class ListAdapter extends HashMap{    private List list;    ListAdapter(List list) {        this.list = list;    }    public int size() {        return list.size();    }    public Object get(Object i) {        return list.get((Integer.valueOf(i.toString())).intValue());    }}

最后,看看客户端调用代码

public class Client {    public static void main(String[] argv) {        Facade facade = new Facade();        ListAdapter listAdapter = new ListAdapter(facade.getEmpByOrgan("1"));        Application.execute(listAdapter);    }}

上面演示的是对象适配器的用法,可以看到被适配的对象是被引用的。另外一种方式是类适配器,被适配的类是以继承接口方式使用的。

下面的例子展示两种适配器的使用
考虑一个记录日志的应用,用户可能会提出要求采用文件的方式存储日志,也可能会提出存储日志到数据库的需求,这样我们可以采用适配器模式对旧的日志类进行改造,提供新的支持方式。

首先我们需要一个简单的日志对象类

public class LogBean { private String logId;//日志编号 private String opeUserId;//操作人员 public String getLogId(){ return logId; } public void setLogId(String logId){ this.logId = logId; } public String getOpeUserId(){ return opeUserId; } public void setOpeUserId(String opeUserId){ this.opeUserId = opeUserId;}public String toString(){ return "logId="+logId+",opeUserId="+opeUserId;}}

接下来定义一个操作日志文件的接口

import java.util.List;/* * 读取日志文件,从文件里面获取存储的日志列表对象 * @return 存储的日志列表对象 */public interface LogFileOperateApi { public List<LogBean> readLogFile(); /** * 写日志文件,把日志列表写出到日志文件中去 * @param list 要写到日志文件的日志列表 */ public void writeLogFile(List<LogBean> list);}

然后实现日志文件的存储和获取

import java.io.File;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.util.List;/* * 实现对日志文件的操作 */public class LogFileOperate implements LogFileOperateApi{ /* * 设置日志文件的路径和文件名称 */private String logFileName = "file.log";/* * 构造方法,传入文件的路径和名称 */public LogFileOperate(String logFilename){if(logFilename!=null){this.logFileName = logFilename;}}@Overridepublic List<LogBean> readLogFile() {// TODO Auto-generated method stubList<LogBean> list = null;ObjectInputStream oin =null;//业务代码return list;}@Overridepublic void writeLogFile(List<LogBean> list) {// TODO Auto-generated method stubFile file = new File(logFileName);ObjectOutputStream oout = null;//业务代码}}

如果这时候需要引入数据库方式,引入适配器之前,我们需要定义日志管理的操作接口

public interface LogDbOpeApi { /* * 新增日志 * @param 需要新增的日志对象 */public void createLog(LogBean logbean);}接下来就要实现适配器了,LogDbOpeApi 接口就相当于 Target 接口,LogFileOperate 就相当于 Adaptee 类import java.util.List;/* * 适配器对象,将记录日志到文件的功能适配成数据库功能 */public class LogAdapter implements LogDbOpeApi{ private LogFileOperateApi adaptee; public LogAdapter(LogFileOperateApi adaptee){ this.adaptee = adaptee; }@Overridepublic void createLog(LogBean logbean) {// TODO Auto-generated method stubList<LogBean> list = adaptee.readLogFile();list.add(logbean);adaptee.writeLogFile(list);}}

最后是客户端代码的实现

import java.util.ArrayList;import java.util.List;public class LogClient { public static void main(String[] args){ LogBean logbean = new LogBean(); logbean.setLogId("1"); logbean.setOpeUserId("michael"); List<LogBean> list = new ArrayList<LogBean>(); LogFileOperateApi logFileApi = new LogFileOperate(""); //创建操作日志的接口对象 LogDbOpeApi api = new LogAdapter(logFileApi); api.createLog(logbean); }}

下面演示累类适配器

import java.util.List;/* * 类适配器对象案例 */public class ClassAdapter extends LogFileOperate implements LogDbOpeApi{public ClassAdapter(String logFilename) {super(logFilename);// TODO Auto-generated constructor stub}@Overridepublic void createLog(LogBean logbean) {// TODO Auto-generated method stubList<LogBean> list = this.readLogFile();list.add(logbean);this.writeLogFile(list);}}

在实现中,主要是适配器的实现与以前不一样,与对象适配器实现同样的功能相比,类适配器在实现上有所改变:
需要继承 LogFileOperate 的实现,然后再实现 LogDbOpeApi 接口;

需要按照继承 LogFileOperate 的要求,提供传入文件路径和名称的构造方法;

不再需要持有 LogFileOperate 的对象,因为适配器本身就是 LogFileOperate 对象的子类;

以前调用被适配对象的方法的地方,全部修改成调用自己的方法。

类适配器和对象适配器的选择
从实现上:类适配器使用对象继承的方式,属于静态的定义方式。对象适配器使用对象组合的方式,属于动态组合的方式;

从工作模式上:类适配器直接继承了 Adaptee,使得适配器不能和 Adaptee 的子类一起工作。对象适配器允许一个 Adapter 和多个 Adaptee,包括 Adaptee 和它所有的子类一起工作;

从定义角度:类适配器可以重定义 Adaptee 的部分行为,相当于子类覆盖父类的部分实现方法。对象适配器要重定义 Adaptee 很困难;

从开发角度:类适配器仅仅引入了一个对象,并不需要额外的引用来间接得到 Adaptee。对象适配器需要额外的引用来间接得到 Adaptee。

总的来说,建议使用对象适配器方式。