本文基于《Head First 设计模式》对其中的适配器模式予以总结
适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
问题描述:假设有一个软件系统,你希望它能和一个新的厂商类库搭配使用,但是新厂商设计出来的接口与旧厂商不同。而你又不想改变现有的代码,该怎么办?
答案:写一个适配器类,将新厂商接口转换成你期望的接口。
下面先利用适配器完成一个简单的转换
假设Duck接口是我们期望的接口
public interface Duck{
public void quack();
public void fly();
}
绿头鸭是Duck的子类,它可以实现Duck接口具备具体的能力
public class MallardDuck implements Duck{
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void fly() {
System.out.println("I'm flying");
}
}
现在新厂商设计出来的并不是Duck接口,而是Turkey接口,那么怎么像之前使用Duck接口那样去使用这个新的接口呢?
这是新做的Turkey接口
public interface Turkey{
public void gobble();//火鸡不会quack,只会gobble
public void fly();//火鸡会飞,但是飞不远
}
这是一个野鸡WildTurkey去实现了Turkey接口
public class WildTurkey implements Turkey{
public void gobble(){
System.out.println("Gobble gobble");
}
public void fly(){
System.out.print("I'm flying a short distance");
}
}
假设现在鸭子不够,需要用一部分火鸡去冒充鸭子,这就需要对火鸡进行一些处理,让它的行为接近鸭子。这就用到了火鸡适配器,它可以让火鸡与一般鸭子一样,假装成一只鸭子去做事情。
这是火鸡适配器类
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey){
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
@Override
public void fly() {
for (int i = 0;i<5;i++){//火鸡飞得不远,因此得多飞几段才能让行为看起来像鸭子
turkey.fly();
}
}
}
下面是测试类
public class DuckTestDrive {
public static void main(String[] args){
System.out.println("....................the turkey says...............................");
WildTurkey wildTurkey = new WildTurkey();
wildTurkey.gobble();
wildTurkey.fly();
System.out.println(".....................the turkey adapter says...................");
Duck turkeyAdapter = new TurkeyAdapter(wildTurkey);
turkeyAdapter.quack();//wildTurkey装进了适配器中,因此它在外界看来就是一只鸭子
turkeyAdapter.fly();//它用的是鸭子的方法,但是其实真正完成动作还是用的turkey的动作累加而来
}
}
由此可见,火鸡本来没有鸭子的quack()方法,它的fly()方法也飞不远,但是将火鸡装进适配器对象之后,适配器对象可以对火鸡进行适当的转换,让火鸡可以完成鸭子应有的quack()和fly()方法。
下面是书中适配器模式的解析图
这是类图
总结
适配器模式有良好的OO设计原则:使用对象组合,用修改的接口包装被适配者,被适配者的任何子类,都可以搭配适配器使用。
[注]这个模式是将客户和接口绑定起来,而不是和实现绑定起来。可以使用多个适配器,每个都负责转换不同组的后台类。
什么时候该用适配器模式?
当需要使用一个现有的类但是它的接口并不符合你的需要的时候。适配器可以改变接口以满足用户的期望。
下面介绍一个现实中的适配器
先介绍两个Java中的新旧两个接口
1、早期的枚举器
2、新版本的迭代器
现在我们遇到的情况是:一些旧代码暴露的是枚举器接口,但是我们希望在新代码中只用迭代器。为了让这新旧两个接口能完美衔接,我们就得设计一个适配器。
现在我们就来将枚举器接口适配为迭代器接口
从上图可以看出,要想适配两个接口,主要是要解决remove()方法的映射。
很不幸,枚举是一个“只读”接口,适配器无法完成一个具有实际功能的remove()方法,只能抛出一个Java设计者提前准备的运行时异常。
因此,适配器的remove()方法只能这样写:
public void remove(){
throw new UnsupportedOperationException();
}
[注] 虽然Java已经采用了迭代器,但还是有相当多的遗留“客户代码”,依赖于枚举接口,所以利用适配器将迭代器转换成枚举,其实还是很有用的技巧。
装饰者模式和适配器模式的比较
装饰者模式可以在不改变原有代码的前提下,将对象包装进装饰者,对对象的成员进行一些装饰性的操作,即:将新行为或者责任加入给待装饰的对象。
适配器模式则是将对象包装进适配器中,适配器相当于一个转接器,将被适配对象的成员(方法或者变量)适配成另外一种形式,使其适用于另一个接口,也不需要改变原有代码。
由此可见,装饰者模式和适配器模式看起来做的事情十分相似,但是它们的意图是完全不同的。装饰者模式强调的是给被装饰者添加新的行为,而适配器模式则是强调做转换工作,让客户能兼容新的类库接口。
外观模式
外观模式提供了统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用
外观模式也是一种改变接口的模式,但是它改变接口是为了简化接口,它将一个或多个类的复杂的一面隐藏在背后,只显露出一个干净美好的外观。
从一个问题着手:
假设你想在家里弄一个家庭影院,所有东西都准备好了,现在你想看电影。现在才发现需要做很多事情:打开爆米花机、把灯光调暗、把屏幕放下来、把投影机打开、打开功放设置为DVD模式、打开DVD...
光从这看就涉及到了6个不同的类,而且看完电影还要把这一切又关掉,这简直太麻烦了。
书中这样用外观模式解决:
设计一个高层类WatchMovie,将看电影需要做的事(子系统完成的事)组合到这个外观类中,然后要看电影的时候,只需要按下WatchMovie这个按键就可以完成所有操作,而不是像开始那样花大量的时间亲手去操作那么多的不用类。
书中在这一节还提出了一个新的设计原则:
最少知识原则:只和你的密友谈话
当你正在设计一个系统,不管是任何对象,都要注意它所交互的对象有哪些,并注意它和这些类是如何交互的。
这个原则告诉我们:不要让太多的类耦合在一起,以免修改系统中的一部分会牵连其他部分,如果许多类之间都相互依赖,这必定是一个易碎的系统,维护成本也高。
书中这样说到:
对于任何对象,在该对象的方法内,我们只应该调用属于以下范围的方法:
1、该对象本身
2、被当作方法的参数而传进来的对象
3、此方法所创建或实例化的对象
4、对象的任何组件(与它是has-a关系的对象)
也就是说:一件事能用一个对象完成就尽量用一个,别让你的朋友去另外找人帮忙,因为你和你朋友的朋友不一定很熟,当遇到问题时,你可能不能直接联系到他
什么时候用外观模式?
当需要简化并统一一个很大的接口或者一群复杂的接口的时候,使用外观。它将用户从一个复杂的子系统中解耦。