设计模式之适配器模式(Adapter Pattern)

一、概念

将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

解释:官方概念比较晦涩难懂,用通俗易懂的话说就是,我们需要实现一个接口或者抽象方法,但是在实现类中,我们需要一个第三方类的方法来实现接口方法,我们会怎么办呢?一种方法就是将实现类继承第三方类,来调用第三方方法,另一种方法就是在实现类中定义第三方类的成员属性,然后通过成员属性调用第三方方法。这波操作,就是适配器模式。通过继承调用第三方方法,就是类适配器模式,通过成员属性调用第三方方法,就是对象适配器模式。

 

二、角色

还用上面通俗易懂的解释,来说明适配器模式用到的角色:

目标抽象类(Target):就是接口或抽象类,需要实现的方法,供程序员来调用。

适配者类(Adaptee):就是在实现目标类的方法时,需要用到的第三方的类。之所以称为适配者,是因为在实现目标类方法的时候,需要用到这个类,所以实现类需要适配了这个类,才能用到这个类的方法,所以称之为适配者。

适配器类(Adapter):其实就是目标类的实现类,之所以称之为适配器类,是因为它还要融入第三方类(适配者类)来实现目标类方法。

 

由此可见,无论适配器模式如何设计,只要是通过一个第三方类来实现另一个接口的模式,都可以称作适配器模式。

 

三、缺省适配器模式

当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。缺省适配器模式是适配器模式的一种变体,其应用也较为广泛。在JDK类库的事件处理包java.awt.event中广泛使用了缺省适配器模式,如WindowAdapter、KeyAdapter、MouseAdapter等。

在缺省模式的适配器中,我们可以看到,其缺省的就是一个适配者类。其实就是一个接口有很多抽象方法,那我们就自定义一个实现类,实现所有的方法,但是实现方法里不做任何操作。这样,在业务程序员想使用某个接口中的某个方法时,调用适配器类即可,实现想要用的某个方法,不用全部实现接口中的方法。这里就是涉及到了适配器类和目标类,缺省了适配者类。

 

四、代码实现

1.类适配器的实现

首先,定义一个适配者,也就是第三方类,在实现接口时,需要用到的类,代码如下:

 

/**
 *  适配者类,第三方类,在实现某个接口时需要调用的类
 */
public class Adaptee {
    public void adapteeRequest(){
        System.out.println("被适配者的方法");
    }
    
}

 

然后,定义一个目标接口,这个接口,是业务程序员想使用的产品,

public interface Target {
    void request();
}

 

 下面,我们就要实现Target里的方法了,但是,实现这个方法,我们需要用到适配者类中的方法,所以,我们要继承适配者类,来调用适配者类的方法,

/**
 * 适配器类,实现Target方法。继承Adaptee适配器,来调用第三方类方法
 */
public class Adapter extends Adaptee implements Target{
    @Override
    public void request() {
        super.adapteeRequest();
    }
}

 

这样,我们就完成了适配器模式。

2.对象适配器的实现

对象适配器就是在适配器中,将适配者类设置成成员属性,来进行第三方方法的调用,如下代码:

public class Adapter implements Target{
    // 适配者是对象适配器的一个属性
    private Adaptee adaptee = new Adaptee();

    @Override
    public void request() {
        //...
        adaptee.adapteeRequest();
        //...
    }
}

 

这样,就实现了对象适配器模式。

由此可以看出,适配器模式,是为业务程序员提供了一个问题的解决思路,在实现某个想要的接口时,但是又需要借助第三方类时,我们可以用继承的方式,或者通过加入成员属性的方式。

五、电压适配器代码实现

下面,我们再以电压适配器为例,来加强对适配器模式的理解。

业务场景:中国电压是220V,日本电压是110V。现在要输出5V的电压,来供手机充电使用,利用代码如何实现呢?

我们先进行分析,目标类是什么呢?目标就是输出5V的电压。适配者是谁呢,适配者就是第三方应用,这里就是220V或者110V电压,利用电压这个第三方,来实现5V电压。那么,适配器,就是将110V或220V电压适配成5V的一个类了。

代码实现如下:

 首先,我们定义适配者类,就是电压,代码如下:

public interface AC {
    int outputAC();
}

public class AC110 implements AC {
    public final int output = 110;

    @Override
    public int outputAC() {
        return output;
    }
}

public class AC220 implements AC {
    public final int output = 220;

    @Override
    public int outputAC() {
        return output;
    }
}

 

然后,我们定义适配器接口,适配器用于将电压适配成5V电压 ,

public interface DC5Adapter {
    boolean support(AC ac);

    int outputDC5V(AC ac);
}

 

实现具体的适配器:

public class ChinaPowerAdapter implements DC5Adapter {
    public static final int voltage = 220;

    @Override
    public boolean support(AC ac) {
        return (voltage == ac.outputAC());
    }

