设计模式:适配器模式

适配器模式

提出问题

  • 将类的接口转换为想要的接口,以便实现不同的接口。两个对象因为接口不兼容而不能一起工作,这时需要第三方适配。
  • 适配器:我拥有一个三角插头,而只有一个二角的插座,则我需要一个适配器,让我能够重新充电

为什么要用(作用)

  • 将一个类的接口转换为客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。
  • 为复用而设计的工具箱类不能够被复用的原因仅仅是因为它的接口与专业应用领域所需要的接口不匹配。

应用

适用性

  • 你想使用一个已经存在的类,而它的接口不符合你的需求
  • 你想创建一个可以复用的类,该类可以与其他不想关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作
  • (仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配他们的接口。对象适配器可以适配它的父类接口(什么意思

案例

  • 已有一个软件系统,希望能够与新厂商类库搭配,但是新厂商的接口不同于旧厂的接口
    • 写一个类,将新厂商的接口转换为期望的接口
  • 一个应用可能会具有一些类具有不同的接口,并且这些接口互不兼容,像TextView这样已经存在并且不相关的类如何协同工作呢?
    • 即是我们得到了源代码,修改textView意义也不大,因为不应该仅仅为了实现一个应用,工具箱就不得不采用一些与特定领域相关的接口
    • 因此我们可以定义一个新的类,进行适配接口,从而实现最终目标

基础概述

是什么

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

作为适配器,它还可以提供原系统接口所没有的功能,在原接口的基础上进行拓展

分类

优缺

  • 如果需要实现一个很大的目标接口,要有很多的工作要做
    • 实现一个适配器所需要进行的工作与目标接口的大小成正比
    • 但如果不适用适配器,则需要花更多的力气进行改写工作。即复用了现存的类。
  • 客户端通过适配器可以透明地调用目标接口。
  • 将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
  • 一个适配器可以封装一个或多个类

协作

参与者

  • Client。与符合target接口的对象协同
  • Target。是客户需要使用的接口,定义客户使用的与特定领域相关的接口
  • Adapter。是对Target接口的实现,并且它将原有接口Adaptee转换到了所需要的接口Target
  • Adaptee。系统当中原有的功能接口,这个接口需要被适配

协作

Client在Adapter实例上调用一些操作,接着适配器调用Adapter的操作实现这个请求

权衡

考虑Adapter的匹配程度

对Adaptee的接口与Target接口进行匹配的工作量各个Adapter可能不一样。可能是从简单的接口转换到支持完全不同的操作集合,其取决于Target接口与Adaptee接口的相似程度。

可插入的Adapter

当其他的类使用一个类时,如果所需要的假定条件越少,这个类就更具可复用性。如果将接口匹配构建一个类,就不需要假定对其他的类可见的是相同的一个接口。即接口匹配使得我们可以将自己的类加入到一些现有的系统中去,而这些系统对这个类的接口可能会有不同

使用双向适配器提供透明操作

适配器并不对所有的客户都透明;被适配的对象不再兼容Adaptee的接口,因此并不是所有Adaptee对象都可以被使用的地方它都可以使用。客户可以使用Adaptee对象的接口,但是Adaptee不能使用Target的接口

双向适配器提供了这样的透明性。在两个不同的客户需要用不同的方式查看同一个对象时,尤其有用

类适配器

结构

使多重继承对一个接口与另外一个接口进行匹配

1555665102661

效果

  • 用一个具体的Adapter类对Adaptee和Target进行匹配。结果是当我们想要匹配一个类以及它所有子类时,类Adapter不能胜任
  • Adapter可以重定义Adaptee的部分行为,因为是它的一个子类
  • 仅仅引入了一个对象,并不需要额外的指针以间接得到adaptee

实现

实现步骤

  • 继承原有接口进行扩展

  • 客户通过目标接口调用适配器的方法对适配器发出请求

  • 适配器使用被适配接口把请求转换为被适配者的一个或多个调用接口

  • 客户接受到调用的结果,但并未察觉到这一切是适配器再起转换作用

示例1

首先是Target接口,该接口有一个方法是request方法。但是有一个类Adaptee已经实现了request方法,因此可以去复用该方法

1
2
3
public interface Target {
public void request();
}

类Adaptee实现了Tagret类所希望实现的内容,但是Adaptee是没有实现Target接口的,因此Target无法做复用。

1
2
3
4
5
public class Adaptee {
public void specificRequest() {
System.out.println("适配者中的业务代码被调用!");
}
}

因此,这两个接口并不匹配。需要进行适配操作,即产生了Adapter。类适配器继承自Adaptee因此可以调用它的Adaptee方法。而同时也实现了Target接口,因此Target也可以使用request方法

1
2
3
4
5
6
public class ClassAdapter extends Adaptee implements Target {
@Override
public void request() {
specificRequest();
}
}

让Target进行调用,即让Target引用ClassAdapter,此时调用request时,也调用了specificRequest方法,因此实现了复用,即实现了适配器Adapter。

1
2
3
4
5
6
7
public class ClassAdapterTest {
public static void main(String[] args) {
System.out.println("类适配器模式测试:");
Target target = new ClassAdapter();
target.request();
}
}

对象适配器

结构

依赖于对象组合

1563087069455

效果

  • 允许一个Adapter与多个Adaptee——即本身以及它的所有子类同时工作。Adapter也可以一次给所有的Adaptee添加功能
  • 使得重定义Adaptee的行为比较困难,需要生成Adaptee的子类并且使得Adapter引用这个子类而不是Adaptee本身。

实现

实现步骤

  • 将原有接口的实例作为组成部分,进行组合

示例1

在对象适配器当中更多的是依赖于组合实现适配器。因此在该适配器当中有一个Adaptee成员,而初始化操作即为它分配一个Adaptee

1
2
3
4
5
6
7
8
9
10
11
12
public class ObjectAdapter implements Target {
private Adaptee adaptee;

public ObjectAdapter(Adaptee adaptee) {
this.adaptee = adaptee;
}

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

进行对象适配器的测试

1
2
3
4
5
6
7
8
public class ObjectAdapterTest {
public static void main(String[] args) {
System.out.println("对象适配器模式测试:");
Adaptee adaptee = new Adaptee();
Target target = new ObjectAdapter(adaptee);
target.request();
}
}

示例2

鸭子与火鸡:

Duck

是作为系统当中原有的的功能接口

1
2
3
4
5
6
public interface Duck {
public void quack();

public void fly();

}
1
2
3
4
5
6
7
8
9
10
11
12
public class MallerdDuck implements Duck {

@Override
public void quack() {
System.out.println("Quack");
}

@Override
public void fly() {
System.out.println("Flying");
}
}

Turkey

是系统当中新生的需求和实体

1
2
3
4
5
6
public interface Turkey {
public void gobble();

public void fly();

}
1
2
3
4
5
6
7
8
9
10
11
public class WildTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("Gobble");
}

@Override
public void fly() {
System.out.println("Fly short time");
}
}

适配器

新生的需求需要使用到原有的系统设计的功能,却接口不统一。通过适配器实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//实现了Duck接口
public class TurkeyAdapter implements Duck {
//内部还是turkey
Turkey turkey;

public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
//以turkey的方法转换到Duck的方法
@Override
public void quack() {
turkey.gobble();
}

@Override
public void fly() {
turkey.fly();
}
}

双向适配器

结构

效果

实现

实现步骤

示例1

target接口,即客户

1
2
3
public interface TwoWayTarget {
public void request();
}

adaptee接口,即被适配者

1
2
3
public interface TwoWayAdaptee {
public void specificRequest();
}

构建双向适配器,即适配器实现Target接口与Adaptee接口。因此被适配者与客户都可以去引用该适配器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TwoWayAdapter implements TwoWayTarget, TwoWayAdaptee {
private TwoWayTarget target;
private TwoWayAdaptee adaptee;

public TwoWayAdapter(TwoWayTarget target) {
this.target = target;
}

public TwoWayAdapter(TwoWayAdaptee adaptee) {
this.adaptee = adaptee;
}

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

@Override
public void specificRequest() {
target.request();
}
}

进行测试

1
2
3
4
5
6
7
8
9
10
11
12
public class TwoWayAdapterTest {
public static void main(String[] args) {
System.out.println("目标通过双向适配器访问适配者:");
TwoWayAdaptee adaptee = new AdapteeRealize();
TwoWayTarget target = new TwoWayAdapter(adaptee);
target.request();
System.out.println("适配者通过双向适配器访问目标:");
target = new TargetRealize();
adaptee = new TwoWayAdapter(target);
adaptee.specificRequest();
}
}

相关模式

  • Bridge的结构与对象适配器类似,但是Bridge模式的出发点不同。Bridge目的是将接口部分与实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而Adapter意味着改变一个已有对象的接口
  • Decorator模式增强了其他对象的功能而同时又不改变它的接口。因此Decorator对应用程序的透明性比适配器好。结果是Decorator支持递归组合,而纯粹使用适配器无法实现
  • Proxy在不改变它的接口的条件下,为另一个对象定义了一个代理

进阶

反省总结

  • 在工作的时候,其实很多接口因为版本迭代原因都会出现各种各样的版本,因为新接口、功能迁移,很有可能出现说新接口不兼容旧的接口,而新旧都有各自的引用,重构是十分困难的。
    • 使用适配器模式,你可以在不改变它的实现情况下,进行兼容扩展

参考