Java设计模式:Callback

介绍

回调(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);@Overridepublic 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() {@Overridepublic 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.rhkb.cn/news/158833.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt工具开发,该不该跳槽?

Qt工具开发&#xff0c;该不该跳槽? 就这样吧&#xff0c;我怕你跳不动。 嵌入式UI&#xff0c;目前趋势是向着LVGL发展。QT已经在淘汰期了。很多项目还在用&#xff0c;但技术上已经落后。QT短期内不会全面淘汰&#xff0c;但退位让贤的大趋势已经很清楚了。 最近很多小伙伴…

springboot 志同道合交友网站演示

springboot 志同道合交友网站演示 liu1113625581

百度开放平台第三方代小程序开发,授权事件、消息与事件通知总结

大家好&#xff0c;我是小悟 关于百度开放平台第三方代小程序开发的两个事件接收推送通知&#xff0c;是开放平台代小程序实现业务的重要功能。 授权事件推送和消息与事件推送类型都以event的值判断。 授权事件推送通知 授权事件推送包括&#xff1a;推送票据、授权成功、取…

排序算法-希尔排序法(ShellSort)

排序算法-希尔排序法&#xff08;ShellSort&#xff09; 1、说明 我们知道当原始记录的键值大部分已排好序的情况下插入排序法非常有效&#xff0c;因为它不需要执行太多的数据搬移操作。希尔排序法是D.L.Shell在1959年7月发明的一种排序法&#xff0c;可以减少插入排序法中数…

微信小程序入门

目录 一.微信小程序简介 二. 什么是小程序&#xff1f; 三.小程序商城项目案例 四.小程序之入门案例 json配置 wxml模板 wxss样式 js逻辑交互 测试结果 一.微信小程序简介 微信小程序是一种运行在微信客户端内的应用程序&#xff0c;它以小巧、轻量、便捷的特点受到广…

androidx.appcompat.widget.Toolbar最右边设置控件不能仅靠最右边

androidx.appcompat.widget.Toolbar最右边设置控件不能仅靠最右边 Android Toolbar左、中、右对齐-CSDN博客&#xfeff;&#xfeff;Android Toolbar左、中、右对齐默认的Android Toolbar中添加子元素view是从左到右依次添加。需要注意的是&#xff0c;Android Toolbar为自身的…

推荐几款简单易用的协作化项目管理工具

您是否正在寻找一种有效且简单的项目管理工具来帮助您与团队成员协作?项目管理工具在当今的商业世界中已经变得必不可少&#xff0c;因为它们帮助团队保持组织和生产力。找到合适的工具是困难的&#xff0c;因为有太多的选择。有些工具是为特定类型的项目设计的&#xff0c;而…

腾讯云国际站-阿里云OSS如何迁移到腾讯云COS?腾讯云cos迁移教程

下面小编将介绍当源对象存储部署在阿里云国际版OSS 时&#xff0c;如何配置全托管迁移任务和半托管迁移任务&#xff0c;实现顺利迁移数据至腾讯云国际版COS。 准备工作 阿里云对象存储 OSS 创建 RAM 子账号并授予相关权限&#xff1a; 登录 RAM 控制台。选择人员管理 > …

二分查找:如何用最省内存的方式实现快速查找功能?

文章来源于极客时间前google工程师−王争专栏。 有序数据集合的查找算法&#xff1a;二分查找(Binary Search)算法&#xff0c;也叫折半查找算法。二分查找的思想非常简单&#xff0c;但是难掌握好&#xff0c;灵活运用更加困难。 问题&#xff1a;假设有1000万个整数数据&…

S32K1xx的MBD工具箱加载及激活

1、安装Matlab&#xff0c;本次使用Matlab2022b 2、打开Matlab&#xff0c;加载含有MBD工具的目录&#xff0c;如下 3、双击第一个---安装&#xff0c;正常安装就可以 4、双击第二个---安装&#xff0c;正常安装就可以 5、找到MBD的安装位置如下 C:\Users\Administrator\App…

应用在汽车发动机温度检测中的高精度温度传感芯片

汽车发动机是为汽车提供动力的装置&#xff0c;是汽车的心脏&#xff0c;决定着汽车的动力性、经济性、稳定性和环保性。根据动力来源不同&#xff0c;汽车发动机可分为柴油发动机、汽油发动机、电动汽车电动机以及混合动力等。 常见的车用温度传感器有进气温度传感器、变速器…

C++智能指针(三)——unique_ptr初探

与共享指针shared_ptr用于共享对象的目的不同&#xff0c;unique_ptr是用于独享对象。 文章目录 1. unqiue_ptr的目的2. 使用 unique_ptr2.1 初始化 unique_ptr2.2 访问数据2.3 作为类的成员2.4 处理数组 3. 转移所有权3.1 简单语法3.2 函数间转移所有权3.2.1 转移至函数体内3.…

Puppeteer实现上下滚动、打开新Tab、用户数据保存(三)

Puppeteer实现上下滚动、打开新Tab、用户数据保存&#xff08;三&#xff09; Puppeteer实现上下滚动、打开新Tab、用户数据保存&#xff08;三&#xff09;一、实现上下滚动二、打开新Tab三、用户数据保存四、效果演示 一、实现上下滚动 在自动化测试中&#xff0c;我们需要能…

Blender:使用立方体制作动漫头像

好久没水文章 排名都掉到1w外了 ~_~ 学习一下blender&#xff0c;看能不能学习一点曲面变形的思路 一些快捷键 ctrl 空格&#xff1a;区域最大化&#xff0c;就是全屏 ctrl alt 空格&#xff1a;也是区域最大化 shift b&#xff1a;框选区域然后最大化显示该范围 shift 空…

XGBoost+LR融合

1、背景简介 xgboostlr模型融合方法用于分类或者回归的思想最早由facebook在广告ctr预测中提出&#xff0c;其论文Practical Lessons from Predicting Clicks on Ads at Facebook有对其进行阐述。在这篇论文中他们提出了一种将xgboost作为feature transform的方法。大概的思想…

函数栈帧的创建与销毁(保姆级讲解)

局部变量是怎么创建的? 在为main函数开辟栈帧空间时&#xff0c;在一定范围内初始化成0CCCCC&#xff0c;再把里面0CCCC的一些开辟空间给局部变量使用。 为什么局部变量的值是随机值? 因为我们在为main函数开辟栈帧空间时&#xff0c;会将一定范围内空间初始成0CCCCCC里面…

【云原生】都在说云原生?到底什么是云原生?

文章目录 一、云原生是什么云原生云原生 二、云原生四要素微服务容器化DevOps 三、具体的云原生技术有哪些容器(Containers)微服务(Microservices)服务网格(Service Meshes)不可变基础设施(Immutable Infrastructure)声明式API(Deciarative API) 四、云服务器相对传统物理服务器…

小程序的入门

目录 小程序的简介 好处 安装及使用 小程序的入门案列 小程序的简介 微信小程序是一种轻量级的应用程序&#xff0c;可以在微信平台上运行。它们具有快速、便捷和低成本等特点。通过微信小程序&#xff0c;用户可以在微信内直接使用各种功能&#xff0c;而无需下载和安装额外…

python pip安装超时使用国内镜像

网络环境差的时候需要我们独立的进行相对应的包下载离线安装&#xff0c;或者给pip 加上 国内的镜像源比如加上清华的镜像源&#xff1a; 参考网址&#xff1a;pypi | 镜像站使用帮助 | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror https://mirrors.tuna.tsinghua…

SpringCloud-Hystrix

一、介绍 &#xff08;1&#xff09;避免单个服务出现故障导致整个应用崩溃。 &#xff08;2&#xff09;服务降级&#xff1a;服务超时、服务异常、服务宕机时&#xff0c;执行定义好的方法。&#xff08;做别的&#xff09; &#xff08;3&#xff09;服务熔断&#xff1a;达…