对于设计模式,是长期代码的一种优化,我结合一些实例,,以自己的见解讲解一下我的浅识。说到适配器模式,
第一点比较重要的是uml图的理解,也许你现在不理解代码,但是理解了uml图,对于你以后的认识会有很大帮助。
适配器模式主要分为类适配器,对象适配器和缺省适配器。
现在我们看一下类适配器的uml结构图
上面可以看出,适配器模式包括三部分Target(目标),Adaptee(源),Adapter(适配器),在这里的我简单的写下代码,让大家有一个直观的认识
Target
public interface Target {
public void operation1();
public void operation2();
}
Adaptee
public class Adaptee {
public void operation1(){
System.out.println("我是操作1哦");
}
}
Adapter
public class Adapter extends Adaptee implements Target{
public void operation2() {
}
}
但是从上面我们除了能看到复用了一些代码外,几乎看不到任何别的意思了,这样的感觉很不好,所以现在我们就构造一个经典的场景,
中国的电器工作电压是220v的,而美国的电器工作电压是110v,现在我们在美国买了电器要在中国用,那么怎么办呢?我们需要一个变压器,
因此适配器模式有了另一个名字,变压器模式,或者转换器模式。
那么代码如何进行表示呢?下面给出实例。
中国电压
public class ChinaStandard {
public void useCnSta(){
System.out.println("我们能使用220V的电压");
}
}
能使用中国的美国制式电压
public interface AmericaEle {
public void useAmSta();
}
美国电压
public class AmericaStandard implements AmericaEle{
public void useAmSta() {
System.out.println("我们只能使用使用110v");
}
}
变压器
public class Transform extends ChinaStandard implements AmericaEle{
//使用中国220v电压
public void useAmSta() {
this.useCnSta();
}
}
我们通过变压器,能够使用220v的中国电压。哈哈,不难吧。
从上面的场景不难看出我们的适配器模式在于复用代码,(这不废话么),但是我们复用代码的前提是什么呢?
复用代码的前提是在不破坏原有代码的继承结构的基础上,并且不对代码内部的结构进行破坏,也就是我们所说的
为了某个目的,进行暂时性复用某个类的方法,既然是暂时性的,我们不能破坏原来的部分,这个不好理解,我会以jdk的io流为大家
详细介绍为什么。
我们上面的代码对于单一的类能够很好的使用了,但是如果我们的源如果有很多的派生类,但是我们的继承只能继承一个,我们没办法了,
所以我们的对象适配器横空出现了。下面是我们对象适配器的uml:
那么以这个方式,我们对原来的变压器进行优化,就变成了我们的对象适配器变压器
public class Transform implements AmericaEle{
private ChinaStandard chinaStandard=null;
public Transform(ChinaStandard chinaStandard){
this.chinaStandard=chinaStandard;
}
public void useAmSta() {
chinaStandard.useCnSta();
}
}
从这个代码上我们可以看出,我们只需要定义一个父类,通过构造函数传进去,我们就可以对多个继承关系的源进行适配了。
我们遇到的情况不会像上面一样简单,但是通过变压器的例子我们可以看出,我们的适配器是为了兼容而生的,所以他大多数用的
时候是系统升级的时候,或者要同时使用二个类的功能,而这两个类都有自己的体系,我们破坏将会影响一系列的问题,说了这么多,
意思没有变,那么我们看下复杂的情况。
那么拿我们java io的结构来说吧。我们java io的体系图:
看上去是比较复杂的,也是很让大家头疼的,那么我们一点点的说:
我们的顶级InputStream是一个抽象类,他又自己的流式功能,但是我们的InputStream现在需要一个处理文件的功能,
但是我们处理文件的功能和流式功能是兼容性不好的,也许你会直接说,我们可以直接在InputStream里加入File的处理么?
但是如果贸然加入一些方法,我们在扩展别的功能处理时就会增加InputStream的复杂性,而我们需要的也只是File的一部分功能,
如果继承的话,不仅会破坏原有的体系而且会把我们不用的功能加进来。那么在处理这个问题时,我们用到了适配器模式,
其情况就像下面的图
代码部分:
public
class FileInputStream extends InputStream
{
/* File Descriptor - handle to the open file */
private FileDescriptor fd;
private FileChannel channel = null;
public FileInputStream(FileDescriptor fdObj) {
SecurityManager security = System.getSecurityManager();
if (fdObj == null) {
throw new NullPointerException();
}
if (security != null) {
security.checkRead(fdObj);
}
fd = fdObj;
}
}
具体的方式就是用了对象适配器,就像我们有了StringBufferInputStream,但是我们现在需要文件流,必然和StringBufferInputStream不兼容,就跟中国电压和美国电压一样,虽然形式上不完全一样,但是思想上是一样的。StringBufferInputStream和ByteArrayInputStream以及InputStreamReader等等都是用的适配器,因为不知道现有的那个流。
这里我们不详细说了,那么我们看一下缺省适配器,其uml:
缺省适配器在java中的使用比较好的是WindowAdapter,我们怎么理解呢?
看一下代码结构:
public interface WindowListener extends EventListener {
public void windowOpened(WindowEvent e);
public void windowClosing(WindowEvent e);
public void windowClosed(WindowEvent e);
public void windowIconified(WindowEvent e);
public void windowDeiconified(WindowEvent e);
public void windowActivated(WindowEvent e);
public void windowDeactivated(WindowEvent e);
}
如果我们实现WindowListener我们必须写出所有方法,但是我们现在有了WindowAdapter,他是一个抽象类,代码结构
public abstract class WindowAdapter implements WindowListener{
public void windowOpened(WindowEvent e);
public void windowClosing(WindowEvent e);
public void windowClosed(WindowEvent e);
public void windowIconified(WindowEvent e);
public void windowDeiconified(WindowEvent e);
public void windowActivated(WindowEvent e);
public void windowDeactivated(WindowEvent e);
}
我只写了部分,但是如果我们实现WindowAdapter,我们就可以只复写自己需要的方法,而不需要把所有的方法都列出来。当然有一点别的好处是。抽象类可以写
默认的实现,而我们的接口不行,而有时候默认的行为是很有用的。
写到这里,本文已经差不多要完了,有很多准备了很久,但是写出来改了又改,我在努力从深一点的方式和大家探讨问题,我相信我会越来越熟练
这种方式。在这里我希望大家把我的错误说出来,代码思想本来就是分享的,为了不误人子弟,有错误一定要和我说,在此献上感谢,笔者和大家都正在这条路上奋斗。