title: Adapter模式
tag: 笔记 设计模式
![image-20231018141003528](https://i-blog.csdnimg.cn/blog_migrate/fa096ba1b9b80a137bac7774bccbfaab.png)
Adapter模式
在程序世界中,经常会存在现有的程序无法直接使用,需要做适当的变换之后才能使用的情况。这种用于填补“现有的程序”和“所需的程序”之间差异的设计模式就是Adapter
模式。
Adapter
模式也被称为Wrapper
模式。Wrapper
有“包装器”的意思,就像用精美的包装纸将普通商品包装成礼物那样,替我们把某样东西包起来,使其能够用于其他用途的东西就被称为“包装器”或是“适配器”。
Adapter模式有下面两种模式:
- 类适配器模式(继承实现)
- 对象适配器模式(委托实现)
下面是一个简单的示例。
使用继承的适配器
这个示例程序是一个会将输入的字符串显示为 (hello)
或者*hello*
的简单程序:
Banner
类:有两个方法对字符串做不同的处理。showWithParen
:将字符串用()
括起来。- showWithAster:将字符串用
**
包起来。
Print
接口:定义了两个抽象方法用不同的方式输出。printWeak
:弱化字符串显示(括号)printStrong
:强化字符串显示(星号)
现在我们要实现的就是使用Banner
类编写一个实现Print
接口的类。Banner
就类似我们现有的程序,而Print
接口就是我们的需求,现在我们需要在它们之前编写一个适配器(adapter)PrintBanner
来实现目标需求。
UML图如下:
Banner类
现有程序:
public class Banner {
private String s;
public Banner(String s){
this.s = s;
}
public void showWithParen(){
System.out.println(new StringBuilder().append("(").append(s).append(")").toString());;
}
public void showWithAster(){
System.out.println(new StringBuilder().append("*").append(s).append("*").toString());;
}
}
Print接口
我们要实现的需求:
public interface Print {
void printWeak();
void printStrong();
}
PrintBanner类
该类扮演适配器的角色,它继承了Banner
又实现了Print
接口。
public class PrintBanner extends Banner implements Print{
public PrintBanner(String s) {
super(s);
}
@Override
public void printWeak() {
showWithParen();
}
@Override
public void printStrong() {
showWithAster();
}
}
测试
public static void main(String[] args) {
Print print = new PrintBanner("Hello World");
print.printWeak();
print.printStrong();
}
输出:
(Hello World!)
*Hello World!*
这里使用Print
类型(接口)来表示PrintBanner
示例(实现)的目的是明确表示程序的意图:PrintBanner
类仅仅只使用Print
接口中的方法。因为在有些情况,适配器(PrintBanner
)中的方法会比需求接口(Print
)中的方法多。
使用委托的适配器
在上面的示例中,我们将适配器(PrintBanner
)继承了适配者(Banner
),在很多情况我们都可以使用委托代替继承的方法来将继承关系(is)更改为聚合关系(has)。
我们在PrintBanner
中添加Banner
实例的成员变量,并在其构造方法中执行初始化。然后我们通过Banner
来调用它的方法,这样就实现了与继承相同的作用。
UML图:
![image-20231018165003984](https://i-blog.csdnimg.cn/blog_migrate/0b4cd13ebf0c796496469c1f13691993.png)
修改为使用委托的适配器我们只需要修改适配器(PrintBanner)即可。
PrintBanner类
使用委托代替继承的方法,我们只需要实现Print
接口即可:
public class PrintBanner implements Print{
Banner banner;
public PrintBanner(String s) {
banner = new Banner(s);
}
@Override
public void printWeak() {
banner.showWithParen();
}
@Override
public void printStrong() {
banner.showWithAster();
}
}
测试
public static void main(String[] args) {
Print print = new PrintBanner("Hello World!");
print.printWeak();
print.printStrong();
}
输出:
(Hello World!)
*Hello World!*
与之前相同。
Adapter模式中登场的角色
在Adapter模式中下面几种角色:
Target(对象)
:该角色负责定义所需的方法。在示例程序中,由Print
接口扮演此角色。Client(请求者)
:该角色负责使用Target
角色所定义的方法进行具体处理。在实例程序中由测试程序扮演该角色。Adaptee(被适配)
:该角色是一个持有既定方法的角色。在示例程序中,由Banner类扮演此角色。Adapter(适配器)
:Adapter
模式的主人公。使用Adaptee角色的方法来满足Target角色的需求,这是Adapter模式
的目的,也是Adapter角色的作用。在实例程序中,由PrintBanner
扮演此角色。
类适配器的类图
对象适配器的类图
![image-20231018204111676](https://i-blog.csdnimg.cn/blog_migrate/a72a8b5b3d4721898762c398a74910f6.png)
拓展思路
什么时候使用Adapter模式
如果一个方法是我们需要的方法,我们为何不直接在当前类实现而是要使用Adapter
模式呢?
很多时候,我们并非从零开始编程,经常会用到现有的类。特别是当现有的类已经被充分测试过了,Bug很少,而且已经被用于其他软件之中时,我们更愿意将这些类作为组件重复利用。
Adapter
模式会对现有的类进行适配,生成新的类。通过该模式可以很方便地创建我们需要的方法群。当出现Bug时,由于我们很明确地知道Bug不在现有的类(Adaptee
角色)中,所以只需调查扮演Adapter
角色的类即可。这样一来,代码问题的排查就会变得非常简单。
扩展而不是修改
当我们想让现有的类适配新的接口时,我们常常会有修改原有的类来适配接口的想法,这样修改类可能会导致一些问题。
使用Adapter模式可以在完全不改变现有代码的前提下使现有代码适配于新的接口。
版本升级和兼容性
软件的生命周期总是伴随着版本的升级,在版本升级的时候就会伴随着版本兼容问题,很多时候我们都不能完全抛弃旧版本。这个时候我们就可以使用Adapter模式来同时维护旧版本和新版本。
我们可以让新版本扮演adaptee
模式而旧版本扮演Target
角色,然后编写一个扮演Adapter
的类,让它使用新版本的类来实现旧版本的方法:
![image-20231018212821394](https://i-blog.csdnimg.cn/blog_migrate/0a2a2df977df676343be3b7c8829a131.png)
相关的设计模式
Bridge
模式
Adapter
模式用于连接接口(API)不同的类,而Bridge
模式则用于连接类的功能层次结构与实现层次结构。
Decorator
模式
Adapter
模式用于填补不同接口(API)之间的缝隙,而Decorator
模式则是在不改变接口(API)的前提下增加功能。
t=“image-20231018212821394” style=“zoom:80%;” />
相关的设计模式
Bridge
模式
Adapter
模式用于连接接口(API)不同的类,而Bridge
模式则用于连接类的功能层次结构与实现层次结构。
Decorator
模式
Adapter
模式用于填补不同接口(API)之间的缝隙,而Decorator
模式则是在不改变接口(API)的前提下增加功能。