适配器模式从字面意思来看,有适配器的意思,这么说吧,最通常的体现是我们的插座,有两孔的,有三孔的,当我们只有两空的插座时,而又想使用一个三孔的插头,这时候怎么办,我们可以通过寻找一个适配器,其实就是一个能够连接两孔和三孔的插头。同样的我们运用到软件设计上来说有些时候我们同样的也需要有这样的适配器,比如有个系统暴露了很多接口,另一个数据部分也暴露的很多接口,但是这些接口不能够直接的联系在一起使用,这时候我们可以编写一个类,实现将两个部分连接起来,其实这个类就可以被称作为适配器,我想这样解释一下也应该有了更为直观的认识:
比如说我们可以参考下面的例子:
我们有一个鸭子类的接口,也有一个火鸡类的接口,当遇到某个情况的时候,我们需要很多的鸭子,然而,鸭子不够了,需要使用火鸡类包装下当作鸭子类进行使用,如果直接使用是不可行的,那按照上面所说的,我们可以来定义一个适配器,实现这其中的转变:
首先,设计一个鸭子类的接口和接口的实现MallardDuck类,定义了鸭子叫和飞翔的方法:
public interface Duck{
public void quack();
public void fly();
}
public class MallardDuck implements Duck{
public void quack(){
System.out.println("Quack!");
}
public void fly(){
System.out.println("I'm flying");
}
}
接下来设计一个火鸡接口以及其实现类WildTurkey类:
public interface Turkey{
public void gobble();
public void fly();
}
public class WildTurkey implements Turkey{
public void gobble(){
System.out.printlin("Gobble gobble!");
}
public void fly(){
System,out.println("I'm flying a short distance");
}
}
为了完成上述的场景,我们开始写一个适配器:
public class TurkeyAdapter (Turkey Tturkey) implements Duck{
Turkey turkey;
public TurkeyAdapter(Turkey turkey){
this.turkey = turkey;
}
public void quack(){
turkey.gobble();
}
public void fly(){
for (int i = 0;i < 5 ;i++ )
{
turkey.fly();
}
}
}
好了,使用我们自己的代码来进行一个测试吧:
public class TurkeyAdapter (Turkey Tturkey) implements Duck{
Turkey turkey;
public TurkeyAdapter(Turkey turkey){
this.turkey = turkey;
}
public void quack(){
turkey.gobble();
}
public void fly(){
for (int i = 0;i < 5 ;i++ )
{
turkey.fly();
}
}
}
public class AdapterTest{
public static void main(String[] args){
MallardDuck duck = new MallardDuck();
WildTurkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("the Turkey says...");
turkey.gobble();
turkey.fly();
System.out.println("\nthe Duck says...");
testDuck(duck);
System.out.println("\nthe TurkeyAdapter says...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck){
duck.quack();
duck.fly();
}
}
测试程序中,定义了三个对象,duck,turkey和turkeyAdapter,分别对比比较观察是否能够完成所说的情景,实验结果如下:
当看完这个实现过程之后,你可能会有这样的疑问:假如是一个很大的目标接口,我们是不是需要很多的工作要做?是的,的确像我们猜想的这样,和目标接口的大小成正比,如果不用适配器,则需要我们重新调查代码,重新改写代码,这样的工作要比写一个适配器要复杂的多,将所有的代码改变封装在一个类中还是比较好的,所以适配器模式再这方面还是有优势的。
同样的你也会问适配器的工作是将一个接口转化为另一个接口,当我们需要一个适配器适合多个被适配者的时候还要结合外观模式来进行设计,人们常常将外观模式和适配器模式混淆。稍后将会解释两者之间的不同。
适配器模式其实就是进行接口转换,让不兼容的接口变成兼容的接口,这可以让客户从实现的接口解耦,当需要改表接口的时候,只需要修改相应的适配器即可,客户就不必为了不同的接口而每次跟着修改。
这个是适配器符合良好的OO设计原则,使用对象的组合,以修改的接口包装被适配者,这种做法还有一个有点,其可以适配任何子类,都可以搭配着适配器来使用,不过这个模式是将客户和接口连接起来,而不是实现绑定起来,我们可以使用数个适配器,每一个都负责转换不同的后台类,或者加上新的实现,只要它们遵守目标接口就可以。上面的图是解释的对象适配器,而我们下面将要说一下类适配器,为什么不亦快说了呢,是有原因的:
很明显,这个我们在Java中是无法不能够实现的,Java中不支持多重继承,所以这个模式在Java中是不能实现的,但是在其他的支持多重继承的语言中则可以运用这个模式,这个模式是通过继承来实现的,而不是再根据接口来实现了。
让我们来了解一个更为真实的例子,首先,我们知道在早期的Java版本中存留着枚举器,而当下的版本中更多的是使用iterator来实现对元素的遍历,并且后者比前者多提供了一个remove()方法,在实际的工作中也会遇到这样的情况,需要将原来的遗留下的老代码实现对当下代码的支持,因此我们可以借用适配器模式来实现;
上面的类图可以看出,iterator接口中定义了三个方法,Enumeration中只有两个方法,那我们如何实现呢?
实现过程如下:
public class EnumerationIterator implements Iterator{
Enumeration enum;
public EnumerationIterator(Enumeration enum){
this.enum = enum;
}
public boolean hasNext(){
return enum.hasMoreElements();
}
public Object next(){
return enum.netElement();
}
public void remove(){
throw new UnsupportedOperationException();
}
}
可以看到,其它的方法可以对应起来,下一个元素,是否有其他元素的方法都可以实现对应。remove方法仅仅是抛出了一个异常,这是因为Enumeration接口中没有对应remove方法的接口定义,但是,这个类可以抛出UnsupportedOperationException,所以简单的将remove方法的作用作为跑出这个异常,而不是同iterator一样将元素进行删除。
几个设计模式之间的区分:
适配器模式和装饰者模式:装饰者模式是与类的责任或者说是功能,或者是过程中的增加功能有关的,当一些新的行为等需要增加事则可以使用装饰者模式,他可能是装饰了一个对应的行为,但他也可能是其它装饰者的一个装饰对象,这样同样也实现了从客户中解耦。适配器则是实现的接口转化而不是进行装饰。
外观模式和适配器模式:外观模式是接口的简化,如果暴露了很多接口,使用和管理起来很不方便,则可以通过外观模式来对接口进行转化。外观模式注重的是对接口的简化,而适配器模式则是注重接口的转化,但都能够实现从从客户中实现解耦。如果将这两个模式结合起来将会更加方便一些。
浅谈一下外观模式:
我们创建了一个接口简化而统一的类,用来包装子系统中一个或者是多个复杂得嘞,外观模式相当直接,很容易理解,但是这不会降低它的功能。其提供了一个统一的接口,用来访问子系统中的一群接口,外观定义了一个高层接口,让子系统更容易使用。
那如何不去影响太多的对象呢?
这里提供一个原则:就任何对象而言,在该对象的方法内,我们只应该调用属于一下范围的方法:
1.该对象本身
2.被当作方法的参数而传递进来的对象
3.此方法所创建或实例化的任何对象
4.对象的任何组件
如果我们从另一个调用中返回对象的方法会有什么坏处呢,这样做的话,相当于另一个对象的子部分发出请求,在这种情况下,原则要我们改为要求该对象为我们做出请求,这么一来,我们就不需要认识该对象的组件了。
其次,还要遵守另一个原则,叫做墨忒耳法则,但是这个法则也有缺点:这个原则减少了对象之间的以来,减少了维护成本,但是这样的话也会导致更多的包装类制造出来,这可能会导致复杂度和开发时间的增长,降低程序运行的性能。