前言
这是我参与2022首次更文挑战的第9天,活动详情查看:2022首次更文挑战
大聪明在写代码的过程中发现设计模式的影子是无处不在,设计模式也是软件开发人员在软件开发过程中面临的一般问题的解决方案。大聪明本着“独乐乐不如众乐乐”的宗旨与大家分享一下设计模式的学习心得。
在现实生活中,我们经常会遇到两个对象因接口不兼容而不能在一起工作的情况,这时需要第三者进行适配。就像小日....子过的还不错的日本人需要一个翻译才能跟我们沟通一样。 在软件设计中也可能出现类似的问题,当我们需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。
适配器模式涉及3个角色:
源(Adaptee): 需要被适配的对象或类型,相当于小日...子过的还不错的日本人。 适配器(Adapter): 连接目标和源的中间对象,相当于翻译。 目标(Target): 期待得到的目标,相当于谈话的另一方了,也就是我们。 适配器模式包括3种形式:类适配器模式、对象适配器模式、接口适配器模式(也叫缺省适配器模式)。
还是老规矩,用代码分别讲解一下这三种模式。
类适配器模式
首先先定义一个源
/**
* 定义一个源
* @description: Adaptee
* @author: 庄霸.liziye
* @create: 2021-08-13 10:55
**/
public class Adaptee {
public void method1(){
System.out.println("这是方法一");
}
}
接下来定义一个目标
/**
* 定义一个目标
* @description: Target
* @author: 庄霸.liziye
* @create: 2021-08-13 10:56
**/
public interface Target {
void method1();
void method2();
}
最后再定义一个适配器
/**
* 适配器
* @description: Adapter
* @author: 庄霸.liziye
* @create: 2021-08-13 10:57
**/
public class Adapter extends Adaptee implements Target{
@Override
public void method2() {
System.out.println("这是方法二");
}
}
// 测试
class Test {
public static void main(String[] args) {
Adapter adapter = new Adapter();
adapter.method1();
adapter.method2();
}
}
我们可以看到Adaptee类中并没有method2()方法,但是测试类中则需要用到这个方法。为使测试类能够使用Adaptee类,我们把Adaptee与Target衔接起来。Adapter与Adaptee是继承关系,这就是一个类适配器模式。
对象适配器模式
“源”和“目标”和上面的写法是一样的,这里就直接定义一个适配器
/**
* 适配器
* @description: Adapter
* @author: 庄霸.liziye
* @create: 2021-08-13 10:57
**/
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void method1() {
adaptee.method1();
}
@Override
public void method2() {
System.out.println("这是方法二");
}
}
class Test {
public static void main(String[] args) {
Adapter adapter = new Adapter(new Adaptee());
adapter.method1();
adapter.method2();
}
}
同样的Adaptee类并没有method2()方法,但测试类则需要使用这个方法。与类适配器模式一样,为使客户端能够使用Adaptee类,我们把Adaptee与Target衔接起来。但这里我们不继承Adaptee,而是把Adaptee封装进Adapter里。也就是说Adaptee与Adapter是组合关系。
我们通过上面的代码可以看出类适配器和对象适配器比较像,但是也存在着一些区别:
- 第一点就是类适配器使用的是继承的方式,直接继承了Adaptee,所以无法对Adaptee的子类进行适配。
- 第二点是对象适配器使用的是组合的方式,所以Adaptee及其子孙类都可以被适配。另外,对象适配器在增加一些新的方法时是很方便的,而且新增加的方法同时适用于所有的源。
我们都知道组合/聚合优于继承的原则,我们根据这个原则也就很容易的看出来使用对象适配器更好一些。不过具体情况还需要具体分析,选择最适合的才是最好的。
接口适配器模式(缺省适配器模式)
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
* @description: test
* @author: 庄霸.liziye
* @create: 2021-08-13 11:19
**/
public class test {
public static void main(String[] args) throws CloneNotSupportedException {
JFrame frame = new JFrame();
frame.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent ke) {}
@Override
public void keyPressed(KeyEvent ke) {
System.out.println("hello word !!");
}
@Override
public void keyReleased(KeyEvent ke) {
}
});
}
}
java.awt.KeyListener是一个键盘监听器接口,我们把这个接口的实现类对象注册进容器后,这个容器就会对键盘行为进行监听。 其实我们只使用到其中一个方法,但必须要把接口中所有方法都实现一遍,如果接口里方法非常多,那岂不是猴子的妈妈——废废(狒狒)了。于是我们引入一个默认适配器,让适配器把接口里的方法都实现一遍,使用时继承这个适配器,把需要的方法实现一遍就好了。我们在对上面的代码改造一下。
/**
* @description: test
* @author: 庄霸.liziye
* @create: 2021-08-13 11:19
**/
public class test {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
System.out.println("hello world !");
}
});
}
}
这次代码看起来就简洁多了。在任何时候,如果不准备实现一个接口里的所有方法时,就可以使用“缺省适配模式”制造一个抽象类,实现所有方法,这样,从这个抽象类再继承下去的子类就不必实现所有的方法,只要重写需要的方法就可以了。
小结
适配器模式在源码中也有很多应用,比如JDK源码的IO模块【例如 java.io.InputStreamReader(InputStream)、java.io.OutputStreamWriter(OutputStream)】;mybatis源码日志模块用到对象适配器模式。
适配器模式可以让我们的代码具有更好的复用性和扩展性,但是如果滥用适配器模式的话,则会让我们的代码变得非常零乱,例如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。所以使用适配器模式的时候要谨慎一些,不是必须情况的时候就最好选择其他方式来解决问题。
本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨🙇
希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●'◡'●)
如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。
爱你所爱 行你所行 听从你心 无问东西