Java设计模式:Callback

news2025/1/16 1:50:37

介绍

回调(Callback)是一种设计模式,在这种模式中,一个可执行的代码被作为参数传递给其他代码,接收方的代码可以在适当的时候调用它。

在真实世界的例子中,当我们需要在任务完成时被通知时,我们可以将一个回调方法传递给调用者,并等待它调用以通知我们。简单地说,回调是一个传递给调用者的方法,在定义的时刻被调用。

维基百科说

在计算机编程中,回调又被称为“稍后调用”函数,可以是任何可执行的代码用来作为参数传递给其他代码;其它代码被期望在给定时间内调用回调方法。

代码

回调是一个只有一个方法的简单接口。

public interface Callback {

  void call();
}

下面我们定义一个任务它将在任务执行完成后执行回调。

public abstract class Task {

  final void executeWith(Callback callback) {
    execute();
    Optional.ofNullable(callback).ifPresent(Callback::call);
  }

  public abstract void execute();
}

public final class SimpleTask extends Task {

  private static final Logger LOGGER = getLogger(SimpleTask.class);

  @Override
  public void execute() {
    LOGGER.info("Perform some important activity and after call the callback method.");
  }
}

最后这里是我们如何执行一个任务然后接收一个回调当它完成时。

var task = new SimpleTask();
task.executeWith(() -> LOGGER.info("I'm done now."));

类图 

适用场景

回调模式适用于以下场景:

  1. 异步操作:当需要在异步操作完成后执行某些操作时,可以使用回调模式。例如,在网络请求中,可以传递一个回调函数,在请求完成后调用该函数处理响应数据。
  2. 事件处理:当需要对事件进行响应和处理时,可以使用回调模式。例如,在图形界面开发中,可以注册某个控件的回调函数,以便在用户触发事件时执行相应的操作。
  3. 插件扩展:当需要为应用程序提供扩展性,允许第三方插件在特定事件发生时进行自定义操作时,可以使用回调模式。例如,游戏引擎中的事件系统允许开发者注册回调函数以响应游戏中的特定事件。
  4. 回调链:当需要按特定顺序执行多个回调函数,并将前一个回调函数的结果传递给下一个回调函数时,可以使用回调模式。这种情况下,回调函数形成了一个回调链。
  5. 模板方法模式:回调模式常与模板方法模式结合使用。模板方法模式定义了一个算法的骨架,而具体的步骤由子类实现。可以使用回调模式将子类中的具体步骤作为回调函数传递给模板方法。

总的来说,回调模式适用于需要在特定事件发生后执行某些操作的情况,以及需要实现解耦和灵活性的场景。它提供了一种在代码间通信的方式,使得代码可以更加模块化和可复用。

Java例子

  • CyclicBarrier 构造函数可以接受回调,该回调将在每次障碍被触发时触发。

FAQ

回调模式如何实现解耦和灵活性?

回调模式通过将一个可执行的代码块(回调函数)作为参数传递给其他代码,实现了解耦和灵活性。

  • 解耦性:回调模式可以将调用方与被调用方解耦,使它们之间的关系更加松散。调用方只需要知道回调函数的接口,而不需要了解具体的实现细节。被调用方在特定的时机调用回调函数,而不需要知道调用方的具体实现。这种解耦性使得系统中的不同部分可以独立地进行修改和扩展,而不会对彼此产生过多的依赖。
  • 灵活性:回调模式提供了一种灵活的扩展机制。通过传递不同的回调函数,可以改变程序的行为或逻辑,而不需要修改原有的代码。这种灵活性使得系统可以适应不同的需求和变化,而不需要进行大规模的修改或重构。同时,回调模式也允许在运行时动态地修改回调函数,从而实现更高级的动态行为。

通过使用回调模式,系统的不同部分可以相互独立地演化和扩展,而不会引入过多的紧耦合关系。这使得代码更加模块化、可复用和可维护。此外,回调模式还可以提高代码的可测试性,因为可以使用模拟或替代的回调函数来进行单元测试。

总而言之,回调模式通过解耦和灵活性的特性,帮助提高了代码的可维护性、可扩展性和可测试性,使系统更加灵活和适应变化。

回调模式和事件驱动模式有什么区别?

回调模式和事件驱动模式是两种常见的设计模式,它们在某些方面有相似之处,但也存在一些区别。

