编程爱好者联盟 2016-11-07
国际惯例,本文仍然是在学习设计模式的路上所写,希望对同样在学习设计模式的童靴有点作用,大牛误入的话还请给点宝贵意见,感激不尽。
今天接着来学习设计模式吧,今天要学的这个设计模式叫做适配器模式,大家都知道设计模式一般是在做系统设计的时候用的,但是适配器模式比较特殊,为什么说它比较特殊呢?因为在做系统设计的时候你可以选择遗忘这个设计模式,这个设计模式是补救措施,通常用在维护阶段或者准确来说是已经有一套完整的类结构,我们想使用其中的某些类的方法,但是这些类的方法在客户端不能直接使用,也就是说客户端不认识这些方法,这时才使用适配器模式,通过创建一个适配的类来达到使用与客户端不相关的类的方法的目的。
前面说的那么神秘,那么到底什么是适配器模式呢?我们来看一下他的定义:适配器模式将一个类的接口适配成用户所期待的,一个适配允许通过因为接口不兼容而不能再一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。直白的说,现在有一个类中有一个method方法,并且这个类不能被改变,但是客户端只能使用request方法,这时候就可以使用适配器模式,怎么做呢?下面给出简单的代码实现:
首先需要有一个被适配的类,也就是已经存在的类:
public class SourceClass { public void method(){ System.out.println("原来的类---被适配的类中的method方法"); } }
假设客户端认可的目标接口是Target,如下所示:
public interface Target { public void request(); }
客户端认可的接口的正常实现如下所示:
public class ConcreteImplement implements Target{ @Override public void request() { System.out.println("客户端认可接口的正常实现"); } }
现在客户端要使用SourceClass类中的method方法,但是有不能改变SourceClass类,这时候就需要一个适配器来进行适配,从而让客户端能够在不修改原有类的基础上也能使用method方法,适配器如下所示:
public class Adapter extends SourceClass implements Target { @Override public void request() { System.out.println("适配器----适配原来的类中的method方法"); super.method(); } }
在适配器中,要实现当前的目标接口同时要继承原来的类。
测试代码以及测试结果如下所示:
public static void main(String[] args) { Target target = new ConcreteImplement(); target.request(); System.out.println("----------------"); target = new Adapter(); target.request(); } ![](http://i.imgur.com/OylOng3.png)
通过上面的例子我们可以看到,通过添加一个继承原有类实现目标接口的适配器类就可以达到让客户端使用与目标接口中没有的类中的方法,这种适配器方式被称之为类适配器,主要的实现方式就是通过继承,这种方式很好的利用继承的优点,在达到适配的目的同时也很好的复用了原有的代码。
适配器模式还有另外一种形式,称之为对象适配器,主要是通过组合的方式来实现,还是上面的例子,其他的都不需要变,只需要将适配器修改成如下的形式即可:
public class Adapter implements Target { private SourceClass source; public Adapter(){ this.source = new SourceClass(); } @Override public void request() { System.out.println("适配器,添加被适配的类作为属性----适配原来的类中的method方法"); source.method(); } }
具体的实现方式就是在适配器类中添加被适配的类作为其一个属性,采用直接关联的方式或者说是委托的方式来达到客户端调用原来的类中的方法的目的。
这时候可能有的兄弟比较疑惑,使用继承的类适配器不是很好嘛,为什么要多此一举再加个什么对象适配器,其实这个还是JAVA不支持多继承的锅,设想一下,假设现在SourceClass类已经有了继承体系,要满足客户端调用时还需要继承另外一个类,这时候就需要同时继承两个类了,很显然是没法在extends后面写两个被继承的类的,这也就是为什么要多一个对象适配器的原因。
上面的类适配器也好,对象适配器也好,这两者都是为了客户端能够调用某个特殊的方法而写的适配器,这种适配器可以称之为特殊适配器,这些适配器都是为了达成某种目的而写,适配器模式还有另外一种适配器,叫做缺省适配器。
大家都知道,在JAVA中,只要一个类实现了一个接口,那么就必须实现这个接口中的所有的方法,前面我们的最小接口原则的那篇文章中说过,在进行类的设计时要根据具体功能实现最小的接口,也就是要遵循最小接口原则,但是即使是最小接口原则也会出现一个接口的某个实现类缺省实现某个接口方法的情况,也就是某个具体的实现类压根儿就不需要实现这个方法,但是这个类实现了接口就必须实现接口中的方法,但是又没有具体的代码实现,因此只能是空着的。为了解决这个问题也就有了我们今天所说的缺省适配器。
缺省适配器的实现一般是由一个抽象类实现的,并且在抽象类中要实现目标接口中所规定的所有方法,但是很多方法的实现都是“平庸”的实现,也就是这些方法的方法体都是空的,然后再由具体的子类继承这个抽象类,在具体的子类中覆盖掉感兴趣的方法,例子如下:
首先是目标接口,假设接口中有三个方法:
public interface TargetInterface { public void method1(); public void method2(); public void method3(); }
使用抽象类实现上述的接口中的方法,但是方法体都是默认实现:
public abstract class AbstractImpl implements TargetInterface{ public void method1(){} public void method2(){} public void method3(){} }
使用具体的子类继承上述的抽象类,并且在具体的子类中覆盖掉感兴趣的方法,不需要的方法就不必再管了,而且在抽象类中已经对其进行了平庸化实现,并不会出现编译的错误:
public class Concrete extends AbstractImpl{ public void method1(){ System.out.println("具体实现类需要实现的方法1"); } public void method2(){ System.out.println("具体实现类需要实现的方法2"); } }
在测试类(客户端)中直接实例化具体的实现类即可:
TargetInterface target = new Concrete(); target.method1(); target.method2();
刚开始看到这个缺省适配器的时候我觉得这个好像没什么用,毕竟就算具体的实现类只覆盖了部分方法,但是在客户端实例化之后还是可以调用抽象类中的其他方法,这些方法虽然都是空实现,但是还是可以调用,这也是事实,但是相对于它带来的好处却又微不足道,设想一下,如果你的目标接口中有很多方法,如果单纯实现的这个接口,在实现类中会出现很多的空方法,这些空方法没有用,但是你又不得不实现,很大程度上增加了实现类的复杂性,但是采用缺省适配器之后,虽然也有一个抽象类实现了所有的接口方法,但是在具体的实现类中我们只需要关注我们感兴趣的方法即可,这在很大程度上能减轻实现类的复杂度。
归根结底还是需要对于设计原则的灵活使用,首先就是在设计的时候需要遵循最小接口原则,但是有时候即使遵循了最小接口原则,但是还是会出现上述的情况,这时候就可以使用缺省的适配器,但是事物都有两面性,过多的使用缺省适配器,不仅仅是缺省适配器,应该说是过多的使用适配器会大大的增加系统的类的数量,并且会增加系统的复杂度,在维护上也会出现很多麻烦,明明调用的方法A,但是却是通过适配器调用的方法B,大量出现这种情况也是不好的,因此不管是再好的设计模式,再好的设计原则,总归是需要灵活使用的。
此外,在刚刚了解适配器模式的时候我觉得和代理模式很像,因为都是通过一个类来调用另一个类,但是客户端只认识代理的类,并不认识真真实的类,但是两者还是有区别的,首先就是适配器模式是一个补救措施,在设计阶段可以完全遗忘,其次,代理模式中的代理类和被代理类要实现同样的接口,也就是代理类中有的方法被代理类中也有,只不过对于关注的方法重新实现,不关注的方法就使用被代理类的实现,但是适配器模式并不需要实现被适配类中的所有方法,适配器类只需要关注感兴趣的方法即可,并且把感兴趣的方法转换成目标接口认可的方法就可以了。
此外,适配器模式也有点类似装饰者模式,但是适配器模式强调的是适配接口,但是装饰者模式强调的是增强功能,两者还是有本质区别的。
最后再简单总结一下适配器模式,适配器模式是一种结构型的设计模式,其中类适配器主要是针对目标是接口的情况下使用的,对象适配器主要是针对适配目标是具体类或者是需要将多个对象一起适配的时候,对于缺省适配器,则是为了修补接口设计过大的问题,不管是哪种适配器,使用的时候都需要根据实际情况来灵活使用,并不是说哪个适配器是好的,哪个是不好的,没有最好的,只有最合适的。
前几篇介绍了设计模式的特性并且详细讲解了4种创建型模式,创建型模式是负责如何产生对象实例的,接下来讲讲结构型模式。结构型模式是解析类和对象的内部结构和外部组合,通过优化程序结构解决模块之间的耦合问题。