Adapter模式
上图为笔记本电脑的电源适配器,笔记本需要12~20v直流电压供电,但是插座出来的电压为220v交流电。笔记本电源适配器的作用就是将家庭用的220v交流电压转换成笔记本可以使用的20v直流电。这就是适配器的工作,它位于实际情况与需求之间,填补两者之间的差异。适配器模式起到的作用和适配器起到的作用是一样的。
适配器模式可以分为两种:
- 类适配器
类适配器中,Adapter与Adaptee是继承关系 - 对象适配器
对象适配器中,Adapter与Adaptee是委托关系
接下来,通过示例程序,我们来看看这两种适配模式分别是什么
类适配器示例程序
示例程序是一段会将输入的字符串Hello
显示为(Hello)
或者是*Hello*
的简单程序
目前我们有Banner类,Banner类中有将字符串用括号括起来的showWithParen方法和将字符串用*号括起来的showWithAster方法。这个类的角色就是Adaptee也就是被适配的类。
假设Print接口中声明了两种方法,即弱化字符串显示(加括号)的printWeak方法,和强调字符串(加星号)的printStrong方法。
我们现在要做的事情就是做一个Banner类的适配器,为Banner类编写一个实现了Print接口的类,这个类继承了Banner类,通过Print接口完成Banner类中showWithParen和showWithAster的功能。
假设这样一个适配器我们称作PrintBanner类。
使用类适配器模式的UML图如下:
程序如下:
Banner类
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 + "*");
}
}
Print接口
public interface Print{
public abstract void printWeak();
public abstract void printStrong();
}
PrintBanner类
public PrintBanner extends Banner implements Print{
public PrintBanner(String string){
super(string);
}
public void printWeak(){
showWithParen();
}
public void printStrong(){
showWithAster();
}
}
Main程序
public class Main{
public static void main(String[] args){
Print p = new PrintBanner("Hello");
p.printWeak();
p.printStrong();
}
}
对象适配器示例程序
依旧使用上面的例子。对于类适配器来说,我们使用了继承的思想实现了适配,对于对象适配器来说,则是委托。
在类适配器模式中Print作为一个接口,但是在对象适配器这个例子中,假设Print则作为一个抽象类的存在。那么PrintBanner适配器则需要继承Print类;因为Java中无法多继承,所以这里面PrintBanner类无法同时继承Print类和Banner类,不过可以在适配器PrintBanner类中使用Banner类的对象,作为它的字段。这样当PrintBanner类的printWeak被调用的时候,并不是PrintBanner类自己进行处理的,而是将其委托给了Banner类的实例调用了showWithParen方法。
对象适配器模式的UML图如下:
相较于类适配器模式来说,对象适配器的示例程序改动如下:
Print类
public abstract class Print{
public abstract void printWeak();
public abstract void printStrong();
}
PrintBanner类
public class PrintBanner extends Print{
private Banner banner;
public PrintBanner(String string){
this.banner = new Banner(string);
}
public void printWeak(){
banner.showWithParen();
}
public void printStrong(){
banner.showWithAster();
}
}
Adapter模式中的角色
Target
该角色负责定义所需要的方法,示例程序中Print接口(类适配)和Print类(对象适配)扮演这个角色。
Client
Client负责使用Target角色所定义的方法进行具体处理。示例程序的Main类扮演这个角色
Adaptee
被适配的类,示例程序中的Banner类扮演这个角色。
Adapter
适配器,Adapter角色的方法用来满足Target角色的需求,示例程序中PrintBanner类扮演这个角色。
类适配器模式的UML图
对象适配器模式的UML图
关于Adapter模式的思考
为什么需要适配器模式
实际发开中,如果有一个类库具有很多方法,但我们只需要其中一两个,那么就可以写一个适配器。Adapter模式会对现有的类进行适配,生成新的类,然后我们用我们创造的新的类即,适配器去调用这一两个我们需要的方法。要是出了Bug,由于我们很明确地知道Bug不在现有的类库(Adaptee角色)中,就可以直接去Adapter里面找,而不是去翻那个类库,这样一来,代码问题的排查就会变得非常简单。
为什么需要Target角色
在示例程序中,我们使用了这样的写法
Print p = new PrintBanner("Hello");
为什么不去直接这样写呢
PrintBanner p = new PrintBanner("Hello");
原因在于,本示例程序中,PrintBanner类和Print接口对外提供的方法是相同的。但是在大部分情况下,PrintBanner类中的方法可能会比Print接口对外提供的方法多的多。通过将对象保存在Print类型的变量中使用,可以明确地表明程序的意图。