回调模式:

  • 在回调模式中,一个可执行的代码块(回调函数)被传递给其他代码,以便在特定事件发生时被调用。
  • 回调函数通常由调用方提供,用于定义在特定事件发生时应该执行的操作。
  • 回调模式用于实现解耦和灵活性,允许不同模块之间通过回调函数进行通信,但不依赖于具体的实现细节。

事件驱动模式:

  • 事件驱动模式是一种编程范式,其中系统的行为和控制是由事件的发生和处理驱动的。
  • 在事件驱动模式中,组件(如控件、对象等)可以产生事件,并将其发送到事件处理程序进行处理。
  • 事件处理程序是事先定义好的,用于响应特定类型的事件。
  • 事件驱动模式通常涉及事件的发布、订阅和分发机制,以便将事件路由到正确的处理程序。

区别:

  1. 角色和通信方式:在回调模式中,回调函数是被调用方提供给调用方的,通过函数参数进行传递。而在事件驱动模式中,组件产生事件并将其发送给事件处理程序进行处理。
  2. 控制流:在回调模式中,调用方主动调用回调函数来传递控制权,以响应特定事件。而在事件驱动模式中,控制流是由事件的发生和处理驱动的,事件处理程序被动地等待事件的发生。
  3. 灵活性和扩展性:回调模式更加灵活,因为可以将不同的回调函数传递给相同的调用方,从而改变其行为。而事件驱动模式更加适用于大型系统,因为可以通过添加、移除或替换事件处理程序来扩展系统的功能。
  4. 通信机制:回调模式通常使用函数参数进行通信,而事件驱动模式通常使用发布-订阅或观察者模式来实现事件的传递和处理。

需要注意的是,回调模式和事件驱动模式并不是互斥的,它们可以同时存在于一个系统中,相互配合使用来实现不同的需求。

回调模式和观察者模式有什么区别?

回调模式和观察者模式是两种常见的设计模式,它们在某些方面有相似之处,但也存在一些区别。

回调模式:

  • 在回调模式中,一个可执行的代码块(回调函数)被传递给其他代码,以便在特定事件发生时被调用。
  • 回调函数通常由调用方提供,用于定义在特定事件发生时应该执行的操作。
  • 回调模式用于实现解耦和灵活性,允许不同模块之间通过回调函数进行通信,但不依赖于具体的实现细节。

观察者模式:

  • 观察者模式是一种发布-订阅模式,用于在对象之间建立一对多的依赖关系。当一个对象的状态发生变化时,它会通知所有依赖于它的观察者对象。
  • 观察者模式通常由一个主题(被观察者)和多个观察者组成。主题维护观察者列表,并在状态变化时通知观察者。
  • 观察者模式用于实现对象之间的松耦合,使得主题和观察者可以独立变化,而不会相互影响。

区别:

  1. 角色和通信方式:在回调模式中,回调函数是被调用方提供给调用方的,通过函数参数进行传递。而在观察者模式中,主题通常维护观察者列表,并通过通知方法将状态变化信息传递给观察者。
  2. 控制流:在回调模式中,调用方主动调用回调函数来传递控制权,以响应特定事件。而在观察者模式中,主题对象在状态变化时被动地通知观察者,并由观察者决定如何处理通知。
  3. 关注点:回调模式更关注于事件发生后的回调操作。观察者模式更关注于主题和观察者之间的状态变化通知和处理。
  4. 依赖关系:在回调模式中,调用方和被调用方之间存在直接依赖关系,因为回调函数是由调用方提供的。而在观察者模式中,主题和观察者之间松耦合,它们只通过接口进行通信,不直接依赖于具体的实现。

需要注意的是,回调模式和观察者模式可以根据具体的应用场景进行选择和组合使用。在某些情况下,它们可以互为补充,实现更灵活和可扩展的系统设计。

使用回调模式,会存在内存泄露吗?

在Java中使用回调模式时,也存在潜在的内存泄漏问题。内存泄漏可能发生在以下情况下:

  1. 长期持有回调对象:如果一个对象持有一个回调对象的引用,并且该回调对象的生命周期比持有对象更长,那么即使持有对象不再使用,回调对象仍然保持对其的引用,从而导致内存泄漏。
  2. 匿名内部类回调:当使用匿名内部类作为回调对象时,如果匿名内部类引用了外部类的实例,且该实例的生命周期比回调对象更长,那么即使外部类实例不再需要,回调对象仍然保持对其的引用,导致内存泄漏。

