适配器模式:将一个类的接口,转换为客户期望的另一个接口。适配器让原本接口不兼容的类合作无间。是一种结构型设计模式。
先来看一个示例:我有一个手机充电器,但接口是Micro-USB,但我的手机需要使用Type-C。在不换充电器的情况下,我可通过一个装接头,把Micro-USB转成Type-C。
java 代码
代码: micro-usb接口:
public interface IMicroUSB {
void powerWithMicroUSB();
}
micro-usb实现:
public class MicroUSBImpl implements IMicroUSB {
@Override
public void powerWithMicroUSB() {
System.out.println("使用Micro-USB给手机充电");
}
}
type-c接口:
public interface ITypeC {
void powerWithTypeC();
}
type-c实现:
public class TypeCImpl implements ITypeC {
@Override
public void powerWithTypeC() {
System.out.println("使用type-c给手机充电");
}
}
微usb充电器:
public class Charger {
private IMicroUSB microUSB;
public Charger(IMicroUSB microUSB) {
this.microUSB = microUSB;
}
public void setMicroUSB(IMicroUSB microUSB) {
this.microUSB = microUSB;
}
public void chage(){
microUSB.powerWithMicroUSB();
}
}
测试:
public class Test {
public static void main(String[] args) {
IMicroUSB microUSB = new MicroUSBImpl();
Charger charger = new Charger(microUSB);
charger.chage();
}
}
使用Micro-USB给手机充电
从运行结果看到,不是我们想要的结果。我们需要一个转接头(适配器):
public class ChargePortAdapter implements IMicroUSB{
private ITypeC typeC;
public ChargePortAdapter(ITypeC typeC) {
this.typeC = typeC;
}
@Override
public void powerWithMicroUSB() {
typeC.powerWithTypeC();
}
}
再测试
public class Test {
public static void main(String[] args) {
ITypeC typeC = new TypeCImpl();
ChargePortAdapter chargePortAdapter = new ChargePortAdapter(typeC);
Charger charger = new Charger();
charger.setMicroUSB(chargePortAdapter);
charger.chage();
}
}
使用type c给手机充电
得到了我们想要的结果。
类图和角色
上列类图:
- Target: 目标抽象类(IMicroUSB),定义客户所需接口,可以是一个抽象类或接口,也可以是具体类
- Adapter: 适配类(ChargeProtAdapter)。作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心。两个关键点:实现target;持有被适配接口的引用。
- Adaptee:适配者类(ITypeC),适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配。
适配器有对象适配器和类适配器。
优缺点
优点:
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
- 一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
缺点:
- 类适配器:Java不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
- 对象适配器:类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。
适配器中mybatis中的应用
mybatis包org.apache.ibatis.logging
下,首先是LogFactory类,负责创建Log对象,这些Log接口的实现类中,就是三方日志的适配器。
其中LogFactory的静态代码,会依次加载第三方日志的适配器
public final class LogFactory {
private static Constructor<? extends Log> logConstructor;
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
private LogFactory() {
// disable construction
}
}
中tryImplementation中,会检查logConstructor是否为空,空表示已经加载到具体实现,直接返回,否则执行当前线程传入的Runnable.run()方法,去尝试加载使用的日志实现。以log4j2为例子:
public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
// 获取Log4j2Impl这个适配器的构造方法
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
// 尝试加载implClass 这个适配器,如果系统没引入log4j,反射创建适配器时,就会因为没有log4j.LogManager类二抛出异常
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
// 加载成功,把构造记录下来使用
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
使用时:日志输入会委托给org.apache.logging.log4j.Logger