适配器模式
适配器(Adapter)模式也称Wrapper模式,Wrapper有“包装器”的意思,就像用精美的包装纸将普通的商品包装成礼物那样,替我们把某样东西包装起来,使其能够用于其他用途的东西被称为“适配器”或是“包装器”。
在我们程序中,经常会存在现有的程序无法直接使用,需要做适当的变换后才能使用的情况。用于填补“现有的程序”和“所需的程序”之间差异的设计模式就是适配器。
举个栗子
左边是一台笔记本,右边是一个家庭电源,我们都知道,家庭电源是220V的交流电压,而笔记本工作的电压是12V直流电,那么如何如笔记本正常工作呢?通常,我们会用到AC适配器,将家庭用的220V交流电转化成我们所需要的直流12V电压。这就是适配器的工作,它位于实际情况和需求之间,填补两者之间的差异。
适配器模式有以下两种:
-
类适配器模式(使用继承的适配器)
-
2.对象适配器模式(使用委托的适配器)
使用继承的适配器
Banner类
package adapter;
public class Banner {
private String string;
public Banner(String string){
this.string=string;
}
public void showWithParen(){
System.out.println("("+string+")");
}
public void showWithAster(){
System.out.println("*"+string+"*");
}
}
目前Banner类(Banner有广告横幅的意思)中,有将字符串用括号括起来的showWithParen()方法,和将字符串用*号括起来的showWithAster()方法。我们假设这个Banner类类似于前文中的“交流电220V”现有的程序”。
Print接口
package adapter;
public interface Print {
public abstract void printWeek();
public abstract void printStrong();
}
Print中有两个抽象方法,printWeek()和printStrong()。我们假设这个接口类似于前文中的“直流电12V”的“所需程序”。
现在需要做的事情是使用Banner类编写一个实现了Print接口的类,也就是说要做一个“交流电220V”转换成“直流电12V”的适配器。
需求:通过Print类的printWeek()和printStrong()输出(Hello)和*Hello*
PrintBanner类
package adapter;
public class PrintBanner extends Banner implements Print{
public PrintBanner(String string) {
super(string);
}
@Override
public void printWeek() {
super.showWithParen();
}
@Override
public void printStrong() {
super.showWithAster();
}
}
PrintBanner扮演适配器这角色,该类继承了Banner类并实现了Print接口。PrintBanner类使用了showWithParent方法实现了printWeek,使用showWithAster方法实现了printStrong。这样,PrintBanner类就具有了适配器的功能了。
Main类
package adapter;
//使用继承的适配器
public class Main {
public static void main(String[] args) {
Print print=new PrintBanner("hello");
print.printWeek();
print.printStrong();
}
}
Main类的作用是通过适配器角色的PrintBanner类来输出指定字符串格式。
运行结果:
(hello)
*hello*
请注意,这里我们将PrintBanner类的实例保存在了Print类型的变量中。在Main类中,我们是使用Print接口调用printWeek方法和printStrong方法来进行编程的。对于Main类的代码而言,Banner类的showWithParent方法和showWithAster方法被完全隐藏起来了。这就好像笔记本电脑只要在直流电12V下就能正常工作,但它并不知道这12V的电压是由适配器将220V交流电转化成的。
使用委托的适配器
“委托”通俗点讲就是“交给其他人”。比如,当我们无法出席一个重要会议时,可以写一份委任书,说明一下“我无法出席会议,安排XXX代我出席”。委托和委任的意思是一样的。在Java语言中,委托就是指将某个方法中的实际处理交给其他实例的方法。
Banner类
package adapter2;
public class Banner {
private String string;
public Banner(String string){
this.string=string;
}
public void printWithParen(){
System.out.println("("+string+")");
}
public void printWithAster(){
System.out.println("*"+string+"*");
}
}
在这Banner类内容没有任何改变。
Print抽象类
package adapter2;
public abstract class Print {
public abstract void printWeek();
public abstract void printStrong();
}
这里需要注意一点,Print不再是个接口,而是变成了一个抽象类。其中,Print中有两个抽象方法没有变,仍然是printWeek()和printStrong()。
PrintBanner类
package adapter2;
public class PrintBanner extends Print {
private Banner banner;
public PrintBanner(String string){
banner=new Banner(string);
}
@Override
public void printWeek() {
banner.printWithParen();
}
@Override
public void printStrong() {
banner.printWithAster();
}
}
PrintBanner类的banner字段中保存了Banner类的实例。该实例是在PrintBanner类的构造函数中生成的。然后,printWeek方法和printStrong方法会通过banner字段调用Banner类的showWithParent和showWithAster方法。
与之前的使用继承适配器调用父类中继承的showWithParent和showWithAster方法不同,这次我们通过字段来调用这两个方法。
这样就形成了一种委托关系,当PrintBanner类的printWeek被调用时,并不是PrintBanner类自己进行处理,而是将处理交给了Banner实例的showWithParent方法。
Main类
package adapter2;
//使用委托的适配器
public class Main {
public static void main(String[] args) {
Print print=new PrintBanner("hello");
print.printWeek();
print.printStrong();
}
}
Main类和之前的也完全一样。
运行结果:
(hello)
*hello*
适配器模式中的角色
在适配器模式中有以下角色。
- Target(对象)
该角色负责定义所需的方法。即让笔记本电脑正常工作所需的直流12V电。在程序中,由Print扮演此角色。
- Client(请求者)
该角色负责使用Target角色所定义的方法进行具体处理。即12V直流电所驱动的笔记本电脑。在程序中,由Main类扮演此角色。
- Adaptee(被适配)
Adaptee是一个持有既定方法的角色。即交流电220V。由Banner扮演此角色。
- Adapter(适配)
Adapter角色的方法来满足Target角色的需求,这是Adapter模式的目的,也是Adapter角色的作用。Adapter角色就是将交流电220V转换成直流电12V的适配器。在程序中,由PrintBanner类扮演此角色。
什么时候使用Adapter模式
很多时候,我们并非从零开始编程,经常会用到现有的类。而且现有的类已经经过充分测试过,bug很少,而且已经被用在其他软件中,我们更愿意将这些类作为组件重复利用。
Adapter模式会对现有的类进行适配,生成新的类。通过该模式可以很方便地创建我们需要的方法群。当bug出现时,我们明确的知道bug不在现有的类中,所以我们只需调查扮演Adapter角色的类即可。
当Adapter角色和Target角色功能完全不同时,Adapter模式是无法使用的。就像我们无法用220V的交流电压让自来水出水一样。