1. 介绍
1.1 模式说明
定义一个包装类,用于包装不兼容接口的对象。
- 包装类 = 适配器Adapter;
- 被包装对象 = 适配者Adaptee = 被适配的类。
1.2 定义
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式的两种形式:类的适配器模式 & 对象的适配器模式
2. 类的适配器模式
2.1 UML类图 & 组成讲解
- Target:期待得到的接口(目标角色)
- ConcreteTarget:具体目标类,具有目标的普通功能
- Adaptee:需要适配的接口(源角色)
- Adapter:把源接口转换成目标接口(适配器角色)
2.2 解释说明
在类的UML类图可以看出:
- 冲突:Target期待调用Request方法,而Adaptee并没有(这就是所谓的不兼容了)。
- 解决方案:为使Target能够使用Adaptee类里的SpecificRequest方法,故提供一个中间环节Adapter类(继承Adaptee & 实现Target接口),把Adaptee的API与Target的API衔接起来(适配)。
Adapter与Adaptee是继承关系,这决定了这个适配器模式是类的适配器模式。
2.3 使用步骤(代码解析)
步骤1: 创建Target接口
public interface Target {
//这是源类Adapteee没有的方法
void Request();
}
步骤2: 创建源类(Adaptee)
public class Adaptee {
public void SpecificRequest(){
System.out.println("源类方法");
}
}
步骤3: 创建适配器类(Adapter)
//适配器Adapter继承自Adaptee,同时又实现了目标(Target)接口
public class Adapter extends Adaptee implements Target {
//目标接口要求调用Request()这个方法名,但源类Adaptee没有方法Request()
//因此适配器补充上这个方法
//但实际上Request()还是调用的源类Adaptee的SpecificRequest()的方法
//所以适配器只是将SpecificRequest()方法作了一层包装处理,兼容成了可以调用的Request()的Target而已
@Override
public void Request() {
this.SpecificRequest();
}
}
步骤4:定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标
public class AdapterPattern {
public static void main(String[] args){
Target mAdapter = new Adapter();
mAdapter.Request();//会打印输出:源类方法
}
}
2.4 实例讲解
接下来我用一个实例来对类的适配器模式进行更深一步的介绍。
a. 实例概况
- 背景:小成买了一个进口的电视机。
- 冲突:进口电视机要求电压(110V)与国内插头标准输出电压(220V)不兼容。
- 解决方案:通过适配器将插头输出的220V转变成110V。
b. 使用步骤
步骤1: 创建Target接口(期待得到的插头):能输出110V电压
public interface Target {
//将220V转换输出110V方法
void Convert_110v();
}
步骤2: 创建源类(国内输出220V电压的插头)
public class PowerPort220V {
//国内输出220V电压的插头
public void Output_220v(){}
}
步骤3: 创建适配器类(Adapter)
public class Adapter220V extends PowerPort220V implements Target {
@Override
public void Convert_110v() {
this.Output_220v();
}
}
步骤4: 进口电视机类
public class ImportedTv {
//进口电视机需要传入目标110V电压进行供电才能正常运行
public void Work(Target target) {
System.out.println("进口电视正常运行");
}
}
步骤5: 使用
public class AdapterPattern {
public static void main(String[] args){
Target mAdapter220V = new Adapter220V();
ImportedTv mImportedTv = new ImportedTv();
//用户拿着进口机器插上适配器(调用Convert_110v()方法)
//再将适配器插上原有插头(Convert_110v()方法内部调用Output_220v()方法)
//适配器只是对国内220V电压进行转换,转换成进口电视机能正常使用的110V电压,但本质还是使用国内220V进行供电
mAdapter220V.Convert_110v();
mImportedTv.Work(mAdapter220V);//会打印输出:进口电视正常运行
}
}
3. 对象的适配器模式
与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee类,而是使用委派关系连接到Adaptee类。
3.1 UML类图
Adapter与Adaptee是委派关系,即通过在Adapter的构造方法中传入具体需要适配的对象Adaptee,这决定了适配器模式是对象的适配器模式。
3.2 使用步骤(代码解析)
步骤1: 创建Target接口
public interface Target {
//这是源类Adapteee没有的方法
void Request();
}
步骤2: 创建源类(Adaptee)
public class Adaptee {
public void SpecificRequest(){
System.out.println("源类方法");
}
}
步骤3: 创建适配器类(Adapter)(不再用继承而是委派)
public class Adapter implements Target {
// 直接关联被适配类
private Adaptee adaptee;
//通过构造函数传入具体需要适配的对象
public Adapter (Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void Request() {
// 这里是使用委派的方式完成特殊功能
this.adaptee.SpecificRequest();
}
}
步骤4:定义具体使用目标类,并通过Adapter类调用所需要的方法从而实现目标
public class AdapterPattern {
public static void main(String[] args){
Target mAdapter = new Adapter(new Adaptee());
mAdapter.Request();//会打印输出:源类方法
}
}
4. 优缺点和两种方式的适配器比较
4.1 优点
- 更好的复用性:系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。
- 透明、简单:客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单 & 更直接
- 更好的扩展性:在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
- 解耦性:将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码
- 符合开放-关闭原则:同一个适配器可以把适配者类和它的子类都适配到目标接口;可以为不同的目标接口实现不同的适配器,而不需要修改待适配类
4.2 缺点
- 过多的使用适配器,会让系统非常零乱,不易整体进行把握。
4.3 比较
- 类的适配器模式使用方便,代码简化;而对象的适配器模式使用复杂,需要引入对象实例。
- 类的适配器模式高耦合,灵活性低,使用对象继承的方式,是静态的定义方式;而对象的适配器模式灵活性高、低耦合,采用 “对象组合”的方式,是动态组合方式。
5. 使用建议
建议尽量使用对象的适配器模式,多用合成/聚合、少用继承。