    @Override
    public int outputDC5V(AC ac) {
        int adapterInput = ac.outputAC();
        //变压器...
        int adapterOutput = adapterInput / 44;
        System.out.println("使用ChinaPowerAdapter变压适配器,输入AC:" + adapterInput + "V" + ",输出DC:" + adapterOutput + "V");
        return adapterOutput;
    }
}

public class JapanPowerAdapter implements DC5Adapter {
    public static final int voltage = 110;

    @Override
    public boolean support(AC ac) {
        return (voltage == ac.outputAC());
    }

    @Override
    public int outputDC5V(AC ac) {
        int adapterInput = ac.outputAC();
        //变压器...
        int adapterOutput = adapterInput / 22;
        System.out.println("使用JapanPowerAdapter变压适配器,输入AC:" + adapterInput + "V" + ",输出DC:" + adapterOutput + "V");
        return adapterOutput;
    }
}

 

这样,我们就可以通过适配器,将110V或220V电压转变成5V电压了,测试代码如下:

public class Test {
    private List<DC5Adapter> adapters = new LinkedList<DC5Adapter>();

    public Test() {
        this.adapters.add(new ChinaPowerAdapter());
        this.adapters.add(new JapanPowerAdapter());
    }

    // 根据电压找合适的变压器
    public DC5Adapter getPowerAdapter(AC ac) {
        DC5Adapter adapter = null;
        for (DC5Adapter ad : this.adapters) {
            if (ad.support(ac)) {
                adapter = ad;
                break;
            }
        }
        if (adapter == null){
            throw new  IllegalArgumentException("没有找到合适的变压适配器");
        }
        return adapter;
    }

    public static void main(String[] args) {
        Test test = new Test();
        AC chinaAC = new AC220();
        DC5Adapter adapter = test.getPowerAdapter(chinaAC);
        adapter.outputDC5V(chinaAC);

        // 去日本旅游,电压是 110V
        AC japanAC = new AC110();
        adapter = test.getPowerAdapter(japanAC);
        adapter.outputDC5V(japanAC);
    }
}

 

上面这个案例我们可以知道,适配器模式,其实就是需要我们写适配器这个类。目标类是我们想要的产品,为了实现目标类,我们又要用第三方类,这时候,我们就定义适配器类,来实现想要的产品,同时适配第三方类,来帮助我们实现产品。说白了,它还是去实现一个我们想要的一个产品。上面讲到的电压这个案例,并没有严格按照标准的适配器模式来做,而是用了适配器模式的思想。本质思想就是要获得5V的电压,如何获得呢,需要借助第三方电压(220V或110V),所以,在适配器类里,就将电压AC以参数的方式传入了,来将其适配5v电压。这里的适配器既没有继承适配者类,也没有将适配者类当成成员属性,而是以变量的形式传入。且这里的目标,就是5V的电压,也没有形成具体的接口。但是,这里的思想,用的就是适配器模式。所以,我们在学习设计模式时,最终要掌握的,是它的思想,而不是它的形式。

六、优缺点

优点:

  1. 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构。这句话的意思也表明,我们利用适配器模式,其实是设计好适配器类。
  2. 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
  3. 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
  4. 一个对象适配器可以把多个不同的适配者适配到同一个目标;
  5. 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据“里氏代换原则”,适配者的子类也可通过该适配器进行适配。

缺点:

与类适配器模式相比,要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

七、适配器模式在SpirngMVC中的应用

这里,我们不详细讲解SpringMVC的源码和流程,我们只将涉及到适配器模式的地方。

SpringMVC的本质是一个servlet(DispatcherServlet),在这里servlet里,最终要将数据和样式(ModelAndView),返回给浏览器。在这里,我们的目标产品就是ModelAndView。那么,谁定义了这个规范呢,是HandlerAdapter接口。这里面定义了实现ModelAndView的规范,代码如下:

public interface HandlerAdapter {
    boolean supports(Object var1);

    ModelAndView handle(HttpServletRequest var1, HttpServletResponse var2, Object var3) throws Exception;

    long getLastModified(HttpServletRequest var1, Object var2);
}

 

如何得到ModelAndView呢,我们知道,在Controller里,能够返回ModelAndView。所以,这个接口里,需要用到Controller。所以,Controller就是适配者,调用Controller,来获得ModelAndView。所以,在SpringMVC中,DispatcherServlet是调用HandlerAdapter的实现类(适配器),实现类里又调用了Controller(适配者),来返回目标ModelAndView这样一个流程。HandlerAdapter接口,既属于目标抽象类,也属于适配器抽象类,起到了两个作用。很多框架源码中,都把目标抽象类和适配器抽象类合二为一了,因为这样既定义了产品,又定义了适配器,一举两得。

那么为什么SpringMVC要用适配器模式呢?DispatcherServlet直接从Controller里获取ModelAndView不好吗,为什么要从适配器里获取呢?这个问题,我们在阅读SpringMVC源码时,再进行深入讲解。

posted on 2021-03-17 15:49  敲代码的小小酥  阅读(82)  评论(0编辑  收藏  举报