`

适配器模式

 
阅读更多

4.1  场景问题

4.1.1  装配电脑的例子

1:旧的硬盘和电源

小李有一台老的台式电脑,硬盘实在是太小了,仅仅40GB,但是除了这个问题外,整机性能还不错,废弃不用太可惜了,于是决定去加装一块新的硬盘。

在装机公司为小李的电脑加装新硬盘的时候,小李也在边上观看,顺便了解点硬件知识。很快的,装机人员把两块硬盘都安装好了,细心的小李发现,这两块硬盘的连接方式是不一样的。

经过装机人员的耐心讲解,小李搞清楚了它们的不同。以前的硬盘是串口的,如图4.1,电脑电源如图4.2,那么连接电源的时候是直接连接。

         

2:加入新的硬盘

但是现在的新硬盘是并口的,如图4.3,电源的输出口无法直接连接到新的硬盘上了,于是就有了转接线,一边和电源的输出口连接,一边和新的硬盘电源输入口连接,解决了电源输出接口和硬盘输入接口不匹配的问题,如图4.4

            

 

3:有何问题

如果把上面的问题抽象一下,用对象来描述,那就是:有一个电源类和旧的硬盘类配合工作得很好,现在又有了一个新的硬盘类,现在想让新的硬盘类和电源类也配合使用,但是发现它们的接口无法匹配,问题就产生了:如何让原有的电源类的接口能够适应新的硬盘类的电源接口的需要呢?

4:如何解决

解决方法是采用一个转接线类,转接线可以把电源的接口适配成为新的硬盘所需要的接口,那么这个转接线类就类似本章的主角——适配器。

4.1.2  同时支持数据库和文件的日志管理

       看了上面这个例子,估计对适配器模式有一点感觉了。这是个在生活中常见的例子,类似的例子很多,比如:各种管道的转接头、不同制式的插座等等。但是这种例子只能帮助大家理解适配器模式的功能,跟实际的应用系统开发总是有那么些差距,会感觉到好像是理解了模式的功能,但是一到真实的系统开发中,就不知道如何使用这个模式了,有些隔靴搔痒的感觉。因此,下面还是以实际系统中的例子来讲述,以帮助大家真正理解和应用适配器模式。

考虑一个记录日志的应用,由于用户对日志记录的要求很高,使得开发人员不能简单的采用一些已有的日志工具或日志框架来满足用户的要求,而需要按照用户的要求重新开发新的日志管理系统。当然这里不可能完全按照实际系统那样去完整实现,只是抽取跟适配器模式相关的部分来讲述。

1:日志管理第一版

在第一版的时候,用户要求日志以文件的形式记录。开发人员遵照用户的要求,对日志文件的存取实现如下。

(1)先简单定义日志对象,也就是描述日志的对象模型,由于这个对象需要被写入文件中,因此这个对象需要序列化,示例代码如下:

/**

 * 日志数据对象

 */

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;

    }

}

(2)接下来定义一个操作日志文件的接口,示例代码如下:

/**

 * 日志文件操作接口

 */

public interface LogFileOperateApi {

    /**

     * 读取日志文件,从文件里面获取存储的日志列表对象

     * @return 存储的日志列表对象

     */

    public List<LogModel> readLogFile();

    /**

     * 写日志文件,把日志列表写出到日志文件中去

     * @param list 要写到日志文件的日志列表

     */

    public void writeLogFile(List<LogModel> list);

}

(3)实现日志文件的存取,现在的实现也很简单,就是读写文件,示例代码如下:

/**

 * 实现对日志文件的操作

 */

public class LogFileOperate implements LogFileOperateApi{

    /**

     * 日志文件的路径和文件名称,默认是当前项目的根下的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<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 (IOException e) {

              e.printStackTrace();

           }

       }

       return list;

    }

 

    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 (IOException e) {

           e.printStackTrace();

       }finally{

           try {

              oout.close();

           } catch (IOException e) {

              e.printStackTrace();

           }

       }

    }

}

(4)写个客户端来测试一下,看看好用不,示例代码如下:

public class Client {

    public static void main(String[] args) {

       //准备日志内容,也就是测试的数据

       LogModel lm1 = new LogModel();

       lm1.setLogId("001");

       lm1.setOperateUser("admin");

       lm1.setOperateTime("2010-03-0210:08:18");

       lm1.setLogContent("这是一个测试");

      

       List<LogModel> list = new ArrayList<LogModel>();

       list.add(lm1);

 

       //创建操作日志文件的对象

       LogFileOperateApi api = new LogFileOperate("");

       //保存日志文件

       api.writeLogFile(list);

      

       //读取日志文件的内容

       List<LogModel> readLog = api.readLogFile();

       System.out.println("readLog="+readLog);

    }

}

       测试的结果如下:

readLog=[logId=001,operateUser=admin,operateTime=2010-03-02 10:08:18,logContent=这是一个测试]

至此就简单的实现了用户的要求,把日志保存到文件中,并能从文件中把日志内容读取出来,进行管理。

看上去很容易,对吧,别慌,接着来。

2:日志管理第二版

       用户使用日志管理的第一版一段时间过后,开始考虑升级系统,决定要采用数据库来管理日志,很快,按照数据库的日志管理也实现出来了,并定义了日志管理的操作接口,主要是针对日志的增删改查方法,接口的示例代码如下:

/**

 * 定义操作日志的应用接口,为了示例的简单,只是简单的定义了增删改查的方法

 */

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<LogModel> getAllLog();

}

       对于使用数据库来保存日志的实现,这里就不去涉及了,反正知道有这么一个实现就可以了。

       客户提出了新的要求,能不能让日志管理的第二版,实现同时支持数据库存储和文件存储两种方式?

4.1.3  有何问题

       有朋友可能会想,这有什么困难的呢,两种实现方式不是都已经实现了的吗,合并起来不就可以了?

       问题就在于,现在的业务是使用的第二版的接口,直接使用第二版新加入的实现是没有问题的,第二版新加入了保存日志到数据库中;但是对于已有的实现方式,也就是在第一版中采用的文件存储的方式,它的操作接口和第二版不一样,这就导致现在的客户端,无法以同样的方式来直接使用第一版的实现,如下图4.5所示:

 

图4.5  无法兼容第一版的接口示意图

这就意味着,要想同时支持文件和数据库存储两种方式,需要再额外的做一些工作,才可以让第一版的实现适应新的业务的需要。

可能有朋友会想,干脆按照第二版的接口要求重新实现一个文件操作的对象不就可以了,这样确实可以,但是何必要重新做已经完成的功能呢?应该要想办法复用,而不是重新实现。

一种很容易想到的方式是直接修改已有的第一版的代码。这种方式是不太好的,如果直接修改了第一版的代码,那么可能会导致其它依赖于这些实现的应用不能正常运行,再说,有可能第一版和第二版的开发公司是不一样的,在第二版实现的时候,根本拿不到第一版的源代码。

那么该如何来实现呢?

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics