如果我们有一个方块,有一个圆孔,并且方块的外接圆半径大于圆孔的半径,现要求在不改变这两者自身尺寸的情况下,将这两者作为一个整体。按理说,因为方块的外接圆半径大于圆孔,硬塞是塞不进去了。那么我们可以换种思路,就是设计一个物体,既要能够和圆孔合为一个整体,又能够和方块合成一个整体,就像我们手机常用的转接口一样,这样三者合为一体,那么方块和圆孔间接的合为一体了,而且就算两者发生改变了,也互不干扰,只需要修改转接口即可。这种情况也就是我们今天要讲的适配器模式会遇到的场景。通过上述例子,我们也明白了适配器模式的作用,就是相当于生活中常用的转接口的作用。适配器模式将一个类的接口,转换成客户期望的另一个接口;适配器让原本接口不兼容的类可以合作无间。
1.适用性与优缺点
1.适用性
a.你想使用一个已经存在的类,而它的接口不符合你的要求
b.你想创建一个可以复用的类,该类可以与其它不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
c.你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它们的父类接口。
2.优点
a.更好的复用性
b.更好的扩展性
3.缺点
过多的使用适配器会让系统紊乱,看到调用的是A接口,其实内部适配成了B接口的实现,透明性较低。
2.示例讲解
适配器模式分为对象适配器和类适配器,前者使用接口形式,适配器通过组合的形式将请求传送给被适配者;而后者适配器继承了目标类以及被适配者。一般情况下,我们会使用对象适配器,更符合我们的设计原则,也体现了多态的特点。接下来我们讲的示例就是对象适配器。其实就是对象适配器的原理大致是:适配器实现目标接口,并将被适配者对象传入适配器,并且通过目标类的方法封装被适配者的方法(即在目标类方法中调用被适配者的方法),这样外部体现的是目标对象的特征,而看不到被适配者的具体方法。
我想大家都听过指鹿为马的故事吧,那么我们今天就来举一个类似的实例,就是将火鸡接口变为鸭子接口。那么我们就只需要让适配器实现鸭子接口,然后将火鸡对象传入适配器中,然后在实现鸭子接口的方法中调用火鸡相应的方法,这样这个适配器就成了鸭子对象。
首先定义一个Duck接口,该接口有表示鸭子Duck特性的两个方法,quack()和fly(),并且实现一个Duck类:
package adapterpattern;
public interface Duck {
public void quack();
public void fly();
}
package adapterpattern;
public class MallardDuck implements Duck {
@Override
public void quack() {
// TODO Auto-generated method stub
System.out.println("Quack");
}
@Override
public void fly() {
// TODO Auto-generated method stub
System.out.println("I'm flying");
}
}
package adapterpattern;
public interface Turkey {
public void gobble();
public void fly();
}
package adapterpattern;
public class WildTurkey implements Turkey {
@Override
public void gobble() {
// TODO Auto-generated method stub
System.out.println("Gobble gobble");
}
@Override
public void fly() {
// TODO Auto-generated method stub
System.out.println("I'm flying a short distance");
}
}
接着要把火鸡类变成鸭子类,那就是引出适配器部分,首先这个适配器要实现目标接口,即Duck接口,并且在该类中以组合形式声明被适配者对象,便于对象的传入,以下是适配器的具体实现。
package adapterpattern;
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey)
{
this.turkey=turkey;
}
@Override
public void quack() {
// TODO Auto-generated method stub
turkey.gobble();
}
@Override
public void fly() {
// TODO Auto-generated method stub
for(int i=0;i<5;i++)
{
turkey.fly();
}
}
}
由上面适配器类可以看出,该适配器类实现了Duck接口,并将接口中的quack()方法和fly方法实现,并且将火鸡类中方法在duck类的方法中调用,相当于将火鸡对象的方法进行了封装,外界看到的只是鸭子的方法,实现了指鹿为马的效果。
接下来是客户端的测试程序:
package adapterpattern;
public class DuckTestDrive {
public static void main(String[] args) {
// TODO Auto-generated method stub
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();
}
}
运行程序,结果如下:
The Turkey says...
Gobble gobble
I'm flying a short distance
The Duck says...
Quack
I'm flying
The TurkeyAdapter says...
Gobble gobble
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
以上第一个结果是属于火鸡类的结果,第二个结果是鸭子类的结果,第三个是适配器的结果。我们发现适配器的输出结果是表现的火鸡的特征,但它本身属于鸭子类,即鸭子表现出了火鸡的特征。这就是适配器的功能。
适配器的核心就是适配器实现目标接口,并将被适配者对象传入适配器,并在适配器类中封装被适配者的方法,使其看起来跟目标对象无异,但是内部具体表现却是被适配者的。
Spring中也有使用到适配器模式的,AOP模式中的
Advice(通知)的类型有:BeforeAdvice、AfterReturningAdvice、ThreowSadvice的。
在每个类型Advice(通知)都有对应的拦截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor。Spring需要将每个Advice(通知)都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对Advice进行转换。在这里,目标对象是对应的拦截器,被适配者是Advice,所以适配器模式可以将Advice封装成对应的拦截器。