【进阶之路】理解结构型模式开发(适配器模式)

725 阅读7分钟

大家好,我是练习java两年半时间的南橘,从一名连java有几种数据结构都不懂超级小白,到现在懂了一点点的进阶小白,学到了不少的东西。知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了一些平常学习和面试中的重点(自我认为),希望给大家带来一些帮助

有需要的同学可以加我的公众号,以后的最新的文章第一时间都在里面,也可以找我要思维导图

【进阶之路】理解结构型模式开发(桥接模式)

【进阶之路】理解结构型模式开发(享元模式)

之前的文章,我们讲过桥接模式。桥接模式是我们代码里抽象与现实之间的桥梁,而今天要讲的适配器模式,则可以理解为不兼容的接口(类)之间的桥梁,就像读卡器至于手机卡(现在已经没有手机卡了吧),转接头之于HTML线一样。

一、定义

适配器模式: 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。适配器模式分为类结构型模式和对象结构型模式两种,前者类之间的耦合度比后者高,且应用相对较少些。

适配器模式的优点:

1、客户端通过适配器可以透明地调用目标接口。
2、复用了现存的类,不需要修改原有代码而重用现有的适配者类。
3、将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。

适配器模式的缺点:

1、适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
2、增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。

在实际的工作中,有时候会出现公司合并或者收购别的公司的情况(确信),这个时候,就会把其他公司的代码给接收过来,如果遇到相同相似的模块,往往会需要代码合并,适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。或是在使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同,使用适配器模式来解决这个问题。亦或是以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致

以上种种情况,都是我们使用适配器模式的理由。

再提一句:设计模式就是前人经验的结晶,每一种设计模式都解决了特定的问题,使用这些设计模式就是站在了前人的肩膀上。设计模式更多的是让我们学习前人的经验,而不是为了用而用,为了炫技而炫技。

适配器模式的主要角色:

目标接口:当前系统业务所期待的接口,它可以是抽象类或接口。
适配者类:它是被访问和适配的现存组件库中的组件接口。
适配器类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访
问适配者。

二、类适配器模式实现

1、目标接口

public interface Smoke {
    //烟草专卖
    public void sellSmoke();
}

2、适配者类

public class GuangdongSmoke implements Smoke{
    //广东省售烟平台在系统内,可以卖烟
    public void companyGD(){
        System.out.println("广东省烟草专卖");
    }

    @Override
    public void sellSmoke() {
        this.companyGD();
    }
}

public class GuiZhouSmoke {
    //贵州省没有接入系统,无法卖烟
    public void companyGZ(){
        System.out.println("贵州省接入烟草专卖");
    }

}

3、适配器类

public class Adapter extends GuiZhouSmoke implements Smoke {

    /**
     *     适配器类,继承了被适配类,实现标准接口
     *     就相当于通过中间代码将贵州的烟草信息接入广州
      */
    @Override
    public void sellSmoke() {
        super.companyGZ();
    }
}

4、测试类

  public static void main(String[] args) {
        //本身功能实现
        Smoke gd =new GuangdongSmoke();
        gd.sellSmoke(); //广东省烟草专卖
        //通过适配器来实现
        Smoke gz =new Adapter();
        gz.sellSmoke();//贵州省接入烟草专卖
    }

上面这种实现的适配器称为类适配器,因为 Adapter 类既继承了 GuiZhouSmoke (被适配类),也实现了 Smoke 接口,在测试类中我们可以根据需要选择并创建任一种符合需求的子类,来实现具体功能。

当然,实际开发中可以参考SpringMvc的适配器。

Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类, 让适配器代替controller执行相应的方法。这样在扩展Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了

三、对象适配器模式实现

另外一种适配器模式是对象适配器,它不是使用继承再实现的方式,而是使用直接关联,或者称为委托的方式。

1、目标接口

public interface Smoke {
    //烟草专卖
    public void sellSmoke();
}

2、适配者类

public class GuangdongSmoke implements Smoke{
    //广东省售烟平台在系统内,可以卖烟
    public void companyGD(){
        System.out.println("广东省烟草专卖");
    }

    @Override
    public void sellSmoke() {
        this.companyGD();
    }
}

public class GuiZhouSmoke {
    //贵州省没有接入系统,无法卖烟
    public void companyGZ(){
        System.out.println("贵州省接入烟草专卖");
    }

}

3、适配器类

这一次适配器类直接关联被适配类,同时实现标准接口

public class Adapter  implements Smoke {
    private GuiZhouSmoke guiZhouSmoke;

    public Adapter(GuiZhouSmoke guiZhouSmoke) {
        this.guiZhouSmoke = guiZhouSmoke;
    }

    /**
     *     适配器类,继承了被适配类,实现标准接口
     *     就相当于通过中间代码将贵州的烟草信息接入广州
      */
    @Override
    public void sellSmoke() {
       this.guiZhouSmoke.companyGZ();
    }
}

4、测试类

  public static void main(String[] args) {
        //本身功能实现
        Smoke gd =new GuangdongSmoke();
        gd.sellSmoke(); //广东省烟草专卖
        //通过适配器来实现
        GuiZhouSmoke guiZhouSmoke =new GuiZhouSmoke();
        Smoke gz =new Adapter(guiZhouSmoke);
        gz.sellSmoke();//贵州省接入烟草专卖
    }    

从IEDA的diagrams图也能看出来,只要Adapter自身拥有一个被适配类的对象,再把具体的特殊功能委托给这个对象来实现。使用对象适配器模式,可以使得 Adapter 类(适配类)根据传入的 Adaptee 对象达到适配多个不同被适配类的功能,当然,此时我们可以为多个被适配类提取出一个接口或抽象类。这样看起来的话,对象适配器模式更加灵活一点。

在 Spring 的 AOP 里通过使用的 Advice(通知)来增强被代理类的功能。Spring 实现这一 AOP 功能的原理就使用代理模式(1、JDK 动态代理。2、CGLib 字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类,并在代理类的方法前,设置拦截器,通过执行拦截器中的内容增强了代理方法的功能,实现的面向切面编程。

Advice(通知)的类型有:BeforeAdvice、AfterReturningAdvice、ThrowSadvice 等。每个类型 Advice(通知)都有对应的拦截器,MethodBeforeAdviceInterceptor、AfterReturningAdviceInterceptor、ThrowsAdviceInterceptor。Spring 需要将每个 Advice(通知)都封装成对应的拦截器类型,返回给容器,所以需要使用适配器模式对 Advice 进行转换。

四、适配器模式的应用

适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。比如在需要对早期代码复用一些功能等应用上很有实际价值。适用场景大致包含三类:

  • 1、已经存在的类的接口不符合我们的需求
  • 2、创建一个可以复用的类,使得该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同作
  • 3、在不对每一个都进行子类化以匹配它们的接口的情况下,使用一些已经存在的子类

适配器模式是对现相存系统的封装,为现存系统提供一个更为方便的访问接口。适配器模式为事后设计,没有引入新的接口,只是将一个接口通过适配来间接转换为另一个接口。