定义
适配器大家都不陌生,每个国家规定的标准电压不同,在我国生产的电脑如何能在美国充电使用,答案是使用适配器,即引入一个电源适配器。
在软件开发中,有时也存在类似这种不兼容的情况,我们也可以像引入一个电源适配器一样引入一个称之为适配器的角色来协调这些存在不兼容的结构,这种设计方案即为适配器模式。
适用场景
1、系统需要使用现有的类,而此类的接口不符合系统的需要。
2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
UML类图
适配器模式包含如下几个角色:
Target:目标抽象类
Adapter:适配器类
Adaptee:适配者类
Client:客户类
案例
假设要为某个系统添加日志管理的功能,开发人员按照需求,将日志保存于文件当中。 为了方便,我们大大简化了系统。
/** * 日志文件操作接口 * */
public interface LogFileOperateApi {
/** * 从文件里读取日志 */
public void getFileLog();
/** * 将日志写入文件当中去 */
public void addFileLog();
/** * 删除一条日志 */
public void removeFileLog();
}
/** * 文件操作接口的实现类 * */
public class LogFileOperate implements LogFileOperateApi {
/** * 从文件里读取日志 */
@Override
public void getFileLog() {
System.out.println("读取文件日志");
}
/** * 将日志写入文件当中去 */
@Override
public void addFileLog() {
System.out.println("增加文件日志");
}
/** * 删除一条日志 */
@Override
public void removeFileLog() {
System.out.println("删除文件日志");
}
}
客户端调用如下:
/** * 测试 * */
public class Client {
public static void main(String[] args) {
// 文件日志
LogFileOperateApi fileLog = new LogFileOperate();
fileLog.addFileLog();
fileLog.getFileLog();
fileLog.removeFileLog();
}
}
输出结果如下:
增加文件日志
读取文件日志
删除文件日志
此时,假设来了一个新的需求,想要使用数据库日志管理,而这套管理程序代码已经由他人写好,但是发现其代码的接口与现有文件日志系统的接口不同,如下所示
/** * 操作数据库日志 * */
public interface LogDBOperateApi {
/** * 从文件里读取日志 */
public void getDBLog();
/** * 将日志写入文件当中去 */
public void addDBLog();
/** * 删除一条日志 */
public void removeDBLog();
}
public class LogDBOperate implements LogDBOperateApi {
/** * 从文件里读取日志 */
@Override
public void getDBLog() {
System.out.println("读取数据库日志");
}
/** * 将日志写入文件当中去 */
@Override
public void addDBLog() {
System.out.println("增加数据库日志");
}
/** * 删除一条日志 */
@Override
public void removeDBLog() {
System.out.println("删除数据库日志");
}
}
那么现在带来的问题是如何转换成使用数据库管理日志。 当然最笨的办法是直接修改客户端调用代码,但是这在有时却是不可行的,假设调用日志的代码分散中系统各处,那么修改这样的代码简直就是灾难。
此时可以使用适配器模式,开发一个适配器实现LogFileOperateApi
接口,在该接口中调用LogDBOperateApi
的方法,相当于间接调用。在客户端调用时,使用适配器去调用即可,不需要修改原有的代码逻辑。满足了设计模式中的”开闭原则“。
public class Adapter implements LogFileOperateApi {
private LogDBOperateApi adaptee;
public Adapter(LogDBOperateApi adaptee) {
this.adaptee = adaptee;
}
/** * 从文件里读取日志 */
@Override
public void getFileLog() {
adaptee.getDBLog();
}
/** * 将日志写入文件当中去 */
@Override
public void addFileLog() {
adaptee.addDBLog();
}
/** * 删除一条日志 */
@Override
public void removeFileLog() {
adaptee.removeDBLog();
}
}
此时客户端调用只要进行如下修改即可:
/** * 测试 * */
public class Client {
public static void main(String[] args) {
//改为数据库日志
LogFileOperateApi fileLog = new Adapter(new LogDBOperate());
fileLog.addFileLog();
fileLog.getFileLog();
fileLog.removeFileLog();
}
}
总结
优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
- 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
- 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
缺点
- 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
- 对于类适配器而言,由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。