使用回调模式,如何避免内存泄露?

以下是一些常见的方法来避免内存泄漏:

  • 及时释放对象引用:确保在不再需要对象时,显式地将其引用设置为null。这样可以使垃圾回收器能够回收对象所占用的内存。
SomeObject obj = new SomeObject();
// 使用obj对象...
obj = null; // 不再需要obj对象时,将其引用设置为null
  • 避免长期持有对象引用:当一个对象持有另一个对象的引用时,确保持有引用的对象的生命周期不比被引用对象更长。在不再需要持有对象时,及时将其引用设置为null。
    public class SomeClass {
        private Callback callback;
    
        public void setCallback(Callback callback) {
            this.callback = callback;
        }
    
        public void doSomething() {
            // 使用callback对象...
            callback = null; // 不再需要callback对象时,将其引用设置为null
        }
    }
    
  • 使用弱引用或软引用:对于某些情况下,当对象不再被强引用引用时,希望能够被垃圾回收,可以使用弱引用(WeakReference)或软引用(SoftReference)来持有对象。这样,在内存不足时,垃圾回收器可以回收这些对象。
    SomeObject obj = new SomeObject();
    WeakReference<SomeObject> weakRef = new WeakReference<>(obj);
    // 使用weakRef对象...
    obj = null; // 不再需要obj对象时,将其引用设置为null
    
    // 在适当的时机,检查弱引用是否还持有对象
    if (weakRef.get() == null) {
        // 对象已被垃圾回收
    }
    
  • 避免匿名内部类引用外部对象:在使用匿名内部类时,避免在内部类中引用外部类的实例,或者使用静态内部类来避免该问题。如果匿名内部类引用了外部类实例,并且外部类实例的生命周期比内部类更长,就会导致内存泄漏。
    public class SomeClass {
        public void doSomething() {
            final SomeObject obj = new SomeObject();
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    // 使用obj对象...
                }
            };
            // 使用runnable对象...
        }
    }
    

    在上述示例中,匿名内部类引用了外部类的SomeObject实例obj。如果在run()方法中持续引用了obj,那么即使doSomething()方法执行完毕,obj仍然无法被垃圾回收。为避免该问题,可以将SomeObject声明为final,或者使用静态内部类。

    • 1、在Java中,将SomeObject声明为final可以帮助避免匿名内部类引起的内存泄漏问题。

      当内部类引用外部类的实例时,如果外部类的实例不再需要,但内部类仍然持有对外部类实例的引用,就可能导致内存泄漏。

      当将SomeObject声明为final时,编译器会确保在匿名内部类中使用的外部类实例不可变。这意味着在编译时,编译器会将对外部类实例的引用复制给内部类的成员变量,并且该引用在整个内部类的生命周期中保持不变。

      由于引用是不可变的,因此不会出现外部类实例被内部类持有,从而导致外部类实例无法被垃圾回收的情况。一旦外部类实例不再被引用,即使匿名内部类仍然存在,外部类实例也可以被垃圾回收器回收。

      通过将SomeObject声明为final,可以确保在匿名内部类中对外部类实例的引用是安全的,不会导致内存泄漏问题。这是因为编译器在编译时会生成正确的代码,确保内部类不会持有外部类实例的引用超过其生命周期。

      需要注意的是,虽然使用final修饰外部类引用可以帮助避免内存泄漏问题,但这并不是解决所有可能导致内存泄漏的情况的通用解决方案。在处理回调或内部类时,还需要仔细考虑对象引用的生命周期,并采取适当的措施来避免潜在的内存泄漏。

    • 2、使用静态内部类可以帮助避免内部类引起的内存泄漏问题。

      静态内部类与外部类之间的引用是相互独立的,这意味着静态内部类不会隐式地持有对外部类实例的引用。

      当内部类是静态内部类时,它不会隐式地持有对外部类实例的引用。这意味着即使外部类实例不再被引用,静态内部类仍然可以独立存在,而不会阻止外部类实例被垃圾回收。

      由于静态内部类不持有对外部类实例的引用,因此在外部类实例不再需要时,可以安全地将其设置为null,并允许垃圾回收器回收内存。

      以下是使用静态内部类的示例:

      public class SomeClass {
          private static class CallbackImpl implements Callback {
              // 实现回调接口的方法
          }
      
          public void doSomething() {
              Callback callback = new CallbackImpl();
              // 使用callback对象...
              callback = null; // 不再需要callback对象时,将其引用设置为null
          }
      }
      

      在上述示例中,CallbackImpl是静态内部类,它实现了Callback接口。在doSomething()方法中,我们创建了CallbackImpl的实例,并使用它进行回调操作。当不再需要callback对象时,将其引用设置为null,以允许垃圾回收器回收内存。

      使用静态内部类可以有效地避免内存泄漏问题,因为它们不会持有对外部类实例的引用,从而使得外部类实例可以在不再需要时被垃圾回收。这使得静态内部类成为一种常见的处理回调或复杂逻辑的有效方式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1091906.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】从零开始学习Linux基本指令(一)

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;Linux入门 &#x1f525;该文章主要了解Linux操作系统下的基本指令。 目录&#xff1a; ⌛️指令的理解⏳目录和文件的理解⏳一些常见指令✉…

