前言
小编最近看了很多写设计模式的文章,发现网上已经很好的写出了一些关于设计模式的例子,也总结了对应模式的优缺点。但不知道各位有没有发现其实很少有文章写在框架或项目中的引用,是否和小编一样,看完了对应的设计模式不知道怎么应用到框架中,或者生搬硬套或者没有必要的使用。所以小编最近会在对应的例子上,侧重说明该模式在框架中的引用,并贴出相应的源代码。供各位学习,也请各位提出疑问,当然如果有错误之处也请各位指出。
定义
适配器模式:它是使原本接口不兼容的对象能够相互合作。下面画张图解释一下。
这边不知道小编说清楚没有,图也比较抽象。举个生活中的例子,比方说我们有3.5mm的耳机,但是我们的手机上只有type-c接口,那我们就用不来这个耳机了,但是你又type-c接口转3.5mm的转换器,那你就可以使用了,转换器就是我们软件中的适配器。(题外话:当然你也可以直接买个type-c接口的耳机,但这样成本比较大,对于软件也一样,所以引入了适配器。)
需求
这边小编举一个例子,在项目开发中也使用过,我们目前项目中使用的restful接口,很多传输的数据结构基本上是json格式,这时我们对接了一个外部项目,外部项目比较老旧,它需要xml格式传输,当然他给我们也是xml格式的,那我们这边就需要做转换,外部系统传入我们系统则从xml转到json,我们系统转入他们系统则从json转换成xml。那我们就做个适配器做这个转换功能。(题外话:当然可以使用工具类就可以实现了,相互转换的数据的工具类,但这个工具类肯定会有很多地方调用,同样也可以写个方法统一在数据传出和传入的地方调用工具类即可。不要太在意啊)
同样不可能贴出项目代码,而且如同题外话中一样,可能很多小伙伴认为并不需要。不过同样我先贴出代码。
接口从外部到内部 xml转json
public interface DataJsonConversion {
/**
* 数据转换
*
* @param msg 数据
* @return 转换后数据
*/
String dataTransferJson(String msg);
}
接口从内部到外部 json转xml
public interface DataXmlConversion {
/**
* 数据转换
*
* @param msg 数据
* @return 转换后数据
*/
String dataTransferXml(String msg);
}
双向数据转换适配器(这个有点像一个手机既可以被充电还可以像其他手机充电一样)
public class DataTypeConversion implements DataJsonConversion, DataXmlConversion {
private DataJsonConversion jsonConversion;
private DataXmlConversion xmlConversion;
public DataTypeConversion(DataJsonConversion jsonConversion) {
this.jsonConversion = jsonConversion;
}
public DataTypeConversion(DataXmlConversion xmlConversion) {
this.xmlConversion = xmlConversion;
}
public DataTypeConversion(DataJsonConversion jsonConversion, DataXmlConversion xmlConversion) {
this.jsonConversion = jsonConversion;
this.xmlConversion = xmlConversion;
}
@Override
public String dataTransferJson(String msg) {
return jsonConversion.dataTransferJson(msg);
}
@Override
public String dataTransferXml(String msg) {
return xmlConversion.dataTransferXml(msg);
}
}
测试代码
public class AdapterTest {
@Test
public void dataTypeTest(){
DataJsonConversion jsonConversion = (msg)-> {
System.out.println("转换成json格式了");
return "json";
};
DataXmlConversion xmlConversion = (msg) ->{
System.out.println("转换成xml格式了");
return "xml";
};
DataJsonConversion dataTypeJson = new DataTypeConversion(jsonConversion);
String xml = dataTypeJson.dataTransferJson("xml");
Assert.assertEquals("json", xml);
DataXmlConversion dataTypeXml = new DataTypeConversion(xmlConversion);
String json = dataTypeXml.dataTransferXml("json");
Assert.assertEquals("xml", json);
DataTypeConversion dataTwoType = new DataTypeConversion(jsonConversion,xmlConversion);
dataTwoType.dataTransferJson("xml");
dataTwoType.dataTransferXml("json");
}
}
测试结果
xml转换成json格式了
json转换成xml格式了
上面是双向适配。,可能小伙伴并不觉得比较有用。那下面我们讲讲适配器经典案例中框架代码中,从dubbo,tomcat和mybatis。
在源码框架中的使用
这边主要讲的日志框架的使用,我们只平常开发中会选择相应的日志框架如jcl,log4j,log4j2,logback,jul,slf4j等,那我们再引入第三方框架的时候,他们怎么知道我们使用的是什么日志框架,并且也能打印框架里面的日志信息。这里我们就引入了适配器模式,我们在自己的项目框架拓展也可以使用,废话不多说,上图上代码:
dubbo框架日志结构:
下面是最主要的代码以slf4j为例
/**
* 日志输出器工厂
*
* @author william.liangf
*/
public class LoggerFactory {
private LoggerFactory() {
}
private static volatile LoggerAdapter LOGGER_ADAPTER;
private static final ConcurrentMap<String, FailsafeLogger> LOGGERS = new ConcurrentHashMap<String, FailsafeLogger>();
// 查找常用的日志框架
static {
String logger = System.getProperty("dubbo.application.logger");
if ("slf4j".equals(logger)) {
setLoggerAdapter(new Slf4jLoggerAdapter());
} else if ("jcl".equals(logger)) {
setLoggerAdapter(new JclLoggerAdapter());
} else if ("log4j".equals(logger)) {
setLoggerAdapter(new Log4jLoggerAdapter());
} else if ("jdk".equals(logger)) {
setLoggerAdapter(new JdkLoggerAdapter());
} else {
try {
setLoggerAdapter(new Log4jLoggerAdapter());
} catch (Throwable e1) {
try {
setLoggerAdapter(new Slf4jLoggerAdapter());
} catch (Throwable e2) {
try {
setLoggerAdapter(new JclLoggerAdapter());
} catch (Throwable e3) {
setLoggerAdapter(new JdkLoggerAdapter());
}
}
}
}
}
/**
* 获取日志输出器
*
* @param key
* 分类键
* @return 日志输出器, 后验条件: 不返回null.
*/
public static Logger getLogger(Class<?> key) {
FailsafeLogger logger = LOGGERS.get(key.getName());
if (logger == null) {
LOGGERS.putIfAbsent(key.getName(), new FailsafeLogger(LOGGER_ADAPTER.getLogger(key)));
logger = LOGGERS.get(key.getName());
}
return logger;
}
slf4jAdapter
public class Slf4jLoggerAdapter implements LoggerAdapter {
public Logger getLogger(String key) {
return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(key));
}
public Logger getLogger(Class<?> key) {
return new Slf4jLogger(org.slf4j.LoggerFactory.getLogger(key));
}
}
真正slf4j的适配器
public class Slf4jLogger implements Logger, Serializable {
private static final long serialVersionUID = 1L;
private final org.slf4j.Logger logger;
public Slf4jLogger(org.slf4j.Logger logger) {
this.logger = logger;
}
}
这边小编不知道为什么那个adapter的作用,可能dubbo的作者才清楚,我们只是知道了源代码后,对其作出疑问,其实可以不需要adapter。直接扩展。接下来还有mybatis和tomcat的日志框架留个大家去看一下,其实实现差不多,但是mybatis更加简洁并且代码比较好看。希望大家有这个兴趣去学习一下。
总结
该模式的主要优点如下:
客户端通过适配器可以透明地调用目标接口。
复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
在很多业务场景中符合开闭原则。
其缺点是:
适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。
感谢以及参考
感谢源码阅读网,鲁班大叔的讲解及案例
本文借鉴了相关文章
url : http://c.biancheng.net/view/1361.html
`