- 说到适配器模式相信大多数人肯定听过电源转换头的故事,这里我们再重复一遍:
假如你到国外去,假如那个地方电源都是380v的,而你的手机充电器只能接220v的交流电,直接使用380v的会把手机充电器或者手机烧掉,那这时候怎么办呢?你需要准备一个380转220的电压转换器,其实就是变压器,把电源变成220v的就可以了;
适配器的道理很简单,但是具体到java代码怎样实现呢?
比如某公司做了一款公司内部使用的日志工具,可以把日志写入到文件保存,我们主要讨论设计模式,具体的写文件代码这里只使用控制台输出代替,第一版代码如下:
首先定义操作文件的接口
public interface LogToFile {
void write(String log);
void delete(String log);
String read();
}
实现类:
public class LogToFileImpl implements LogToFile {
@Override
public void write(String log) {
System.out.println("日志写到文件");
}
@Override
public void delete(String log) {
System.out.println("日志从文件删除");
}
@Override
public String read() {
System.out.println("从文件读取日志");
return "";
}
}
客户端使用
LogToFile logToFile = new LogToFileImpl();
logToFile.write("error");
String read = logToFile.read();
logToFile.delete("aaa");
这一版接口定义了读文件read、写文件write、从文件删除delete的方法
后来由于公司日志系统升级,新系统需要把日志保存到数据库中(我们只是举个例子生产中日志收集不是这么做的)
于是定义了新的交互接口api:实现数据库数据的crud
public interface LogToDb {
void add(String log);
void delete(String log);
void update(String log);
String query();
}
实现类:
public class LogToDbImpl implements LogToDb {
@Override
public void add(String log) {
System.out.println("写到数据库");
}
@Override
public void delete(String log) {
System.out.println("从数据库删除");
}
@Override
public void update(String log) {
System.out.println("更新到数据库");
}
@Override
public String query() {
System.out.println("从数据库查询");
return "";
}
}
客户端使用:
//新版本写到数据库,并提供增删改查功能
LogToDb logToDb = new LogToDbImpl();
logToDb.add("aaa");
logToDb.update("aaa");
logToDb.delete("aaa");
String query = logToDb.query();
好的,到这里为止我们有两套日志实现了,一个是file的一个是db的。
老系统使用file的没问题,新系统使用db的api也没问题,如果有用户目前并不希望升级log保存到数据库的版本,但是又想使用新版的提供了CRUD工能的接口去开发新功能(这样的好处是以后想升级db的实现,更换一个实现类就可以了不用改代码),如何实现呢?
者就用到了我们上面讲的适配器了,提供一个适配器类实现新的CRUD接口,但底层仍然用File版的实现类。可以顺便用上我们之前讲的简单工厂模式,封装LogToDb 接口,后期如果项目想用数据库保存日志,直接切换配置文件就好了
public class LogAdapter implements LogToDb {
private LogToFile logToFile = new LogToFileImpl();
@Override
public void add(String log) {
logToFile.write(log);
}
@Override
public void delete(String log) {
logToFile.delete(log);
}
@Override
public void update(String log) {
logToFile.delete(log);
logToFile.write(log);
}
@Override
public String query() {
return logToFile.read();
}
}
简单工厂:
public class LogFactory {
LogToDb createLogger(){
return new LogAdapter();
//可以改为根据配置文件判断使用第一版File版本还是新的DB版本
//return new LogToDbImpl();
}
}
客户端使用,使用了简单工厂的代码是最后三行,上面的代码供参考
public static void main(String[] args) {
//老版本,日志写到文件
LogToFile logToFile = new LogToFileImpl();
logToFile.write("error");
String read = logToFile.read();
logToFile.delete("aaa");
//新版本写到数据库,并提供增删改查功能
LogToDb logToDb = new LogToDbImpl();
logToDb.add("aaa");
logToDb.update("aaa");
logToDb.delete("aaa");
String query = logToDb.query();
//不封装工厂直接使用适配器类
LogAdapter adapter = new LogAdapter();
adapter.add("调用的写到db的api方法,其实底层是写到文件");
//封装工厂的使用方法
LogToDb logger = LogFactory.createLogger();
logger.add("a");
String query1 = logger.query();
}
- 双向适配器
上面的例子是我们可以通过新接口api但是底层仍然使用老的实现,当然也可以使用老的api底层使用新的实现,这种两种都兼容的适配器就叫做双向适配器,简单来说就是适配器类同时实现新老两个接口,注入新老两个实现类,不同的接口方法就交给相应的实现类完成。
我们上面举的例子是新老接口的兼容。
适配器模式的有点可以解决接口的兼容问题,但是相应的过多的使用和滥用适配器模式会让系统变得非常凌乱,一般的原则是能不用就不用。
适配器模式本质:转换匹配,复用功能