设计模式-02 适配器模式
目录简介
当把手机或笔记本电脑插到插座上充电时,适配器就发挥了功效。适配器的作用是使产生 220 V 的插座和需要 4 V 的移动设备能够相互适应,完美地发挥二者的功效。类似地,通过在程序中使用适配器模式,可以做到不兼容的接口在一起正常工作,而填补现有程序和所需程序之间差异就是 Adapter 的工作:
Adapter 模式有两种:
- 类适配器模式(基于继承)。
- 对象适配器模式(使用委托)。
例子
使用继承
类图
代码
public interface Print {
void printWeak();
void printStrong();
}
public class Banner {
private String str;
public Banner(String str) {
this.str = str;
}
public void showWithParen() {
System.out.printf("(%s)\n", str);
}
public void showWithAster() {
System.out.printf("*%s*\n", str);
}
}
public class PrintBanner extends Banner implements Print {
public PrintBanner(String str) {
super(str);
}
@Override
public void printWeak() {
showWithParen();
}
@Override
public void printStrong() {
showWithAster();
}
}
使用委托
类图
代码
public abstract class Print {
public abstract void printWeak();
public abstract void printStrong();
}
public class Banner {
private String str;
public Banner(String str) {
this.str = str;
}
public void showWithParen() {
System.out.printf("(%s)\n", str);
}
public void showWithAster() {
System.out.printf("*%s*\n", str);
}
}
public class PrintBanner extends Print {
private Banner banner;
public PrintBanner(String str) {
this.banner = new Banner(str);
}
@Override
public void printWeak() {
banner.showWithParen();
}
@Override
public void printStrong() {
banner.showWithAster();
}
}
分析
对于第一种情况:
- 我们的目标,就是 Print 接口规定的内容,也就是它含有的两个方法:
printWeak()
和printStrong()
,对应弱提示打印和强提示打印。 - Banner 则是被适配的对象,它能够按照一定的规则打印字符串,但是这两个功能还不满足 Print 类汇总规定的类容。
- PrintBanner 就是适配器,它实现了 Print 接口,也就是说它能完成目标工作;它又继承了 Banner,代表它能够使用被适配对象的功能(方法)。
对于第二种情况
- 我们的目标,或者说是要适配成的样子,就是 Print 类的内容,也就是它含有的两个方法:
printWeak()
和printStrong()
,对应弱提示打印和强提示打印。 - Banner 则是被适配的对象,它能够按照一定的规则打印字符串,但是这两个功能还不满足 Print 类汇总规定的类容。
- PrintBanner 就是适配器,它继承自 Print,也就是说它适配目标,在其中它又将 Banner 组合成了自己的字段,为了完成 Print 定义的目标,它使用了 banner 字段的方法。
在两种情况下,我们都只需要调用 Print 接口中的方法,就可以使用老旧的被适配者来实现新的功能,注意,我们使用到了老旧的被适配者,并不是将它们剔除掉了。这也是 OCP 原则的体现,为了实现新的功能,我们只是增加,而不是修改原有的代码。
总结
通用结构
基于继承:
基于委托:
如何选择
在考虑灵活和拓展性的前提下,组合委托必然是优于继承的,后者是一种高耦合的行为。
何时使用
适配器模式适用于非常多的情境,比如你在网上找到了一段代码,并且想要在其中添加一些额外的功能,修改源代码似乎不是一种明治的行为,甚至有些时候会构成法律问题。适配器模式就能够帮助你,将这段代码以你规定的方式(Target)引入到项目中。
如何使用
直接引入这些工具类似乎是一个更加简单、愉悦的选择,何必大费周章地衍生出那么多接口或者抽象类,再通过进行复杂的继承和实现才引入它们。
在我看来,直接引入是一种极具侵略性的行为,是不符合预期的行为。系统或者某个模块的功能应该由接口来定义,如果直接引入工具类,无异于在自己的系统中增加了一个未曾预想过的接口,这是对工程原则的违反,是一种具有风险的行为。而通过适配器模式将它转换,隐匿到已定义的接口中则是一种归化,温驯而且符合预期,符合工程原则。