适配模式(Adapter)
思想概要
编程中经常会遇到,在要使用某些类的时候发现它所需要的接口和我们所拥有的接口不一致,比如:需要把一个树形控件和一个列表控件中的数据存到数据库里,而存储数据库的API只接受数组类型。你当然可以重写数据库类的代码,让他同时可以接受多种数据,但是这往往不是你期待的,而且修改已经稳定运行的代码不是一样明智的事情。这个时候我们可以考虑使用适配模式。
如果你熟知适配模式的三种实现类型,在看到上面的例子是你就天然的会想到组合模式的适配器,因为看起来顺理成章,但是我假设你并不是那么熟悉适配器模式,我们需要从头比较适配器模式的具体实现。
插座思维
讨论适配模式逃不过老套的插座实例,因为它真的是这个模型的活化石,最好的样本之一。言归正传,当我们购买一台美国电器时,转接口就变成你的必购商品了。但是从逻辑上讲,你有两种思考方式来看待这个转接口,一个是继承思维,即:转接口本身就是插座,我把它封在墙壁里不让你看见它身下的真实插座,你就默认它是个常用插座了。而另一个思维是组合思维,转接口是独立于插座的器件,它需要一个插座给它提供电流,而自己再转成别的接口。
类适配
第一种思维的结果就是类适配,即在类层次进行适配,使用的实现方法当然就只能是继承了。如类图所示:转接口继承了插座(Adaptee),让它看起来就是个普通的插座,而它又实现了电器需要的Target接口,使得电器能够不改变代码的情况下接受自己。
对象适配
第二中思维的结果就是接口适配,即针对某个对象进行适配,采用的技术自然就是组合。如类图所示:适配器实现了Target接口,组合了插座(Adaptee),它看起来就是个转接口而不是真正的插座。
上面我们论述了两种适配模式的实现方法,以这两种实现方法为代表的适配器模式的应用场景其实比较特殊,它的本质是解决已有系统的兼容问题而不应该在构建新系统时积极的使用它。如同我们之前讨论的那个具体问题,当你需要在新系统里,把列表控件和Tree控件中的数据传换成数组的时候,你最好扩展这两个控件的API,让他们可以生成数组类型的数据,而不是编写适配器模式。否则,大量的适配器类将严重的影响代码的可读性。
接口适配
接口适配是个独立于上面两种模型的概念,以至于我从来都不把这个叫做适配器模式,它的应用很广泛,不仅仅在解决已有系统的兼容性问题,甚至是编写第三方类库时都常常使用。它的基本应用场景就是:某些接口的API数量太多,而我其实仅仅会去实现其中的极少数API,但是我却不得不写下大量的空函数类应付编译器。这个时候,你就可以考虑使用接口适配了。
接口适配的实现很简单,即:创建一个包含所有接口API实现的纯虚类,让它来应付编译器,而真实类则继承纯虚类,在其中实现自己想要实现的某些API。这本质上其实就是个应付编译器的模式,简单到我都不想贴类图,仅仅贴上一段Apache Commons IO中的一段代码来说明。
public interface FileAlterationListener { /* APIs*/ void onStart(final FileAlterationObserver observer); void onDirectoryCreate(final File directory); void onDirectoryChange(final File directory); void onDirectoryDelete(final File directory); void onFileCreate(final File file); void onFileChange(final File file); void onFileDelete(final File file); void onStop(final FileAlterationObserver observer); }
这个用来监视硬盘上文件动作的接口拥有数量不少的API,但是对于应用来说,我们常常只关心其中某一个而已,但是如果每次都要实现这么多空的函数来应付编译器,那简直就是噩梦。类库的开发者也意识到了这点,他们设计了一个类来做这件事。
public class FileAlterationListenerAdaptor implements FileAlterationListener { @Override public void onStart(final FileAlterationObserver observer) { } @Override public void onDirectoryCreate(final File directory) { } @Override public void onDirectoryChange(final File directory) { } @Override public void onDirectoryDelete(final File directory) { } @Override public void onFileCreate(final File file) { } @Override public void onFileChange(final File file) { } @Override public void onFileDelete(final File file) { } @Override public void onStop(final FileAlterationObserver observer) { } }
这个类实现了所有的API,但都是空函数。子类如果仅仅想关注文件的创建,那么只需要继承自这个适配器类,然后编写onFileCreate就好了。对于这样的类我们也叫做适配器,当你觉得某些接口太大,但是又不需要每次都要实现的时候,你可以大胆的使用这样的适配器。