1.什么是适配器
适配器,这个概念挺好理解的,感觉就是个中介,两个不对付的物体A和B通过中介可以结合起来或者相适应,有点类似于居委会阿姨的感觉?哈哈哈,举个生活中的例子,iPhone推出新系列之后,圆头的耳机就无法插入iPhone手机了,必须得用扁平头的耳机,这个时候如果你不想再多花100rmb买个新耳机,那么你可以选择买一个转接头,将你的圆头耳机插入到转接口中,然后再将转接头插入到手机中,就可以实现圆头耳机插入到新系列iPhone了。再举个例子,美国电器 110V,中国 220V,将 110V 转化为 220V的这个工作我们就是交给适配器来完成的。
现在想想是不是JAVA 中的 jdbc也是这么个道理呢。
2.面向对象的适配器
假设Joe饲养了一群火鸡和鸭子。火鸡和鸭子都会叫而且也都会飞,只不过火鸡是咯咯叫,鸭子是呱呱叫,鸭子飞的比火鸡远很多。Joe时常将火鸡和鸭子给搞混了。
由于鸭子和火鸡有很多不同的种类,在这儿给出简单的接口,并用一个具体的类来说明下各自的动作(比如飞),尽管后面我们不一定能用到下面示范的具体类。
public interface Duck {
public void quack();//呱呱叫
public void fly();//飞
}
MallardDuck:绿头鸭
public class MallardDuck implements Duck{
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void fly() {
// TODO Auto-generated method stub
System.out.println("I'm flying");
}
}
Turkey:火鸡
public interface Turkey {
public void gobble();
public void fly();
}
WildTurkey:野生火鸡
public class WildTurkey implements Turkey{
@Override
public void gobble() {
// TODO Auto-generated method stub
System.out.println("Gobble");
}
@Override
public void fly() {
// TODO Auto-generated method stub
System.out.println("I'm flying a short distance");
}
}
现在Joe需要很多鸭子,但是鸭子的数量无法满足需求,于是Joe打算用火鸡冒充鸭子,但是鸭子和火鸡终归是两个不同的物种,我们设计的接口也不同,肯定不能直接来用,这个时候我们就需要写个适配器来完成这个伪装过程。
public class TurkeyAdapter implements Duck{
Turkey turkey;
//既然用火鸡充当鸭子,显然在实现Duck接口方法的基础上,我们让本次伪装的对象参与进来
//所以在构造方法中需要实例化Turkey
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
@Override
public void quack() {
// TODO Auto-generated method stub
this.turkey.gobble();
}
@Override
public void fly() {
// TODO Auto-generated method stub
for(int i=0;i<3;i++) {
this.turkey.fly();
}
}
}
基本工作已经完成了,现在来测试一下。
public class DuckTestDrive {
public static void main(String[] args) {
//真正的鸭子--绿头鸭
Duck duck=new MallardDuck();
//真正的火鸡--野生火鸡
Turkey turkey=new WildTurkey();
//ok,现在是我们伪装出来的鸭子,实质上它还是个火鸡,我们的任务是做一点工作让它完成伪装的过程
//我们通过刚刚new的TurkeyAdapter去完成这个过程的转换
Duck fakeduck=new TurkeyAdapter(turkey);
System.out.println("-------The Duck show time-------");
duck.fly();
duck.quack();
System.out.println("-------The Turkey show time-------");
turkey.fly();
turkey.gobble();
System.out.println("-------The FakeDuck(TurkeyAdapter) show time-------");
fakeduck.fly();
fakeduck.quack();
}
}
打印结果:
-------The Duck show time-------
I'm flying
Quack
-------The Turkey show time-------
I'm flying a short distance
Gobble
-------The FakeDuck(TurkeyAdapter) show time-------
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
Gobble
3.过程分析
1.客户(鸭子)通过目标接口(鸭子接口)调用适配器(TurkeyAdapter)的方法对适配器发出请求
2.TurkeyAdapter适配器使用被适配者接口(Turkey)把请求转换成被适配者的一个或多个调用接口。
3.客户(鸭子)收到调用的接口,但并未察觉到这一切是适配器在发挥作用。如果仔细想一想,其实我们可以发现,虽然我们用火鸡充当鸭子混在鸭子群体里,但是实际上鸭子根本不知道火鸡的存在(想想Duck和Adapter中Turkey的关系),所以客户和被适配者实际上是解耦的,因为一个不知道另一个。
适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本就扣不兼容的类可以合作无间。
有关设计模式的书中对该模式定义如上。
现在大概基本上了解了适配器,那我们再进一步的思考些问题:
1.一个适配器只能封装一个类嘛?
大多数的情况是这样的,适配器模式的定义刚刚我们说的是将一个类的接口,转换成客户期望的另一个接口
。但显然生活中的例子怎么可能像刚才举的这么简单,所以一个适配器包装多个被适配者就涉及到另一种模式,即外观模式。但是注意,这两个模式不要混为一谈。
2.有时候你可以让适配器实现两个接口,从而创建双向适配器,这样如果系统中新旧并存需要适配的话,或许更方便一些。
4.类适配器与对象适配器
类适配器是继承被适配者和目标类,而对象适配器是使用组合来适配被适配者。之前我们举的例子是对象适配器的。
还是以之前的鸭子火鸡为例,适配器Adapter要实现Duck接口,这样我们就能继续访问当前接口Duck中的方法(因为最终我们是把伪装好的火鸡当作鸭子来看,自然是要调用鸭子的方法和属性啦),然后再继承接口Turkey的实现类野生火鸡WildTurkey,这样我们可以在适配器Adapter中访问接口Turkey的方法了,这时我们在适配器Adapter中Duck接口的方法中直接引用WildTurkey中的合适方法,这样就完成了一个简单的类适配器。
类适配器:ObjectAdapter
public class ObjectAdapter extends WildTurkey implements Duck{
@Override
public void quack() {
gobble();
}
@Override
public void fly() {
for(int i=0;i<3;i++)
{
super.fly();
}
}
}
测试方法:ObjectAdapterTest
public class ObjectAdapterTest {
public static void main(String[] args) {
System.out.println("----------类适配器-----------------");
System.out.println("-----FakeDuck show time------");
Duck duck=new ObjectAdapter();
duck.quack();
duck.fly();
}
}
打印结果:
--------------类适配器------------
---------FakeDuck show time------
Gobble
I'm flying a short distance
类适配器模式注意事项和细节,以鸭子火鸡为例:
-
Java是单继承机制,所以类适配器需要继承被适配者的具体类(类WildTurkey)这一点算是一个缺点, 因为这要求Duck(目标)必须是接口,有一定局限性(因为一个已经继承了,由于单继承,无法继承第二个,显然只能实现接口了)。
-
火鸡(本文中是WildTurkey)的方法在Adapter中都会暴露出来,也增加了使用的成本。
-
由于其继承了火鸡类,所以它可以根据需求重写火鸡类(本文中是WildTurkey)的方法,使得Adapter的灵活性增强了。
关于对象适配器:
- 对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。 根据合成复用原则,使用组合替代继承, 所以它解决了类适配器必须继承被适配者(本文中是Turkey)的局限性问题,也不再要求目标(在本文中Duck)必须是接口。
- 使用成本更低,更灵活。