Adapter
适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。
适配器模式结构
Object adapter
Class adapter
Java 核心程序库中有一些标准的适配器:
- java.util.Arrays#asList()
- java.util.Collections#list()
- java.util.Collections#enumeration()
- java.io.InputStreamReader(InputStream) (返回
Reader
对象) - java.io.OutputStreamWriter(OutputStream) (返回
Writer
对象) - javax.xml.bind.annotation.adapters.XmlAdapter#marshal() 和
#unmarshal()
识别方法: 适配器可以通过以不同抽象或接口类型实例为参数的构造函数来识别。 当适配器的任何方法被调用时, 它会将参数转换为合适的格式, 然后将调用定向到其封装对象中的一个或多个方法。
样例
让方钉适配圆孔
这个简单的例子展示了适配器如何让不兼容的对象相互合作。
// 假设你有两个接口相互兼容的类:圆孔(RoundHole)和圆钉(RoundPeg)。
class RoundHole is
constructor RoundHole(radius) { ... }
method getRadius() is
// 返回孔的半径。
method fits(peg: RoundPeg) is
return this.getRadius() >= peg.getRadius()
class RoundPeg is
constructor RoundPeg(radius) { ... }
method getRadius() is
// 返回钉子的半径。
// 但还有一个不兼容的类:方钉(SquarePeg)。
class SquarePeg is
constructor SquarePeg(width) { ... }
method getWidth() is
// 返回方钉的宽度。
适配器代码:
package structural.adapter;
import structural.adapter.round.RoundPeg;
import structural.adapter.square.SquarePeg;
/**
* Adapter allows fitting square pegs into hound holes.
*/
public class SquarePegAdapter extends RoundPeg {
private SquarePeg peg;
public SquarePegAdapter(SquarePeg peg) {
this.peg = peg;
}
@Override
public double getRadius() {
double result;
// Calculate a minimum circle radius, which can fit this peg.
result = (Math.sqrt(Math.pow((peg.getWidth() / 2), 2) * 2));
return result;
}
}
测试:
package structural.adapter;
import structural.adapter.round.RoundHole;
import structural.adapter.round.RoundPeg;
import structural.adapter.square.SquarePeg;
public class Demo {
public static void main(String[] args){
// Round fits round , no surprise.
RoundHole hole = new RoundHole(5);
RoundPeg rPeg = new RoundPeg(5);
if(hole.fits(rPeg)){
System.out.println("Round peg r5 fits round hole r5.");
}
SquarePeg smallSqPeg = new SquarePeg(2);
SquarePeg largeSqPeg = new SquarePeg(20);
//hole.fits(smallSqPeg); // won't compile;
//Adapter solves the problem.
SquarePegAdapter smallSqPegAdapter = new SquarePegAdapter(smallSqPeg);
SquarePegAdapter largeSqPegAdapter = new SquarePegAdapter(largeSqPeg);
if(hole.fits(smallSqPegAdapter)){
System.out.println("Square peg w2 fits round hole r5.");
}
if(!hole.fits(largeSqPegAdapter)){
System.out.println("Square peg w20 does not fits into round hole r5.");
}
}
}
// 输出:
// Round peg r5 fits round hole r5.
// Square peg w2 fits round hole r5.
// Square peg w20 does not fits into round hole r5.
适用场景
-
当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类。
适配器模式允许你创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。
-
如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。
你可以扩展每个子类, 将缺少的功能添加到新的子类中。 但是, 你必须在所有新子类中重复添加这些代码, 这样会使得代码有坏味道
将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。 然后你可以将缺少功能的对象封装在适配器中, 从而动态地获取所需功能。 如要这一点正常运作, 目标类必须要有通用接口, 适配器的成员变量应当遵循该通用接口。 这种方式同装饰模式非常相似。
实现方式
- 确保至少有两个类的接口不兼容:
- 一个无法修改 (通常是第三方、 遗留系统或者存在众多已有依赖的类) 的功能性服务类。
- 一个或多个将受益于使用服务类的客户端类。
- 声明客户端接口, 描述客户端如何与服务交互。
- 创建遵循客户端接口的适配器类。 所有方法暂时都为空。
- 在适配器类中添加一个成员变量用于保存对于服务对象的引用。 通常情况下会通过构造函数对该成员变量进行初始化, 但有时在调用其方法时将该变量传递给适配器会更方便。
- 依次实现适配器类客户端接口的所有方法。 适配器会将实际工作委派给服务对象, 自身只负责接口或数据格式的转换。
- 客户端必须通过客户端接口使用适配器。 这样一来, 你就可以在不影响客户端代码的情况下修改或扩展适配器。
适配器模式优点
- 单一职责原则。你可以将接口或数据转换代码从程序主要业务逻辑中分离。
- 开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。
适配器模式缺点
- 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。