前言
📣 📣 📣 📢📢📢
☀️☀️点开就是缘分认识一下,我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。
📒 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️,擅长使用Java技术开发web项目和工具
📒 文章内容丰富:覆盖大部分java必学技术栈,前端,计算机基础,容器等方面的文章
📒 如果你也对Java感兴趣,关注小冷吧,一起探索Java技术的生态与进步,一起讨论Java技术的使用与学习
✏️高质量技术专栏专栏链接: 微服务,数据结构,netty,单点登录,SSM ,SpringCloudAlibaba等
😝公众号😝 : 想全栈的小冷,分享一些技术上的文章,以及解决问题的经验
⏩当前专栏:设计模式系列
⏩专栏代码地址: 代码地址
适配器模式 Aadpter
这个模式其实在生活中很好就能找到案例:
比如我们的笔记本电脑 都会有一个电源适配器来基于我们笔记本进行供电。
在程序中,经常会现有的程序无法直接使用,会出现需要做适当的变化之后才可以使用到情况。折中用于填补现有程序和所需程序之间差异的设计模式就是Adapter模式
Adapter模式也被称为Wrapper模式,wrapper有包装器的意思,就像用精美的包装纸把商品包装成礼物那样,替我们把代码封装起来,使其可以用于其他用途
Adapter模式有以下两种:
- 类适配器模式(使用继承的适配器)
- 对象适配器模式(使用委托的适配器)
类适配器模式
实现思路
首先,让我们来看一段使用继承的适配器的示例程序。这里的示例程序是一段会将输入的字符
串显示为(Hello)或是&Hello&的简单程序。
目前在Banner类中,有将字符用括号括起来的showWithParen方法,和将字符串用&号括起来的showwithAster方法
假设Print接口中声明了两种方法,即字符串显示(加括号)的printWeak方法,和调字符串显示(加&号)的printstrong方法。
现在要做的事情是使用Banner类编写一个实现了Print接口的类,也就是说要做一个将
内容转换的适配器。
扮演适配器角色的是PrintBanner类。该类继承了Banner类并实现了“需求”——Print
接口。PrintBanner类使用showWithParen方法实现了printWeak,使用showwithAster
方法实现了printstrong。这样,PrintBanner类就具有适配器的功能了。
类的对应图
使用了类适配器的示例程序的类图(继承)
示例代码
像上面举例的笔记本电脑的例子
- banner类 就像是输出 100伏特电压的插座口
- print接口 就像是输出12伏特电压的输出口
- printBanner类就像适配器一样,让我们只需要调用print接口就可以拿到banner类的结果
Banner类
作为基类,有提供输出两种不同内容的方法,
public class Banner {
private String string;
public Banner(String string) {
this.string = string;
}
public void showWithParen(){
System.out.println("("+string+")");
}
public void showWithAster(){
System.out.println("&"+string+"&");
}
}
Print接口
可以理解为 适配器的一端 提供两种输出方式
public interface Print {
public abstract void printWeak();
public abstract void printStrong();
}
printBanner类
作为适配器 让需要使用到类只需要调用 print 就可以拿到 banner的结果
public class printBanner extends Banner implements Print {
public printBanner(String string) {
super(string);
}
@Override
public void printWeak() {
showWithParen();
}
@Override
public void printStrong() {
showWithAster();
}
}
测试用例
public static void main(String[] args) {
Print printBanner = new printBanner("hello");
printBanner.printWeak();
printBanner.printStrong();
}
总结
在main方法中 我们使用到时print 接口,对于main来说 banner对于我们来说是完全隐藏起来的,就像是笔记本只要能在直流12伏特电压下就可以正常工作,他并不知道这12伏特是由适配器将100伏特电压转换而成的。
main类是不知道printBanner类是如何实现的,这样就可以在不用对main类进行修改的情况下,改变printBanner类
使用委托方式的适配器模式
上一个示例我们用类适配器模式了,下面我们再来看看对象适配器模式,在之前的示例程序中,我们用继承实现适配,在这里我们用 “委托” 来实现
PS: 委托可以理解为 把事情交给别人干
类图
类图也是发生了变化 printbanner实例化了banner来做一些事情
示例
public class printBanner1 implements Print {
private Banner banner;
public printBanner1(String string) {
this.banner = new Banner(string);
}
@Override
public void printWeak() {
banner.showWithParen();
}
@Override
public void printStrong() {
banner.showWithAster();
}
}
只需要修改PrintBanner就可以了,这里没有采用继承而是直接生成了banner的示例去调用banner的方法
在Adapter适配器模式中的角色
- Target 对象 这个角色定义了所需要的方法 print 依照文章开头的例子来说 就是需要使用到12伏特电源
- Client 使用者 这个角色负责使用被适配后的内容,main 依照例子就是需要使用12伏特电源的电脑
- Adaptee 被适配 这个角色是一个持有既定方法的角色 依照例子就是一百伏特的插头,由banner扮演此角色,当被适配的角色和target角色方法相同,也就是家庭电压就是12伏特那么就不需要这个角色
- Adapter 适配器模式的主人公 pringBanner主要负责兼具两侧的功能作为混合,用Adaptee的角色来满足target的要求,这就是适配器模式的目的
拓展思路
什么时候我们需要用上Adapter模式
如果某个方法就是我们所需要的方法,那么直接在程序中使用不就可以了? 很多时候进场会用到的类
实际上,我们在让现有的类适配新的接口时,常常会有 “只要将这里稍微修改一下就可以了” 的想法,一不留神就会修改现有代码,需要注意的是,如果要对已经测试完毕的现有代码进行修改,就必须要在修改后重新测试
Adapter 模式会对现有的类进行适配,生成新的类,通过该模式很方便创建我们需要的方法区,当出现bug的时候,我们明确的知道bug不在现有的类(Apdaptee 角色)中,所以只需要调查Adapter角色的类即可,这样代码的可维护性也提高了
如果没有线程的代码
让现有类适配新的接口时,使用适配器类貌似是一种很不错的解法,但是实际上我们遇到现有类适配新接口时,尝尝会有“只要修改一下就可以使用到”的想法,就会修改现有代码,需要注意的是如果要对已经测试完毕的代码进行修改就会又要测试
兼容性与升级性
软件的生命周期总是版本的升级,当版本升级的时候经常会出现与旧版本的兼容性问题,如果能够完全抛弃旧版本,那么软件维护起来也会轻松一些,但是在日常开发中往往无法抛弃旧版本,这个时候就可以使用适配器版本来做新旧版本的兼容
假设我们今后指向维护新版本那么只需要维护新版本的Adaptee角色,旧版本扮演target角色
拓展示例
Q: 我们为什么用print来使用printbanner的功能 而不是直接用printbanner呢?
A:我们使用print的原因是只需要使用print里的方法,其实用哪一种都是一样,printbanner里的可能还有其他的方法,
拓展代码
请使用适配器模式编写一个将属性集合保存到文件中的FileProperties类中。
这里,我们假设代码的FileIO接口声明了FileProperties需要实现的方法,输入文件file.txt和输出文件newfile.txt的内容如下
输入文件
yaer= 1999
输出文件
day=21
yaer=2004
month=4
main方法为
public static void main(String[] args) {
FileIO f = new FileProperties();
try {
f.readFromFile("D:\\JavaEngineer\\DesignPatterns\\DesignPattern\\DesignPattern\\src\\main\\java\\hyc\\Adapter\\expand\\file.txt");
f.setValue("year","2004");
f.setValue("month","4");
f.setValue("day","21");
f.writeToFile("D:\\JavaEngineer\\DesignPatterns\\DesignPattern\\DesignPattern\\src\\main\\java\\hyc\\Adapter\\expand\\newfile.txt");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
fileio类
public interface FileIO {
void readFromFile(String filename) throws IOException;
void writeToFile(String filename) throws IOException;
void setValue(String key,String value);
String getValue(String key);
}
fileProperties 作为适配fileio和Properties的类
Properties为java.util下的Proerties工具类
public class FileProperties extends Properties implements FileIO {
@Override
public void readFromFile(String filename) throws IOException {
load(new FileInputStream(filename));
}
@Override
public void writeToFile(String filename) throws IOException {
store(new FileOutputStream(filename),"written by FileProperties");
}
@Override
public void setValue(String key, String value) {
setProperty(key,value);
}
@Override
public String getValue(String key) {
return getProperty(key);
}
}
file文件有上述内容 newfile为空 此时我们执行代码
运行结果