设计模式之适配器模式

441 阅读4分钟

概念

适配器模式的英文翻译是 Adapter Design Pattern。顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。

​ 适配器的实现有两种方式:类适配器和对象适配器。其中,类适配器是通过继承关系来实现,对象适配器使用组合关系来实现。

使用方式

关键点:

  • 定义适配器接口;
  • 定义适配器实现类,实现适配器接口;

假设有发送短信的场景,有阿里和网易的发送通道接口。假设在设计之初,阿里和网易的发送接口分别只有单发和群发的接口,那么此时就可以利用适配器模式将发送接口统一。

  • 阿里的发送接口
public class SmsForAli {
    public void sendSms(String mobile,String content) {
        System.out.println("发生短信,调用阿里的接口,mobile=" +mobile);
    }
}
  • 网易的发送接口
public class SmsForWangyi {

    public void sendSms(List<String> mobileList, String content) {
        System.out.println("发生短信,调用网易的发送接口,content=" +content);
    }
}

在没有适配器之前,调用方需要知道所有短信渠道的发送接口,且知道他们的方法签名。此时是这样调用的。

	@Test
    public void testSmsOrigin() {

        // 使用阿里的发送短信
        SmsForAli smsForAli = new SmsForAli();
        smsForAli.sendSms("123456","hahaha");

        // 使用网易的发生短信
        SmsForWangyi smsForWangyi = new SmsForWangyi();
        smsForWangyi.sendSms(Arrays.asList("123456"),"hahahha");
    }

定义适配器接口

/**
 * 定义适配类的方法
 */
public interface SmsIAdaptor {

    // 单发
    void sendSms(String mobile,String content);

    // 群发
    void sendSms(List<String> mobileList, String content);

}

/**
* 短信通道
*/
public enum SmsWay {
    ALi,
    WANG_YI
}

类适配器

  • 阿里适配器的实现类
/**
 * 阿里短信的适配器
 */
public class SmsAliAdaptor extends SmsForAli implements SmsIAdaptor {

    @Override
    public void sendSms(String mobile, String content) {
        System.out.println("开始适配,调用父类方法");
        super.sendSms(mobile, content);
    }

    @Override
    public void sendSms(List<String> mobileList, String content) {
        for(String mobile : mobileList) {
            sendSms(mobile,content);
        }
    }
}
  • 网易短信的适配器实现类
/**
 * 网易短信的适配·
 */
public class SmsWangyiAdaptor extends SmsForWangyi implements SmsIAdaptor {

    @Override
    public void sendSms(String mobile, String content) {
        this.sendSms(Collections.singletonList(mobile),content);
    }

    @Override
    public void sendSms(List<String> mobileList, String content) {
        System.out.println("开始适配");
        super.sendSms(mobileList,content);
    }
}
  • 定义短信适配器工厂
public class SmsFactory {


    public static SmsIAdaptor getSmsAdaptor(SmsWay smsWay) {
        switch (smsWay) {
            case ALi:
                return new SmsAliAdaptor();
            case WANG_YI:
                return new SmsWangyiAdaptor();
            default:
                throw new RuntimeException("way error");
        }
    }

}
  • 使用方式
    /**
     * 类适配器测试
     */
    @Test
    public void testSmsAdaptorForClazz() {

        // 使用阿里适配器发送短信
        SmsIAdaptor smsIAdaptor = SmsFactory.getSmsAdaptor(SmsWay.ALi);
        smsIAdaptor.sendSms("123456","阿里通道发生短信");

        // 使用网易通道发生短信
        smsIAdaptor = SmsFactory.getSmsAdaptor(SmsWay.WANG_YI);
        smsIAdaptor.sendSms("123456","网易通道发生短信");

    }

在测试方法中可以看出,调用者只需要认识适配器的签名以及有哪些发送通道即可。无需像没接适配器的时候,需要认识多个渠道的短信接口签名。

对象适配器

对象适配器也是类似的,其实就是把继承方式改为组合方式去实现。

  • 阿里适配器实现类
public class SmsAliAdaptor implements SmsIAdaptor {

    private SmsForAli smsForAli;

    public SmsAliAdaptor(SmsForAli smsForAli) {
        this.smsForAli = smsForAli;
    }

    @Override
    public void sendSms(String mobile, String content) {
        smsForAli.sendSms(mobile,content);
    }

    @Override
    public void sendSms(List<String> mobileList, String content) {
        for (String mobile : mobileList) {
            smsForAli.sendSms(mobile,content);
        }
    }
}
  • 网易适配器实现类
public class SmsWangyiAdaptor implements SmsIAdaptor {

    private SmsForWangyi smsForWangyi;

    public SmsWangyiAdaptor(SmsForWangyi smsForWangyi) {
        this.smsForWangyi = smsForWangyi;
    }

    @Override
    public void sendSms(String mobile, String content) {
        sendSms(Arrays.asList(mobile),content);
    }

    @Override
    public void sendSms(List<String> mobileList, String content) {
        smsForWangyi.sendSms(mobileList, content);
    }
}
  • 短信适配器的工厂
public class SmsFactory {
    public static SmsIAdaptor getSmsAdaptor(SmsWay smsWay) {
        switch (smsWay) {
            case ALi:
                return new SmsAliAdaptor(new SmsForAli());
            case WANG_YI:
                return new SmsWangyiAdaptor(new SmsForWangyi());
            default:
                throw new RuntimeException("error");
        }
    }
}

  • 对象适配器的使用方式
    @Test
    public void testSmsAdaptorForObject() {
        // 使用阿里适配器发送短信
        SmsIAdaptor smsIAdaptor = structure.adapter.object.SmsFactory.getSmsAdaptor(SmsWay.ALi);
        smsIAdaptor.sendSms("123456","阿里通道发生短信");

        // 使用网易通道发生短信
        smsIAdaptor =  structure.adapter.object.SmsFactory.getSmsAdaptor(SmsWay.WANG_YI);
        smsIAdaptor.sendSms("123456","网易通道发生短信");
    }

总结

​ 适配器模式通常用于补偿的场景,用来补救接口上的缺陷。在Java日志中也有着适配器模式的应用。

​ 由于Java日志并没有规范,在开发中有很多类似的框架,提供了不同的日志级别来打印信息,其中,比较常用的有 log4j、logback,以及 JDK 提供的 JUL(java.util.logging) 和 Apache 的 JCL(Jakarta Commons Logging) 等。

​ 当项目中存在多个日志框架时,由于每个框架的配置方式都不一致,这样将导致项目日志的管理变得复杂。为了解决这个问题,Slf4j 日志就是应用了适配器模式来统一这些日志规范的。

​ Slf4j 并没有具体的实现,只定义了接口,也就是规范,具体的实现就是log4j、logback这些框架。Slf4j 不仅仅提供了统一的接口定义,还提供了针对不同日志框架的适配器。对不同日志框架的接口进行二次封装,适配成统一的 Slf4j 接口定义。在具体的应用中,只需要获取对应的日志实现即可(如log4j、logback)