设计模式之适配器模式

设计模式之适配器模式

我们周围的适配器

OO适配器是什么,你一定不难理解,因为现实中到处都是。比方说:如果你需要在欧洲国家使用美国制造的笔记本电脑,你可能需要使用一个交流电的适配器…

你知道适配器的作用:它位于美式插头和欧式插头中间,它的工作是将欧式插座转换成美式插座,好让美式插头可以插进这个插座得到电力。或者也可以这么认为:适配器改变了插座的接口,以符合美式笔记本电脑的需求。

其实OO适配器和真实世界的适配器扮演着同样的角色:将一个接口转换成另一个接口,以符合客户的期望。

面向对象适配器

假设已有一个软件系统,你希望它和一个新的产商类库搭配使用,但是这个新产商所设计出来的接口,不同于旧产商的接口;我们又不想改变现有的代码,解决这个问题。所以我们可以提供一个适配器的类,将新产商接口转换成你所期望的接口。

这个适配器工作起来就如同一个中间人,它将客户所发出的请求转换成产商类能理解的请求。

简单示例

1
2
3
4
5
6
7
8
9
/**
* 鸭子接口
* @author Mr.zxb
* @date 2020-08-21 22:05:10
*/
public interface Duck {
void quack();
void fly();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 鸭子实现
* @author Mr.zxb
* @date 2020-08-21 22:05:32
*/
public class MallardDuck implements Duck {
@Override
public void quack() {
System.out.println("Quack");
}

@Override
public void fly() {
System.out.println("I'm flying.");
}
}
1
2
3
4
5
6
7
8
9
/**
* 火鸡接口
* @author Mr.zxb
* @date 2020-08-21 22:06:05
*/
public interface Turkey {
void gobble();
void fly();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 野火鸡
* @author Mr.zxb
* @date 2020-08-21 22:06:44
*/
public class WildTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("Gobble gobble.");
}

@Override
public void fly() {
System.out.println("I'm flying a short distance.");
}
}

假设你缺一些鸭子对象, 想用一些火鸡对象来冒充。显而易见,不能直接使用火鸡接口。所以写一个适配器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 火鸡适配器,将火鸡伪装成鸭子类
* @author Mr.zxb
* @date 2020-08-21 22:07:25
*/
public class TurkeyAdapter implements Duck {
private final Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}

@Override
public void quack() {
turkey.gobble();
}

@Override
public void fly() {
// 火鸡飞行距离很短,不像鸭子飞行距离远,所以让火鸡多飞几次来完成
for (int i = 0; i < 5; i++) {
turkey.fly();
}
}
}

测试适配器:

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
public class DuckTestDrive {
public static void main(String[] args) {
// 创建一只鸭子
MallardDuck duck = new MallardDuck();
// 创建一只火鸡
WildTurkey turkey = new WildTurkey();
// 然后将火鸡包装进一个火鸡适配器中,使它看起来像只鸭子
Duck turkeyAdapter = new TurkeyAdapter(turkey);

// 测试火鸡
System.out.println("The Turkey says...");
turkey.gobble();
turkey.fly();

// 测试鸭子
System.out.println("The Duck says...");
testDuck(duck);

// 测试适配器包装的假鸭子
System.out.println("The TurkeyAdapter says...");
testDuck(turkeyAdapter);
}

static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
The Turkey says...
Gobble gobble.
I'm flying a short distance.

The Duck says...
Quack
I'm flying.

The TurkeyAdapter says...
Gobble gobble.
I'm flying a short distance.
I'm flying a short distance.
I'm flying a short distance.
I'm flying a short distance.
I'm flying a short distance.

适配器模式解析

现在我们知道了什么是适配器,让我们看看各部分之间的关系。

客户使用适配器的过程如下:

  1. 客户通过目标接口调用适配器的方法对适配器发出请求
  2. 适配器使用被适配器接口把请求转换成被适配器的一个或多个调用接口
  3. 客户接收调用的结果,但并未察觉这一切是适配器在起转换作用

定义适配器模式

适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

适配器类图

设计JDK类库中的适配器

在Java早期集合类型(Vector、Stack、Hashtable)都实现了一个名为elements()的方法。这个Enumeration接口可以逐一走过此集合内的每个元素,而无需知道它们在集合内是如何被管理的。

在JDK后来的更新中,开始使用了Iterator迭代器接口,这个接口和枚举接口很像,都可以让你遍历此集合类型内的每个元素,但不同的是,迭代器提供了删除元素的功能。

将枚举Enumeration适配到迭代器中

看看两个接口的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Enumeration<E> {
/**
* Tests if this enumeration contains more elements.
*
* @return <code>true</code> if and only if this enumeration object
* contains at least one more element to provide;
* <code>false</code> otherwise.
*/
boolean hasMoreElements();

/**
* Returns the next element of this enumeration if this enumeration
* object has at least one more element to provide.
*
* @return the next element of this enumeration.
* @exception NoSuchElementException if no more elements exist.
*/
E nextElement();
}
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public interface Iterator<E> {
/**
* Returns {@code true} if the iteration has more elements.
* (In other words, returns {@code true} if {@link #next} would
* return an element rather than throwing an exception.)
*
* @return {@code true} if the iteration has more elements
*/
boolean hasNext();

/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration
* @throws NoSuchElementException if the iteration has no more elements
*/
E next();

/**
* Removes from the underlying collection the last element returned
* by this iterator (optional operation). This method can be called
* only once per call to {@link #next}. The behavior of an iterator
* is unspecified if the underlying collection is modified while the
* iteration is in progress in any way other than by calling this
* method.
*
* @implSpec
* The default implementation throws an instance of
* {@link UnsupportedOperationException} and performs no other action.
*
* @throws UnsupportedOperationException if the {@code remove}
* operation is not supported by this iterator
*
* @throws IllegalStateException if the {@code next} method has not
* yet been called, or the {@code remove} method has already
* been called after the last call to the {@code next}
* method
*/
default void remove() {
throw new UnsupportedOperationException("remove");
}

/**
* Performs the given action for each remaining element until all elements
* have been processed or the action throws an exception. Actions are
* performed in the order of iteration, if that order is specified.
* Exceptions thrown by the action are relayed to the caller.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* while (hasNext())
* action.accept(next());
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}

设计适配器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.Enumeration;
import java.util.Iterator;

/**
* 枚举迭代器适配器
* @author Mr.zxb
* @date 2020-08-21 22:43:49
*/
public class EnumerationIterator<T> implements Iterator<T> {
private final Enumeration<T> enumeration;
public EnumerationIterator(Enumeration<T> enumeration) {
this.enumeration = enumeration;
}

@Override
public boolean hasNext() {
return enumeration.hasMoreElements();
}

@Override
public T next() {
return enumeration.nextElement();
}
}

总结

  • 当需要使用一个现有的类而其接口并不符合你的需要时,就使用适配器。
  • 适配器改变接口以符合客户的期望。
  • 实现一个适配器可能需要一番功夫,也可能不费功夫,视目标接口的大小与复杂度而定。
  • 适配器模式有两种形式:对象适配器和类适配器。类适配器需要用到多重继承。
  • 适配器将一个对象包装起来以改变其接口;装饰者将一个对象包装起来以增加新的行为和责任。