C++入门篇11 模板进阶

一、非类型模板参数 模板参数分为类型形参和非类型形参 类型形参&#xff1a;出现在模板参数列表里&#xff0c;跟在class/typename之后的参数类型名称非类型参数&#xff1a;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中可将参数当作常量来使用 …

SRAM电路设计

RAM是随机存取存储器&#xff08;random access memory&#xff09;&#xff0c;是计算机内部存储器中的一种&#xff0c;也是其中最重要的&#xff0c;计算机和手机中一般把其叫做&#xff08;运行&#xff09;内存&#xff0c;它的速度要比硬盘快得多&#xff0c;所以用运行程…

设计师都应该知道的事:极简主义家具该怎么去用

这座房子有黑暗而沉重的特征&#xff0c;包括棕色和白色的马赛克浴室瓷砖&#xff0c;弯曲的锻铁壁灯和土黄色的威尼斯石膏墙。但由于房屋与他们的风格相去甚远&#xff0c;白色&#xff0c;干净和简约&#xff0c;接下来我们就着这个方向去帮助房主进行改造。 她解释说&#x…

uniapp 小程序实现图片宽度100%、高度自适应的效果

因为image组件默认是有宽度跟高度的&#xff0c;所以这个高度不怎么好写 通过load事件来控制图片的高度 话不多说&#xff0c;直接上代码&#xff0c; <image class"img" src"/static/image.png" :style"{ height: imgHeight px }"mode&q…

【linux】E45: ‘readonly‘ option is set (add ! to override)

vim 编辑文件保存时 E45:设置了“只读”选项&#xff08;添加&#xff01;以覆盖&#xff09; 输入&#xff1a; wq! 提示 "/etc/my.cnf" E212: Cant open file for writing 依然是没有权限&#xff1a; 解决一&#xff1a; 切换用户&#xff1a; su root 解…

Elastic Cloud v.s. Zilliz Cloud:性能大比拼

Elastic Cloud v.s. Zilliz Cloud:性能大比拼 Zilliz 经常会收到来自开发者和架构师的提问:“Zilliz Cloud 和 Elastic Cloud 比起来,谁进行向量处理能力比较强?” 诸如此类的问题很多,究其根本,大都是开发者/架构师在为语义相似性检索系统进行数据库选型时缺少决策依据有…

网络层:常见的面试题和答案

1、什么是IPv4和IPv6&#xff1f;它们有什么区别&#xff1f; 答&#xff1a;IPv4是32位的IP地址格式&#xff0c;而IPv6是128位的IP地址格式。IPv4地址空间有限&#xff0c;而IPv6地址空间更大&#xff0c;可以提供更多的地址。 2、说说 HTTP 和HTTPS 的区别&#xff1f; H…

【C++】适配器模式 - - stack/queue/deque

目录 一、适配器模式 1.1迭代器模式 1.2适配器模式 二、stack 2.1stack 的介绍和使用 2.2stack的模拟实现 三、queue 3.1queue的介绍和使用 3.2queue的模拟实现 四、deque&#xff08;不满足先进先出&#xff0c;和队列无关&#xff09; 4.1deque的原理介绍 4.2dequ…

基于matomo实现业务数据埋点采集上报

