第9章 适配器模式
9.1 适配器模式概述
适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另一个接口。适配器模式让那些接口不兼容的类可以一起工作。
- 包装器模式(Wrapper)
- 类结构性模式 && 对象结构型模式
- 接口是广义的接口:方法或方法的集合
9.2 适配器模式结构与实现
9.3.1 适配器模式结构
- Target(目标抽象类):目标抽象类定义客户所需的接口,可以是一个抽象类或接口,也可以是具体类。在类适配器中,由于Java语言不支持多重继承,它只能是接口。
- Adapter(适配器类):它可以调用另一个接口,作为一个转换器。对Adaptee和Target进行适配。适配器Adapter是适配器模式的核心,在类适配器中,它通过实现Target接口并继承Adaptee类来使二者产生联系,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
- Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下甚至没有适配者类的源代码。
9.3.2 适配器模式实现
- 客户端需要调用request()
- 适配者类Adaptee没有,但提供的specificRequest()满足
- 为了使客户端能够使用Adaptee的specificRequest(),需要提供一个包装类Adapter。
类适配器
适配器类和适配者类继承关系。
public class Adapter extends Adaptee implements Target {
public void request() {
super.specificRequest();
}
}
对象适配器(使用频率更高)
适配器类和适配者类关联关系。
public class Adapter extends Target {
private Adaptee adaptee; //维持一个对适配者对象的引用
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest(); //转发调用
}
}
9.3 适配器模式应用实例
实例说明
某公司要开发一款儿童玩具汽车。为了更好地吸引小朋友的注意力,该玩具汽车在移动过程中伴随着灯光闪烁和声音提示。在该公司以往的产品中已经实现了控制灯光闪烁(例如警灯闪烁)和声音提示(例如警笛音效)的程序,为了重用先前的代码并且使汽车控制软件具有更好的灵活性和扩展性,现使用适配器模式设计该玩具汽车控制软件。
实例类图
- 抽象目标
- CarController
- 适配者
- PoliceSound
- PoliceLamp
- 适配器
- PoliceCarAdapter
实例代码
CarController:汽车控制类,充当目标抽象类。
package designpatterns.adapter;
public abstract class CarController {
public void move() {
System.out.println("玩具汽车移动!");
}
public abstract void phonate();//发出声音
public abstract void twinkle();//灯光闪烁
}
PoliceSound:警笛类,充当适配者。
package designpatterns.adapter;
public class PoliceSound {
public void alarmSound() {
System.out.println("发出警笛声音!");
}
}
PoliceLamp:警灯类,充当适配者。
package designpatterns.adapter;
public class PoliceLamp {
public void alarmLamp() {
System.out.println("呈现警灯闪烁! ");
}
}
PoliceCarAdapter:警车适配器,充当适配器。
package designpatterns.adapter;
public class PoliceCarAdapter extends CarController {
private PoliceSound sound;
private PoliceLamp lamp;
public PoliceCarAdapter() {
sound = new PoliceSound();
lamp = new PoliceLamp();
}
@Override
public void phonate() {
sound.alarmSound();
}
@Override
public void twinkle() {
lamp.alarmLamp();
}
}
配置文件config.xml,在配置文件中存储了适配器类的类名。
<?xml version="1.0"?>
<config>
<className>designpatterns.adapter.PoliceCarAdapter</className>
</config>
XMLUtil:工具类。
package designpatterns.adapter;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class XMLUtil {
//该方法用于从XML配置文件中提取具体类的类名,并返回一个实例对象
public static Object getBean() {
try {
//创建DOM文档对象
DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = dFactory.newDocumentBuilder();
Document doc;
doc = builder.parse(new File("src//designpatterns//adapter//config.xml"));
//获取包含类名的文本结点
NodeList nl = doc.getElementsByTagName("className");
Node classNode = nl.item(0).getFirstChild();
String cName = classNode.getNodeValue();
//通过类名生成实例对象并将其返回
Class c = Class.forName(cName);
Object obj = c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Client:客户端测试类。
package designpatterns.adapter;
public class Client {
public static void main(String[] args) {
CarController car;
car = (CarController) XMLUtil.getBean();
car.move();
car.phonate();
car.twinkle();
}
}
结果及分析
9.4 缺省适配器模式
缺省适配器模式(Default Adapter Pattern):当不需要实现一个接口提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中的每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求,它适用于不想使用一个接口中的所有方法的情况,又称为单接口适配器模式。
- ServiceInterface(适配者接口):它是一个接口,通常在该接口中声明了大量的方法。
- AbstractServiceClass(缺省适配器类):它是缺省适配器模式的核心类,使用空方法的形式实现了在ServiceInterface接口中声明的方法。通常将它定义为抽象类,因为对它进行实例化没有意义。
- ConcreteServiceClass(具体业务类):它是缺省适配器类的子类,在没有引入适配器之前它需要实现适配者接口,因此需要实现在适配者接口中定义的所有方法,而对于一些无须使用的方法不得不提供空实现。在有了缺省适配器之后可以直接继承该适配器类,根据需要有选择性地覆盖在适配器类中定义的方法。
AbstractServiceClass:
public abstract class AbstractServiceClass implements ServiceInterface {
public void serviceMethod1() {
}//空方法
public void serviceMethod2() {
}//空方法
public void serviceMethod3() {
}//空方法
}
举例:
JDK类库的时间处理包java.awt.event中广泛使用。
处理窗口事件中通过两种方式实现窗口时间处理类:
-
实现WindowListener接口
- 需要实现接口中的7个方法
- 需要提供空实现
-
继承WindowAdapter类
- 适配器类提供了7个方法的空实现
- 不需要提供空实现,只需要实现自己想要的方法
9.5 双向适配器
- ConcreteTarget→Target→Adapter→Adaptee→ConcreteAdaptee
- ConcreteAdaptee→Adaptee→Adapter→Target→ConcreteTarget
public class Adapter implements Target, Adaptee {
//同时维持对抽象目标类和适配者的引用
private Target target;
private Adaptee adaptee;
public Adapter(Target target) {
this.target = target;
}
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
public void specificRequest() {
target.request();
}
}
9.6 适配器模式优/缺点与适用环境
9.6.1 适配器模式优点
- 共同优点:
- 将目标类和适配者类解耦,通过引人一个适配器类来重用现有的适配者类,无须修改原有结构。
- 增加了类的透明性和复用性,将具体的业务实现过程封装在适配者类中,对于客户端类而言是透明的,而且提高了适配者的复用性,同一个适配者类可以在多个不同的系统中复用。
- 灵活性和扩展性都非常好,通过使用配置文件可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合开闭原则。
- 类适配器模式优点:
由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。 - 对象适配器模式优点:
- 一个对象适配器可以把多个不同的适配者适配到同一个目标。
- 可以适配一个适配者的子类,由于适配器和适配者之间是关联关系,根据里氏代换原则,适配者的子类也可通过该适配器进行适配。
9.6.2 适配器模式缺点
- 类适配器模式缺点:
- 对于Java,C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者。
- 适配者类不能为最终类,例如在Java中不能为final类。
- 在Java,C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。
- 对象适配器模式缺点:
与类适配器模式相比,在该模式下要在适配器中置换适配者类的某些方法比较麻烦。如果一定要置换掉适配者类的一个或多个方法,可以先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当成真正的适配者进行适配,实现过程较为复杂。
9.6.3 适配器模式适用环境
- 系统需要使用一些现有的类,而这些类的接口(例如方法名)不符合系统的需要,甚至没有这些类的源代码。
- 想创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类(包括一些可能在将来引进的类)一起工作。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律