Adapter模式


上图为笔记本电脑的电源适配器,笔记本需要12~20v直流电压供电,但是插座出来的电压为220v交流电。笔记本电源适配器的作用就是将家庭用的220v交流电压转换成笔记本可以使用的20v直流电。这就是适配器的工作,它位于实际情况与需求之间,填补两者之间的差异。适配器模式起到的作用和适配器起到的作用是一样的。
适配器模式可以分为两种:

  1. 类适配器
    类适配器中,Adapter与Adaptee是继承关系
  2. 对象适配器
    对象适配器中,Adapter与Adaptee是委托关系

接下来,通过示例程序,我们来看看这两种适配模式分别是什么

类适配器示例程序

示例程序是一段会将输入的字符串Hello显示为(Hello)或者是*Hello*的简单程序
目前我们有Banner类,Banner类中有将字符串用括号括起来的showWithParen方法和将字符串用*号括起来的showWithAster方法。这个类的角色就是Adaptee也就是被适配的类。
假设Print接口中声明了两种方法,即弱化字符串显示(加括号)的printWeak方法,和强调字符串(加星号)的printStrong方法。
我们现在要做的事情就是做一个Banner类的适配器,为Banner类编写一个实现了Print接口的类,这个类继承了Banner类,通过Print接口完成Banner类中showWithParen和showWithAster的功能。
假设这样一个适配器我们称作PrintBanner类。
使用类适配器模式的UML图如下:


程序如下:

Banner类

public class Banner{
    private String string;
    public Banner(String string){
        this.string = string;
    }
    public void showWithParen(){
        System.out.println("(" + string + ")");
    }
    public void showWithAster(){
        System.out.println("*" + string + "*");
    }
}

Print接口

public interface Print{
    public abstract void printWeak();
    public abstract void printStrong();
}

PrintBanner类

public PrintBanner extends Banner implements Print{
    public PrintBanner(String string){
        super(string);
    }
    public void printWeak(){
        showWithParen();
    }
    public void printStrong(){
        showWithAster();
    }
}

Main程序

public class Main{
    public static void main(String[] args){
        Print p = new PrintBanner("Hello");
        p.printWeak();
        p.printStrong();
    }
}

对象适配器示例程序

依旧使用上面的例子。对于类适配器来说,我们使用了继承的思想实现了适配,对于对象适配器来说,则是委托。
在类适配器模式中Print作为一个接口,但是在对象适配器这个例子中,假设Print则作为一个抽象类的存在。那么PrintBanner适配器则需要继承Print类;因为Java中无法多继承,所以这里面PrintBanner类无法同时继承Print类和Banner类,不过可以在适配器PrintBanner类中使用Banner类的对象,作为它的字段。这样当PrintBanner类的printWeak被调用的时候,并不是PrintBanner类自己进行处理的,而是将其委托给了Banner类的实例调用了showWithParen方法。
对象适配器模式的UML图如下:



相较于类适配器模式来说,对象适配器的示例程序改动如下:

Print类

public abstract class Print{
    public abstract void printWeak();
    public abstract void printStrong();
}

PrintBanner类

public class PrintBanner extends Print{
    private Banner banner;
    public PrintBanner(String string){
        this.banner = new Banner(string);
    }
    public void printWeak(){
        banner.showWithParen();
    }
    public void printStrong(){
        banner.showWithAster();
    }
}

Adapter模式中的角色

Target

该角色负责定义所需要的方法,示例程序中Print接口(类适配)和Print类(对象适配)扮演这个角色。

Client

Client负责使用Target角色所定义的方法进行具体处理。示例程序的Main类扮演这个角色

Adaptee

被适配的类,示例程序中的Banner类扮演这个角色。

Adapter

适配器,Adapter角色的方法用来满足Target角色的需求,示例程序中PrintBanner类扮演这个角色。

类适配器模式的UML图

对象适配器模式的UML图

关于Adapter模式的思考

为什么需要适配器模式

实际发开中,如果有一个类库具有很多方法,但我们只需要其中一两个,那么就可以写一个适配器。Adapter模式会对现有的类进行适配,生成新的类,然后我们用我们创造的新的类即,适配器去调用这一两个我们需要的方法。要是出了Bug,由于我们很明确地知道Bug不在现有的类库(Adaptee角色)中,就可以直接去Adapter里面找,而不是去翻那个类库,这样一来,代码问题的排查就会变得非常简单。

为什么需要Target角色

在示例程序中,我们使用了这样的写法

Print p = new PrintBanner("Hello");

为什么不去直接这样写呢

PrintBanner p = new PrintBanner("Hello");

原因在于,本示例程序中,PrintBanner类和Print接口对外提供的方法是相同的。但是在大部分情况下,PrintBanner类中的方法可能会比Print接口对外提供的方法多的多。通过将对象保存在Print类型的变量中使用,可以明确地表明程序的意图。