matomo是一款Google-analytics数据埋点采集上报的平替方案&#xff0c;可保护您的数据和客户的隐私&#xff1b;正如它官网的slogan: Google Analytics alternative that protects your data and your customers privacy; 该项目源码开源免费&#xff0c;支持私有化部署&#x…

零基础Linux_17(进程间通信)VSCode环境安装+进程间通信介绍+pipe管道mkfifo

目录 1. VSCode环境安装 1.1 使用VSCode 1.2 远程链接到Linux机器 1.3 VSCode调试 2. 进程间通讯介绍 2.1 进程间通讯的概念和意义 2.2 进程间通讯的策略和本质 3. 管道 3.1 管道介绍 3.2 匿名管道介绍 3.3 匿名管道示例代码 3.3.1 建立管道的pipe 3.3.2 匿名管道…

YOLOv5算法改进(10)— 如何去添加多层注意力机制(包括代码+添加步骤+网络结构图)

前言:Hello大家好,我是小哥谈。注意力机制是近年来深度学习领域内的研究热点,可以帮助模型更好地关注重要的特征,从而提高模型的性能。注意力机制可被应用于模型的不同层级,以便更好地捕捉图像中的细节和特征,这种模型在计算资源有限的情况下,可以实现更好的性能和效率。…

二十一、【文本工具组】

文章目录 横排文本工具字符选项卡段落文字 横排文本工具 需要注意的是一些不是免费的商业字体&#xff0c;一定不要拿去使用&#xff0c;否则后边很容易会受到法律索赔。 在制作海报等一些图形时&#xff0c;需要经常用到文本工具我们需要对文本进行变形&#xff0c;分段&…

Qt开发工程师成系统性长体系教程

QT跨平台开发工程师必备技术栈 基础原理-案例分析-项目实战&#xff0c;紧跟QT开发岗位技术需求. 一、Qt C 语言编程基础专栏 1.1 Qt C 语言编程基础 Visual Studio 2022安装C语言基础概述C指针与引用C类与对象(一)C类与对象(二)类的基它特性构造函数&析构函数&拷贝…

rhel8 nmcli学习

25.3.1 配置动态IP连接 25.3.1.1 配置IP 要使用 DHCP 分配网络时&#xff0c;可以使用动态IP配置添加网络配置文件&#xff0c;命令格式如下&#xff1a; # nmcli connection add type ethernet con-name connection-name ifname interface-name 例如创建名为net-test的动态…

虚拟机的发展史:从分时系统到容器化

一、前世 早期计算机的价格非常昂贵&#xff0c;一台计算机可能需要花费几十万甚至上百万美元。例如&#xff0c;ENIAC计算机&#xff0c;作为世界上第一台通用电子数字计算机&#xff0c;当时的造价约为48万美元。科学家或者工程师们需要计算机的能力&#xff0c;但是买不起整…

C. JoyboardCodeforces Round 902

C. Joyboard 样例1列表找规律&#xff1a; #include<iostream> #define int long long using namespace std; signed main() {int T;cin>>T;while(T--){int n,m,k;cin>>n>>m>>k;if(k1){cout<<1<<endl;}else if(k2){cout<<m…

vue2时间处理插件——dayjs

在vue时间处理上有很多的方法和实现&#xff0c;可以自己实现&#xff0c;但是效率不高&#xff0c;所以&#xff0c;在框架开发中我们一般不会手写&#xff0c;一般是使用集成的第三方插件来解决我们的问题&#xff0c;在vue3中大家一般都使用Moment.js来处理&#xff0c;所以…

Defects4j数据集安装及使用

一、Defects4j数据集安装 在Ubuntu系统上进行操作&#xff0c;具体的在&#xff1a;Defects4j数据集安装 二、Defects4j数据集的使用 1. 常用命令 Getting started ---------------- #### Setting up Defects4J 1. Clone Defects4J:- git clone https://github.com/rjust/d…

JOSEF约瑟 可调漏电继电器RT-LB230KS+Q-FL-100 导轨安装 配套零序互感器

一、产品用途及特点 RT-LB230KS漏电继电器&#xff08;以下简称继电器&#xff09;适用于交流电压为660V.至1140V电压系统中,频率为50Hz,电流15~4000A线路中做有无中性点漏电保护. 该继电器可与带分励脱扣器或失压脱扣器的断路器、交流接触器、磁力启动器等组成漏电保护装置&…