大家好✋,我是知识汲取者😄,今天给大家带来一篇有关适配器模式的学习笔记。众所周知能够熟练使用设计模式是一个优秀程序猿的必备技能,当我们在项目中选择一个或多个合适的设计模式,不仅能大大提高项目的稳健性、可移植性、可维护性,同时还能让你的代码更加精炼,具备艺术美感。
适配器相信大家在生活中一定有经常见到吧,手机的充电器的那个大头就是,它将220V电压转成了手机需要的电压,让手机能够适配220V电压,还有USB接口转接口也是,让不同的插头进行了适配。而在计算机世界中也存在适配器,比如在Java中适配器不是一个硬件,而是一个类,它能够让一个接口转成适配另一个类,从而通过这个接口调用被适配类中的方法,是不是感觉很神奇,现在就让我们开始学习吧(●ˇ∀ˇ●)
推荐阅读:
什么是适配器模式?
设配器模式(Adapter Pattern)属于结构型模式,它将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器模式(Wrapper Pattern)
适配器模式的作用:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
适配器模式的优缺点
优点:
……
缺点:
……
适配器模式的适用场景:
……
生活中的应用:手机充电器、USB转接口
Spring中的应用:Spring中的通知就有用到适配器模式,通知(Advice)使用了适配器AdviceAdapter
,这是适配器接口,对于每一个通知都拥它们各自的适配器,比如前置通知(BeforeAdvice)使用的适配器是BeforeAdviceAdapter
,被适配的对象是拦截器,因为通知的本质对请求进行拦截,然后利用代理模式添加扩展代码,增强程序的功能(具体可以去参考Spring的源码)
适配器模式角色划分:
适配器模式的分类:
示例:
问题描述:机器人拥有自己的行为,但是我们想要机器人能够拥有动物的行为
Step1:创建Adaptee
1)Dog:
package com.hhxy.adaptee.imp;
import com.hhxy.adaptee.Animal;
/**
* @author ghp
* @date 2022/9/30
* @title
* @description
*/
public class Bird implements Animal {
/**
* 鸟发出叫声的方法
*/
@Override
public void cry() {
System.out.println("鸟发出叽叽喳喳的叫声");
}
/**
* 鸟飞的方法
*/
@Override
public void run() {
System.out.println("鸟在天空中飞");
}
}
2)Bird:
package com.hhxy.adaptee.imp;
import com.hhxy.adaptee.Animal;
/**
* @author ghp
* @date 2022/9/30
* @title
* @description
*/
public class Dog implements Animal {
/**
* 狗发出叫声的方法
*/
@Override
public void cry() {
System.out.println("狗发出汪汪汪的叫声");
}
/**
* 狗跑的方法
*/
@Override
public void run() {
System.out.println("狗在地上跑");
}
}
Step2:创建Target
1)RobotClass:
package com.hhxy.target;
/**
* @author ghp
* @date 2022/9/30
* @title 机器人(用于类适配器模式)
* @description
*/
public interface RobotClass {
/**
* 机器人发声的方法
*/
void cry();
/**
* 机器人跑的方法
*/
void run();
}
2)RobotClassImp:
package com.hhxy.target.imp;
import com.hhxy.target.RobotClass;
/**
* @author ghp
* @date 2022/10/7
* @title 机器人的实现类(类适配器模式)
* @description
*/
public class RobotClassImp implements RobotClass {
/**
* 机器人发声的方法
*/
@Override
public void cry() {
System.out.println("机器人叫");
}
/**
* 机器人跑的方法
*/
@Override
public void run() {
System.out.println("机器人跑");
}
}
Step3:创建Adapter
1)DogAdapter:
package com.hhxy.adapter.classAdapter.imp;
import com.hhxy.target.RobotClass;
import com.hhxy.adaptee.imp.Dog;
/**
* @author ghp
* @date 2022/9/30
* @title 模仿狗行为的适配器
* @description 用于提供机器人模仿狗的行为的方法
*/
public class DogAdapter extends Dog implements Adapter,RobotClass {
/**
* 机器人模仿狗发声的方法
*/
@Override
public void cry() {
System.out.print("机器人模仿");
super.cry();
}
/**
* 机器人模仿狗跑的方法
*/
@Override
public void run() {
System.out.print("机器人模仿");
super.run();
}
}
2)BirdAdapter:
package com.hhxy.adapter.classAdapter.imp;
import com.hhxy.target.RobotClass;
import com.hhxy.adaptee.imp.Bird;
/**
* @author ghp
* @date 2022/9/30
* @title 模仿鸟行为的适配器
* @description 用于机器人模仿鸟行为的方法
*/
public class BirdAdapter extends Bird implements Adapter, RobotClass {
/**
* 机器人模仿鸟发声的方法
*/
@Override
public void cry() {
System.out.print("机器人模仿");
super.cry();
}
/**
* 机器人模仿鸟飞的方法
*/
@Override
public void run() {
System.out.print("机器人模仿");
super.run();
}
}
Step4:创建配置文件
class-adapter-config.xml:
这个配置文件主要是用来得到Adapter对象的,适用配置文件获取Adapter对象能够完美符合开闭原则,实现代码解耦,同时很方便测试、维护
<config>
<animal>com.hhxy.adapter.classAdapter.imp.DogAdapteranimal>
<animal>com.hhxy.adapter.classAdapter.imp.BirdAdapteranimal>
<animal>testanimal>
config>
Step5:编写配置文件读取类
ReadClassAdapterConfig:
package com.hhxy.read;
import com.hhxy.target.RobotClass;
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.lang.reflect.Constructor;
/**
* @author ghp
* @date 2022/9/30
* @title
* @description 用于读取配置文件并获取一个适配器对象(类适配器模式使用)
*/
public class ReadClassAdapterConfig {
public static RobotClass getRobot(){
try{
//1、将配置文件加载到内存中,获取DOM对象
//1.1 获取DOM解析器工厂对象DocumentBuilderFactory,用于创建DOM解析器
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//1.2 获取DOM解析器DocumentBuilder
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//1.3 加载配置文件
// Document document = documentBuilder.parse(new FileInputStream("day05_Adapter/src/class-adapter-config.xml"));
//让代码和模块名进行解耦,比上面那种方法更优
Document document = documentBuilder.parse(ReadClassAdapterConfig.class.getResourceAsStream("/class-adapter-config.xml"));
//2、获取配置文件中的数据
//2.1 从DOM中获取指定的结点的结点列表
NodeList nodeList = document.getElementsByTagName("adapterName");
//2.2 获取指定位置的结点
Node classNode = nodeList.item(1).getFirstChild();
//2.3 获取指定结点中的数据(排除空格)
String adapterName = classNode.getNodeValue().trim();
//3、使用反射获取获取Adapter对象
//3.1 获取类对象
Class cls = Class.forName(adapterName);
//3.2 获取该类对象的构造器对象
Constructor constructor = cls.getDeclaredConstructor();
//3.3 暴力反射,防止构造器私有化导致无法创建对象
constructor.setAccessible(true);
//3.4 获取配置器对象(使用多态的方式,提高兼容性)
RobotClass robot = (RobotClass) constructor.newInstance();
//4、返回通过配置文件获取的配置器对象
return robot;
} catch (Exception e) {
//如果异常就打印异常信息,同时返回一个空
e.printStackTrace();
throw new RuntimeException("未找到该适配器,请检查配置文件或者添加一个适配器!");
}
}
}
Step6:测试
package com.hhxy.test;
import com.hhxy.read.ReadClassAdapterConfig;
import com.hhxy.target.RobotClass;
import com.hhxy.target.imp.RobotClassImp;
/**
* @author ghp
* @date 2022/9/30
* @title 测试类1
* @description 用于测试类适配器
*/
public class Test1 {
public static void main(String[] args) {
/*
方式一:直接new
//机器人自己的行为
RobotClass robot = new RobotClassImp();
robot.cry();
robot.run();
System.out.println("-----------------------");
//机器人使用了适配器后能拥有狗的行为
RobotClass robotDog = new DogAdapter();
robotDog.cry();
robotDog.run();
System.out.println("-----------------------");
*/
//方式二:通过读取配置文件获取适配器对象,实现了解耦,同时也很方便测试
//机器人自己的行为
RobotClass robot = new RobotClassImp();
robot.cry();
robot.run();
System.out.println("-----------------------");
//机器人使用了适配器后能拥有动物的行为(如果想要让机器人换种动物模仿,只需要修改配置文件或者配置文件读取类)
RobotClass robotAnimal = ReadClassAdapterConfig.getRobot();
robotAnimal.cry();
robotAnimal.run();
}
}
测试结果:
可以查看以下执行方法时的时序图加深理解:
从上面可以看到DogAdapter,就是Robot和Dog的转接口,它将Dog的行为给了Robot,让Robot能够具有Dog的行为,这和我们生活中的适配器是同样的效果,我们手机的充电器就是一个适配器,它能够将220V的电压转成我们手机所需要的电压,这里DogAdapter也是一样,将Dog的行为转成了Robot的行为,而它实现的基础是Java三大基本特性之一的多态
示例:
问题描述:机器人拥有自己的行为,但是我们想要机器人能够拥有动物的行为
Step1:创建Adaptee
和类适配器中的代码一致,略……
Step2:创建Target
和类适配器的代码类似,略…
Step3:创建Adapter
AnimalAdapter:
之前需要使用BirdAdapter和DogAdapter两个适配器,而现在只需要一个适配器就能实现类适配器模式两个适配器的作用了,大大地提高了代码的复用
package com.hhxy.adapter.objectAdapter;
import com.hhxy.adaptee.Animal;
import com.hhxy.target.RobotObject;
/**
* @author ghp
* @date 2022/9/30
* @title 对象适配器模式
* @description 用于模仿动物的行为
*/
public class AnimalAdapter implements RobotObject {
private Animal animal;
public AnimalAdapter(Animal animal) {
this.animal = animal;
}
/**
* 机器人发声的方法
*/
@Override
public void cry() {
System.out.print("机器人模仿");
animal.cry();
}
/**
* 机器人跑的方法
*/
@Override
public void run() {
System.out.print("机器人模仿");
animal.run();
}
}
Step4:编写配置文件
object-adapter-config.xml:
<config>
<animalName>com.hhxy.adaptee.imp.BirdanimalName>
<animalName>com.hhxy.adaptee.imp.DoganimalName>
<animalName>testanimalName>
<adapterName>com.hhxy.adapter.objectAdapter.AnimalAdapteradapterName>
<adapterName>testadapterName>
config>
Step5:编写配置文件读取类
package com.hhxy.read;
import com.hhxy.adaptee.Animal;
import com.hhxy.target.RobotObject;
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.lang.reflect.Constructor;
/**
* @author ghp
* @date 2022/10/7
* @title
* @description 用于读取配置文件并获取一个适配器对象(对象配器模式使用)
*/
public class ReadObjectAdapterConfig {
public static RobotObject getRobot() {
try {
//1、将配置文件加载到内存中,获取DOM对象
//1.1 获取DOM解析器工厂对象DocumentBuilderFactory,用于创建DOM解析器
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
//1.2 获取DOM解析器DocumentBuilder
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
//1.3 加载配置文件
// Document document = documentBuilder.parse(new FileInputStream("day05_Adapter/src/class-adapter-config.xml"));
//让代码和模块名进行解耦,比上面那种方法更优
Document document = documentBuilder.parse(ReadClassAdapterConfig.class.getResourceAsStream("/object-adapter-config.xml"));
//2、获取配置文件中的数据
//2.1 从DOM中获取指定的结点的结点列表
NodeList nodeListAnimal = document.getElementsByTagName("animalName");
NodeList nodeListAdapter = document.getElementsByTagName("adapterName");
//2.2 获取指定位置的结点
Node classNodeAnimal = nodeListAnimal.item(0).getFirstChild();
Node classNodeAdapter = nodeListAdapter.item(0).getFirstChild();
//2.3 获取指定结点中的数据(排除空格)
String animalName = classNodeAnimal.getNodeValue().trim();
String adapterName = classNodeAdapter.getNodeValue().trim();
//3、使用反射获取获取Animal对象
//3.1 获取类对象
Class clsAnimal = Class.forName(animalName);
Class clsAdapter = Class.forName(adapterName);
//3.2 获取该类对象的构造器对象
Constructor constructorAnimal = clsAnimal.getDeclaredConstructor();
Constructor constructorAdapter = clsAdapter.getDeclaredConstructor(Animal.class);
//3.3 暴力反射,防止构造器私有化导致无法创建对象
constructorAnimal.setAccessible(true);
constructorAdapter.setAccessible(true);
//3.4 获取配置器对象(使用多态的方式,提高兼容性)
Animal animal = (Animal) constructorAnimal.newInstance();
RobotObject robot = (RobotObject) constructorAdapter.newInstance(animal);
//4、返回通过配置文件获取的配置器对象
return robot;
} catch (Exception e) {
//如果异常就打印异常信息,同时返回一个空
e.printStackTrace();
throw new RuntimeException("未找到该适配器,请检查配置文件或者添加一个适配器!");
}
}
}
Step6:测试
package com.hhxy.test;
import com.hhxy.read.ReadObjectAdapterConfig;
import com.hhxy.target.RobotObject;
import com.hhxy.target.imp.RobotObjectImp;
/**
* @author ghp
* @date 2022/10/7
* @title 测试类2
* @description 用于测试对象适配器模式
*/
public class Test2 {
public static void main(String[] args) {
/*
方式一:通过new获取适配器对象
//机器人自己的行为
RobotObject robot = new RobotObjectImp();
robot.cry();
robot.run();
System.out.println("-----------------------");
//机器人使用了适配器后能拥有鸟的行为
RobotObject robotAnimal = new AnimalAdapter(new Dog());
robotAnimal.cry();
robotAnimal.run();
*/
//方式二:通过读取配置文件获取适配器对象,实现了解耦,同时也很方便测试
//机器人自己的行为
RobotObject robot = new RobotObjectImp();
robot.cry();
robot.run();
System.out.println("-----------------------");
RobotObject robotAnimal = ReadObjectAdapterConfig.getRobot();
robotAnimal.cry();
robotAnimal.run();
}
}
测试结果:
示例:
问题描述:我们想要机器人拥有Bird的cry行为,同时拥有Dog的run行为
思考:如果使用第一种方式需要同时创建两个机器人(RobtClass)对象,显然不符合题目描述;使用第二种方式,同样的也需要创建两个机器人(RobotObject)对象,但是可以通过改造Adapter来实现,我们可以通过在Adapter中直接创建一个Dog、Bird对象,然后通过两个调用来实现,这是一种实现方式。
PS:在这里显然是无法体现到接口适配器的厉害之处的,这是因为在Robot接口存在的方法太少了,在实际项目中一般Target接口中会存在大量的方法(实际项目中这种适配器模式是用的最多的!),而有时我们只想使用Adapter让Traget的部分方法与Adaptee进行适配,这时候如果来使用前面两种方式实现Adapter,就需要实现接口的所有方法,这是因为实现类都必须实现接口中的所有的方法,这就导致Adapter显得特别臃肿、混乱。
这个时候就可以使用接口适配器方式来实现了(●ˇ∀ˇ●)
Step1:创建Adaptee
上同,略……
Step2:创建Target
RobotInterface:
package com.hhxy.target;
/**
* @author ghp
* @date 2022/10/7
* @title 机器人(拥有接口适配器)
* @description
*/
public interface RobotInterface {
/**
* 机器人发声的方法
*/
void cry();
/**
* 机器人跑的方法
*/
void run();
/**
* 模拟Target中一些不需要进行适配的方法
*/
void a();
void b();
void c();
void d();
}
Step3:创建Adapter
1)AbstractAdapter
package com.hhxy.adapter.interfaceAdapter;
import com.hhxy.target.RobotInterface;
/**
* @author ghp
* @date 2022/9/30
* @title
* @description
*/
public abstract class AbstractAdapter implements RobotInterface {
/**
* 机器人发声的方法
*/
@Override
public void cry() {
}
/**
* 机器人跑的方法
*/
@Override
public void run() {
}
/**
* 模拟Target中一些不需要进行适配的方法
*/
@Override
public void a() {
}
@Override
public void b() {
}
@Override
public void c() {
}
@Override
public void d() {
}
}
2)AnimalAdapter
package com.hhxy.adapter.interfaceAdapter;
import com.hhxy.adaptee.Animal;
import com.hhxy.adaptee.imp.Bird;
import com.hhxy.adaptee.imp.Dog;
/**
* @author ghp
* @date 2022/10/7
* @title
* @description
*/
public class AnimalAdapter extends AbstractAdapter{
private Animal dog = new Dog();
private Animal bird = new Bird();
/**
* 机器人发声的方法
*/
@Override
public void cry() {
System.out.println("机器人模仿");
bird.cry();
}
/**
* 机器人跑的方法
*/
@Override
public void run() {
dog.run();
}
}
Step4:编写配置文件
略……
Step5:编写配置文件读取类
略……
Step6:测试
package com.hhxy.test;
import com.hhxy.adapter.interfaceAdapter.AnimalAdapter;
import com.hhxy.target.RobotInterface;
/**
* @author ghp
* @date 2022/10/7
* @title
* @description
*/
public class Test3 {
public static void main(String[] args) {
//方式一:通过new获取适配器对象
RobotInterface robot = new AnimalAdapter();
robot.cry();
robot.run();
//方式二:通过读取配置文件获取适配器对象,实现了解耦,同时也很方便测试
//略……
}
}
继承+实现
的方式完成适配器,它拥有适配器的一般性功能,但是不符合“多用组合,少用继承”原则,导致系统具有较高复杂度,同时也限制了Target的类别组合+实现
的方式完成适配器,它不仅拥有适配器的一般性功能,还满足了“多用组合,少用继承”原则,降低了系统复杂度,提高了程序的灵活性,但是由于它是直接实现Target接口,这就导致要适配器要实现Target的所有方法,包括一些无需适配的方法,这就会导致适配器很臃肿、混乱,所以面对一些大型系统就显得有点吃力总的而言,三种适配器模式从上而下使用范围逐渐扩大,灵活性也逐渐扩大
自此,文章就结束了,如果觉得本文对你有一丢丢帮助的话😄,欢迎点赞👍+评论✍,您的支持将是我写出更加优秀文章的动力O(∩_∩)O
上一篇:每日一个设计模式之【建造者模式】
下一篇:每日一个设计模式之【桥接模式】
参考文章:
- 适配器模式 | 菜鸟教程
- Java设计模式之结构型:适配器模式
- 【源码分析设计模式 10】SpringMVC中的适配器模式
- 设计模式-适配器模式(类适配器、对象适配器、接口适配器详解)_
- 计算机术语,“对…是透明的”怎样理解?
在次致谢
透明度:类中行为对于使用者的可见度,透明度越高,使用者越难以了解类的行为 ↩︎