设计模式五:适配器

适配器模式:将一个类的接口,转换为客户期望的另一个接口。适配器让原本接口不兼容的类合作无间。是一种结构型设计模式。

先来看一个示例:我有一个手机充电器,但接口是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),适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配。

适配器有对象适配器和类适配器。

优缺点

优点:

  1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
  2. 一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。

缺点:

  1. 类适配器:Java不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。
  2. 对象适配器:类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

引用Graphic Design Patterns

适配器中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


CONTENTS