结构模式之适配器模式

Posted by Night Field's Blog on April 2, 2020

1 概述

适配器模式(Adapter Pattern),从名字就可以看出,工作模式类似于适配器:将原本不兼容的两样事物连接,以协同工作。

2 适配器模式

充电器(电源适配器)是日常生活中常见的例子。大多手机要求输入电压是5V,而家用交流电的电压都是220V,充电器作为适配器,将220V的电压转为目标电器需要的电压。适配器模式也类似,通过适配器,将类的接口转换为目标所期望的另一个接口。 适配器模式开闭原则的体现,通过增加一个适配类,避免了对原有逻辑的修改。

3 案例

适配器模式主要有三种类型:类适配器,对象适配器,默认适配器。下面举例说明。

3.1 类适配器

类适配器使用的是继承模式,适配器类即是A接口,又是B接口。下面的例子展现了,如何通过适配器,使得“中国人”说“英语”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class Test {
    public static void main(String[] args) {
        // 普通ChineseSpeaker对象,只能输出中文
        ChineseSpeaker xiaoming = new ChinesePeople();
        xiaoming.speak();
        // 被适配过的ChineseSpeaker对象,可以输出英文
        ChineseSpeaker englishTeacher = new ChinesePeopleEnglishAdapter();
        englishTeacher.speak();
    }
}

public interface EnglishSpeaker {
    void talk();
}
public class EnglishPeople implements EnglishSpeaker {
    @Override
    public void talk() {
        System.out.println("Hello!");
    }
}

public interface ChineseSpeaker {
    void speak();
}
public class ChinesePeople implements ChineseSpeaker {
    @Override
    public void speak() {
        System.out.println("大家好");
    }
}

// 适配器类继承了EnglishPeople,将speakChinese方法适配为speakEnglish方法
public class ChinesePeopleEnglishAdapter extends EnglishPeople implements ChineseSpeaker {
    @Override
    public void speak() {
        this.talk();
    }
}

输出:

1
2
大家好
Hello!

uml

ChineseSpeakerEnglishSpeaker是两个不同的接口,而通过适配器类ChinesePeopleEnglishAdapter,使得ChineseSpeaker对象可以调用EnglishSpeaker的方法。

3.2 对象适配器

对象适配器使用的是组合模式,适配器实现A接口,并持有B接口的实例。下面的例子展现了,如何通过适配器,使得“普通人“可以“飞行”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class Test {
    public static void main(String[] args) {
        // 普通的Person对象,只能“跑”
        Person blackWidow = new Mortal("Natasha");
        blackWidow.move();
        IronSuit suit = new IronSuit();
        // 被适配了的对象,可以实现“飞”
        Person ironMan = new FlyablePersonAdapter("Tony Stark", suit);
        ironMan.move();
    }
}

public interface Person {
    void move();
}
public class Mortal implements Person {
    private String name;
    Mortal(String name) {
        this.name = name;
    }
    @Override
    public void move() {
        System.out.println(name + " is running!");
    }
}

public interface Flyable {
    void fly();
}
public class IronSuit implements Flyable {
    @Override
    public void fly() {
        System.out.println("I'm flying!");
    }
}

// 适配器类持有IronSuit实例,将move方法适配为fly方法
public class FlyablePersonAdapter implements Person {
    private String name;
    IronSuit suit;
    FlyablePersonAdapter(String name, IronSuit suit) {
        this.name = name;
        this.suit = suit;
    }
    @Override
    public void move() {
        System.out.print(name + " is wearing Iron Suit: ");
        suit.fly();
    }
}

输出:

1
2
Natasha is running!
Tony Stark is wearing Iron Suit: I'm flying!

uml

通过适配,可以让Personmove()方法变为Flyablefly()方法。

3.3 默认适配器

默认适配器适配器模式的变种,主要解决的问题是,当一个接口有多个方法时,有时候实现类只关心其中的部分方法。通过添加一个适配器类来给方法提供默认实现,可以实现这一需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Test {
    public static void main(String[] args) {
        People jay = new Singer();
        jay.speak();
        People yao = new Athlete();
        yao.move();
    }
}

public interface People {
    void eat();
    void sleep();
    void move();
    void speak();
}
public class PeopleAdapter implements People {
    @Override
    public void eat() {}
    @Override
    public void sleep() {}
    @Override
    public void move() {}
    @Override
    public void speak() {}
}

// 通过适配器,Athlete只需要实现move方法
public class Athlete extends PeopleAdapter {
    @Override
    public void move() {
        System.out.println("Athlete is running.");
    }
}
// 通过适配器,Singer只需要实现speak方法
public class Singer extends PeopleAdapter {
    @Override
    public void speak() {
        System.out.println("Singer is singing.");
    }
}

输出:

1
2
Singer is singing.
Athlete is running.

uml

适配器类PeopleAdapter给接口中的方法提供了默认的实现(或是空实现),使得其子类可以只关心自己需要的方法。

4 使用

前两种适配器模式,在给系统新增功能的时候非常有用,可以避免对原有逻辑的修改,降低系统的复杂度。比如JDK中我们熟悉的Callable接口,跟Runnable一样,也可以新起一个线程。但这是JDK1.5中新增的接口,而新起线程是由Runnable的实现类Thread中的native方法实现的,那如何在原有基础上,增加对Callable支持呢?答案就是适配器模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class FutureTask<V> implements RunnableFuture<V> {
    private Callable<V> callable;
    public FutureTask(Callable<V> callable) {
        this.callable = callable;
    }
    public void run() {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                set(result);
        }
    }
}

Callable都会被包装成一个FutureTask的实例,FutureTask实现了Runnable接口,可以作为RunnableCallable两个接口的适配器,这样,我们不需要对原先Runnable的框架做任何修改。

而第三种适配器模式则主要运用在开发过程中,可以为我们减少很多工作,易于开发。比较广为人知的便是NettyChannelHandlerAdapter,它为开发者提供了接口中方法的空实现,降低了接口使用的难度。

4 总结

适配器模式符合开闭原则。当需要使两个不兼容的接口一起工作时,适配器模式将是很好的选择。

文中例子的github地址