• Java 消息机制之回调详解


    1. 概述

    Java 中的回调机制是一个比较常见的机制,只是有可能在你的程序中使用得比较少,在一些大型的框架中回调机制随处可见。而在之前的博文《Java设计模式——观察者模式》及 Android 中对 ListView 的相关操作也有回调身影。本文就通过一些具体的实例,慢慢走近 Java 的回调机制。


    2. 版权说明

    著作权归作者所有。
    商业转载请联系作者获得授权,非商业转载请注明出处。
    本文作者:Q-WHai
    发表日期: 2016年4月24日
    本文链接:http://blog.csdn.net/lemon_tree12138/article/details/51231841
    来源:CSDN
    更多内容:分类 >> Thinking in java


    3. 目录


    4. 回调

    所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法。实际在使用的时候,也会有不同的回调形式,比如下面的这几种。

    4.1 同步回调

    这里我假设这样的一种情况。
    A 公司的总监 B 跟他的下属(项目经理 C)说要做一个调研,不过不用 C 自己亲力亲为。可以让经理 C 去安排他下面的程序员 D 去完成。
    经理 C 找到了程序员 D,并告诉他,现在要完成一个调研任务。并且把调研的结果告诉经理 C。如果有问题,还是要继续的。
    因为这里是 C 让 D 去做一件事情,之后 D 还是要将结果与 C 进行沟通。这样就是回调的模型了。下面是一般回调的类图:
    这里写图片描述

    首先我们要有一个回调的接口 CallbackInterface
    CallbackInterface.java

    public interface CallbackInterface {
        public boolean check(int result);
    }

    背景里,程序员 D 是要将结果与项目经理 C 进行沟通的,所以这里项目经理需要实现上面的回调接口:
    Manager.java

    public class Manager implements CallbackInterface {
    
        private Programmer programmer = null;
    
        public Manager(Programmer _programmer) {
            this.programmer = _programmer;
        }
    
        /**
         * 用于 Boss 下达的委托
         */
        public void entrust() {
            arrange();
        }
    
        // 进行安排下属进行 study 工作
        private void arrange() {
            System.out.println("Manager 正在为 Programmer 安排工作");
            programmer.study(Manager.this);
            System.out.println("为 Programmer 安排工作已经完成,Manager 做其他的事情去了。");
        }
    
        @Override
        public boolean check(int result) {
            if (result == 5) {
                return true;
            }
            return false;
        }
    
    }

    对于程序员 D 来说他需要持有一个经理 C 的引用,以便与他沟通。不过,这里是总监 B 让 经理 C 去安排的任务。也就是说这里也可以让其他的经理,比如说经理 B1, B2等等。因为经理都实现了回调的接口,所以这里就可以直接让程序员 D 持有这个接口就可以了。如下:
    Programmer.java

    public class Programmer {
    
        public void study(CallbackInterface callback) {
            int result = 0;
            do {
                result++;
                System.out.println("第 " + result + " 次研究的结果");
            } while (!callback.check(result));
    
            System.out.println("调研任务结束");
        }
    }

    对于总监来说就更简单明了了,因为这相当于一个 Client 测试:
    Boss.java

    public class Boss {
    
        public static void main(String[] args) {
            Manager manager = new Manager(new Programmer());
            manager.entrust();
        }
    }

    运行结果

    Manager 正在为 Programmer 安排工作
    第 1 次研究的结果
    第 2 次研究的结果
    第 3 次研究的结果
    第 4 次研究的结果
    第 5 次研究的结果
    调研任务结束
    为 Programmer 安排工作已经完成,Manager 做其他的事情去了。

    4.2 异步回调

    还是上面的例子,你的项目经理不可能要一直等你调研的结果。而是把这个任务交给你之后,他就不管了,他做他的,你做你的。所以,这里需要对回调的函数进行异步处理。
    所以,这里我们需要修改 Programmer 类的代码,修改如下:
    Programmer.java

    public class Programmer {
    
        public Programmer() {
        }
    
        public void study(CallbackInterface callback) {
            new StudyThread(callback).start();
        }
    
        // --------------------------- Programmer 正在做的工作 ---------------------------
    
        class StudyThread extends Thread {
    
            CallbackInterface callback = null;
    
            public StudyThread(CallbackInterface _callback) {
                callback = _callback;
            }
    
            @Override
            public void run() {
                int result = 0;
                do {
                    result++;
                    System.out.println("第 " + result + " 次研究的结果");
                } while (!callback.check(result));
    
                System.out.println("调研任务结束");
            }
        }
    }

    运行结果

    Manager 正在为 Programmer 安排工作
    为 Programmer 安排工作已经完成,Manager 做其他的事情去了。
    第 1 次研究的结果
    第 2 次研究的结果
    第 3 次研究的结果
    第 4 次研究的结果
    第 5 次研究的结果
    调研任务结束

    4.3 闭包与回调

    闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。

    这一部分的内容主要是参见于《Java 编程思想》一书,具体细节大家可以参见此书。
    可能很多人在阅读《Java 编程思想》的时候有一些小迷糊,迷糊的原因可能多种多样。只是书中的代码展示部分的确有一些头痛,没有什么结构可言(这个只是我个人的阅读习惯,无关书籍本身的经典性),所以阅读起来不是很给力吧。下面就我阅读时的一个小总结。

    4.3.1 普通调用

    首先,我们可以看看在正常情况下的调用是怎么进行的。
    Incrementable.java

    interface Incrementable {
        void increment();
    }

    这是一个普通的接口(在普通调用里只是普通接口,在回调中就是回调接口,这一点应该很好理解吧)。

    Callee1.java

    class Callee1 implements Incrementable {
    
        private int i = 0;
    
        @Override
        public void increment() {
            i++;
            System.out.println(i);
        }
    
    }

    Callbacks.java

    public class Callbacks {
        public static void main(String[] args) {
            Callee1 callee1 = new Callee1();
            callee1.increment();
        }
    }

    Callbacks 是一个测试客户端类,没啥好说的,直接看上面的代码。

    4.3.2 回调初试

    上面的普通调用也没啥好说的,因为这对于一个正常的 Java 程序员来说都应该是想都不用想就可以搞定的事情。
    现在如果要构成回调,那么对于程序的结构或是逻辑的思维上都不可能只有一个被调用者(被回调的对象 Callee1),还需要一个调用者对象。调用者可以像下面这样来编写:
    Caller.java

    class Caller {
    
        private Incrementable callbackReference;
    
        public Caller(Incrementable _callbackReference) {
            callbackReference = _callbackReference;
        }
    
        void go() {
            callbackReference.increment();
        }
    }

    这里 Caller 持有一个回调接口的引用 callbackReference,就像在上面说到的程序员需要持有一个项目经理的引用,这样就可以通过这个引用来与项目经理沟通。这里的 callbackReference 也正是起到了这个作用。
    现在我们来看看测试类的编写:
    Callbacks.java

    public class Callbacks {
        public static void main(String[] args) {
            Callee1 callee1 = new Callee1();        
            Caller caller1 = new Caller(callee1);
            caller1.go();
        }
    }

    对于到目前为止的程序代码,完全可以对比上面项目经理安排程序员调研技术难题的代码。有异曲同工之妙。

    4.3.3 闭包回调

    相比于正常的回调,闭包回调的核心自然是在于闭包,也就是对作用域的控制。
    现在假设有一个用户(其他程序员)自定义了一个 MyInCrement 类,同时包含了一个 increment 的方法。如下:

    class MyInCrement {
    
        public void increment() {
            System.out.println("MyCrement.increment");
        }
    
        static void f(MyInCrement increment) {
            increment.increment();
        }
    }

    另外有一个类 Callee2 继承自上面这个类:

    class Callee2 extends MyInCrement {
    
        private int i = 0;
    
        public void increment() {
            super.increment();
            i++;
            System.out.println(i);
        }
    }

    显而易见这里如果要调用 increment() 方法,就变成了一般的函数调用了。所以这里我们需要修改上面的 Callee2 类,修改的目标就是让 Callee2 类可以兼容 MyInCrement 类的 increment() 方法和 Incrementable 的 increment() 方法。修改后:

    class Callee2 extends MyInCrement {
    
        private int i = 0;
    
        public void increment() {
            super.increment();
            i++;
            System.out.println(i);
        }
    
        private class Closure implements Incrementable {
    
            @Override
            public void increment() {
                Callee2.this.increment();
            }
        }
    
        Incrementable getCallbackReference() {
            return new Closure();
        }
    }

    注意,这里的 Closure 类是一个私有的类,这是一个闭包的要素。因为 Closure 类是私有的,那么就要有一个对外开放的接口,用来对 Closure 对象的操作,这里就是上面的 getCallbackReference() 方法。 Caller 类则没有改变。
    对于测试客户端就直接看代码吧:

    public class Callbacks {
        public static void main(String[] args) {        
            Callee2 callee2 = new Callee2();
            Caller caller2 = new Caller(callee2.getCallbackReference());
            caller2.go();
        }
    }

    5. Ref


  • 相关阅读:
    RS485通信和Modbus协议(转)
    Modbus通讯错误检测方法
    Modbus消息帧
    Modbus通讯两种传输方式
    echarts自定义图例legend文字和样式
    host文件的作用
    webpack --watch和supervisor的不同
    vue中如何使用echarts
    Vue父子组件生命周期
    Git总结笔记
  • 原文地址:https://www.cnblogs.com/fengju/p/6335993.html
Copyright © 2020-2023  润新知