适配器模式
概述
《设计模式》一书中对 “适配器模式” 的动机描述如下:
将一个类的接口转换成客户希望的另一个接口,适配器模式使得原本由于接口不兼容而不能一起工作的类可以一起工作
一般情况下的类结构图如下所示:
每个组件的介绍如下:
Client
:实际调用目标接口的对象Target
:当前设计的用于客户端调用的接口类型Adaptee
:已经存在的类,但是和Target
需要的接口不同Adapter
:组合Adaptee
,重写Target
中定义的接口,使得Adapter
能够使用Adaptee
来完成Target
需要的功能
一般在以下几种情况中会考虑使用适配器模式:
- 希望使用一个已经存在的类,但是它的接口不符合目前的要求
- 希望创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作
- 希望使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口,在这种情况下可以使得
Adpater
适配这些子类的公有接口
具体实例
假设现在我们的系统现在需要创建有关交通工具的使用情况统计,我们需要一个如下的类型:
public interface Transportation {
String name(); // 交通工具名称
int walkType(); // 行走方式,空中、陆地、水
double costForYear(); // 每年的消耗金额
}
假设我们的系统已经通过该接口完成了对应的统计需求,现在需要具体化相关的子类。比如,如果我们需要统计飞机的使用情况,那么可以定义相关的飞机子类:
public class Airplane implements Transportation {
@Override
public String name() {
return "airplane";
}
@Override
public int walkType() {
return 0;
}
@Override
public double costForYear() {
return 350000.00;
}
}
然而,现在已有的系统中已经存在了名为 Tractor
的交通工具,但是它的定义如下:
import java.math.BigDecimal;
public class Tractor {
public String type() {
return "tractor";
}
public BigDecimal costForDay() { // 拖拉机每天的消耗金额
return BigDecimal.valueOf(15);
}
public int dayOfYear() { // 拖拉机一年的工作天数
return 200;
}
}
显然,现在系统中存在的 Tractor
无法与我们实现的交通工具统计协作,因为它不是 Transportation
类型的交通工具。一种方式是找到 Tractor
的源代码,强制其实现 Transportation
接口以符合当下系统的需求,但是这并不是合理的解决方案,因为在实际使用中,很大概率无法直接修改源代码。为此,我们可以创建一个适配器类 TractorTransportationAdapter
,以协调两者之间的关系:
public class TractorTransportationAdapter implements Transportation {
private final Tractor tractor;
public TractorTransportationAdapter(Tractor tractor) {
this.tractor = tractor;
}
@Override
public String name() {
return tractor.type();
}
@Override
public int walkType() {
return 1;
}
@Override
public double costForYear() {
return BigDecimal.valueOf(tractor.dayOfYear())
.multiply(tractor.costForDay() == null ? BigDecimal.valueOf(0) : tractor.costForDay())
.doubleValue();
}
}
对于 ”统计交通工具“ 部分的逻辑来讲,现在只需要将 TractorTransportationAdapter
替换现有的 Tractor
作为对应的角色即可使得两者之间能够协同工作
参考:
[1] 《设计模式—可复用面向对象基础》