理解设计模式之适配器模式、桥接模式

两种模式都是结构型模式,而且从名字上,两种模式很容易混淆,都是中间搭桥去适配的感觉,但事实真的如此吗?
下面就来分析一下这两个模式。

数据适配

数据集合也是多样化的,但是数据常常以列表ListView形式展示,如何设计这样的ListView能支持所有的数据集合?
计算机科学领域的所有问题都可以通过增加一个间接中间层来解决,所以需要加一个层分别处理这些数据集合,而ListView依赖于这个层的抽象定义即可。
这个层就叫适配层,既要能对接一个ListView,也要不同类型数据集合注入到这层的具体实现类上:

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 interface ListAdapter {
int getCount();
Object getItem(int position);
View getView(int position, View convertView, ViewGroup parent);
}
// 适配层实现一
public class ArrayAdapter implements ListAdapter {
private List mObjects;
public ArrayAdapter(List objects) {
mObjects = objects;
}
// 实现ListAdapter接口,对List类型的mObjects进行处理
// ...
}
// 适配层实现二
public abstract class CursorAdapter implements ListAdapter {
protected Cursor mCursor;
public CursorAdapter(Cursor c) {
mCursor = c;
}
// 实现ListAdapter接口,对Cursor类型的mCursor进行处理
// ...
}

而ListView只需要依赖于抽象的ListAdapter:

1
2
3
4
5
// 由client自行负责注入具体Adapter
public class ListView {
ListAdapter mAdapter;
public void setAdapter(ListAdapter adapter) {}
}

增加和删除新的适配实现以支持新的数据集合,并不会对系统造成影响。
这就是适配器模式的最简单形态。

PS:以上代码并不与Android的ListView及其Adapter的具体代码完全一致,做了简化,但思想都是一致的。

布局扩展

更进一步,ListView展示也要多样化,既需要列表展示,还需要网格展示等等。
好吧,抽象一下ListView为AbsListView:

1
2
3
4
5
6
7
8
9
public abstract class AbsListView {
}
// 列表ListView
public class ListView extends AbsListView {
}
// 网格ListView
public class GridView extends AbsListView {
}
// WaterfallListView,3DListView ...

现在基于AbsListView,我们横向实现了数据适配,纵向实现了布局扩展,是不是更像Android里的ListView那一套了?
像这样既继承了某种抽象又组合了另外一种扩展的模式,就是桥梁模式。

PS:这里举的例子把两种模式混合在一起了,但实际上桥梁模式并不是建立在适配器模式的基础上,假如把ListAdapter换成其他任何一个非适配接口,那么不是适配器模式了,但这个AbsListView系统依然是桥梁模式。

适配器模式

将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

对象适配器

前面是对数据进行了适配,如果改为对行为的适配也是同一个道理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface ITarget {
void request();
}
// 对应前面数据集合的类型,但是不是数据而是行为了,特此说明
public class Adaptee {
public void specificRequest() {
}
}
public class Adapter implements ITarget {
private Adaptee adaptee;
public void request() {
adaptee.specificRequest();
}
}

这里的Adapter通过组合的方式引用了Adaptee对象,称为对象适配器模式,UML类图如下:
对象适配器UML类图

类适配器

如果Adpater不是组合Adaptee而是继承Adaptee来扩展Adaptee以支持新的接口,也能达到适配器模式的效果。

1
2
3
4
5
public class Adapter extends Adaptee implements ITarget {
public void request() {
super.specificRequest();
}
}

像这种通过继承Adaptee实现,称为类适配器模式。UML类图如下:
类适配器UML类图

可以看出,通过组合对象比继承实现的适配器模式耦合度更小,所以相对来说更推荐使用对象适配器模式。

桥接模式

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

桥接模式在两个维度上以不同的方式(组合、继承)实现了功能扩展,巧妙的结合在一起,非常具有面向对象的“喜感”。
如果你不能理解“抽象部分”和“实现部分”,就把它们当成两个不同领域的功能吧。
实现部分,需要抽象一下:

1
2
3
4
5
6
7
8
9
10
11
public interface IImplementor {
void operationImpl();
}
public class ImplementorA implements IImplementor {
public void operationImpl() {
}
}
public class ImplementorB implements IImplementor {
public void operationImpl() {
}
}

抽象部分,也需要抽象一下,并且把实现部分组合进来:

1
2
3
4
5
6
7
8
public abstract class Abstraction {
public IImplementor imp;
public void operation() {
imp.operationImpl();
}
}
public class RefineAbstraction extends Abstraction {
}

桥接模式展示了变化因素应该如何安排,UML类图如下:
桥接模式UML类图

另外,提两点注意:

  1. 桥接模式刚好分别用了组合、继承实现了一个二维的扩展,如果要扩充到三维,四维(就是需要支持新领域的扩展)如何处理呢?继承是无法再使用了,但是组合可以无线的添加,比如再组合IImplementor1, IImplementor2等等都是没有问题的。桥接模式已经包含了这种情况,这些IImplementor1,IImplementor2就是多个“实现部分”。显然,组合相比继承组装更轻量扩展更灵活。
  2. 当然可以纯用组合实现二维分离,但是系统往往已经存在大量的继承关系(当然这些继承是必须的),桥接模式将会比纯用组合代码简洁的多。

比较ListView和RecyclerView

说到这个是因为,有人站在ListView的基础上认为RecyclerView再搭配了LayoutManager是桥接模式的应用,但是并非如此。
从前面的分析可以看出,ListView模块是再经典不过的使用了桥接模式,然而RecyclerView模块只是纯粹的组合了LayoutManager,并没有使用桥接模式。
使用模式的多少和功能强大也是没有直接关系的,相对于ListView,RecyclerView的更加强大在于它分离了更多的变化便于扩展,增强了其高度定制性。

小结

本文从ListView需求切入,依次演绎出适配器模式和桥接模式。
经过分析,相信大家不仅能够区分它们,而且也能灵活的运用它们到你的工作中去了。