Как Java реализует Callback Hell?

Java

Все знакомы с Callback Hell, особенно для внешних друзей.Конечно, внешние друзья используют различные методы, чтобы избежать Callback Hell, такие как Promise. Но для бэкенд-друзей, особенно после появления фреймворков реактивного программирования, таких как RxJava и Reactor, они только много слышат об аде обратных вызовов, но видят меньше.

Чтобы лучше понять проблему ада обратных вызовов, нам сначала нужно научиться писать ад обратных вызовов. Перед этим мы должны знать, что такое функция обратного вызова.

Эта статья будет содержать:

  • что такое обратный вызов
  • Преимущества обратного вызова
  • что такое ад обратного вызова
  • Почему происходит Callback Hell
  • В чем разница между обратным вызовом и будущим
  • Как исправить ад обратного вызова

Сегодня начнем с самого начала, поговорим о том, что такое функция обратного вызова.

Что такое функция обратного вызова?

В Википедии сказано:

Функция обратного вызова — это функция, вызываемая через указатель на функцию. Если вы передаете указатель (адрес) функции в качестве параметра другой функции, когда этот указатель используется для вызова функции, на которую он указывает, мы говорим, что это функция обратного вызова. Функция обратного вызова не вызывается непосредственно реализатором функции, а вызывается другой стороной, когда происходит определенное событие или условие, чтобы отреагировать на событие или условие. Обратный вызов — это любой метод, который вызывается другим методом, который принимает метод в качестве первого аргумента. Во многих случаях обратный вызов — это метод, который вызывается, когда происходит какое-либо событие.

Какие? Не понимают? Это действительно сложно понять, и в этом объяснении есть указатели и так далее, что действительно недружественно для пользователей Java.

Позвольте мне привести пример для справки и приветствовать критику и исправления:

Обратный вызов: после того, как вызывающая сторона звонит вызываемой стороне, вызываемая сторона также возвращает результат вызывающей стороне. (A вызывает B, и после завершения B результат возвращается обратно в A)

Например: начальник назначает сотруднику работу, а сотрудник ее выполняет. После того, как сотрудник завершит работу, сообщите о результатах работы начальнику. Этот процесс называется обратным вызовом.

回调示例
пример обратного вызова

Теперь это легче понять! Разговоры дешевы, покажите мне код! Что ж, давайте воспользуемся этим, чтобы написать простой пример.

пример обратного вызова

Интерфейс обратного вызова

Во-первых, сначала напишем следующееCallbackИнтерфейс, интерфейс содержит только один метод для операции обратного вызова.

/**
 * @author yangzijing
 */
public interface Callback<T> {

    /**
     * 具体实现
     * @param t
     */
    public void callback(T t);

}

Босс класс

Начальник — объект обратной связи, поэтому это нужно реализоватьCallbackЭтот интерфейс, перегруженныйcallbackМетод; как за то, что босс хочет сделать, конечно, это сделать большой бизнес, так естьmakeBigDealsМетод; конечно, босс не может быть голым командиром, ему нужен сотрудник, мы добавим ему сотрудника в методе строительстваWorker, мы реализуем класс Worker позже.

public class Boss implements Callback<String> {

    private Worker worker;

    public Boss(Worker worker) {
        this.worker = worker;
    }

    @Override
    public void callback(String s) {
    }

    public void makeBigDeals(final String someDetail) {
		worker.work(someDetail);
    }

}

Рабочий класс

Класс сотрудников, очень простой, входит и выходит из задания, завершает его и возвращает результат. Но как завершить обратный вызов?

public class Worker {
    public String work(String someWork) {
		return 'result';
    }
}

Мы можем легко придумать эту идею, что очень логично, но в обратном вызове нам нужно внести некоторые изменения.

Пусть код перезвонит

Сотрудникам необходимо знать две вещи: кто главный и что нужно делать. Итак, введите два параметра, а именно начальника и содержание работы. Конкретный контент разделен на два этапа: сначала выполнить задание, а затем доложить боссу.

public class Worker {
    public void work(Callback<String> boss, String someWork) {
		String result = someWork + 'is done!'; // 做一些具体的处理
		boss.callback(result); // 反馈结果给老板
    }
}

Далее мы завершаем класс Boss. существуетcallbackВ методе входящий результат принимается и обрабатывается, а здесь мы его только печатаем; вmakeBigDealsметод, начальник назначает работу, а сотрудник ее выполняет, еслиЗавершить процесс асинхронноэто异步调用,если синхронно, тогда同步回调, здесь мы используем асинхронный метод.

В новом потоке выполняемworker.work(Boss.this, someDetail)Boss.thisтекущий объект, здесь мы официально завершилиПерезвони.

public class Boss implements Callback<String> {
    ……
    @Override
    public void callback(String result) { // 参数为worker输出的结果
		logger.info("Boss got: {}", result) // 接到完成的结果,并做处理,在这里我们仅打印出来
	}

    public void makeBigDeals(final String someDetail) {
		logger.info("分配工作");
		new Thread(() -> worker.work(Boss.this, someDetail)); // 异步完成任务
		logger.info("分配完成");
		logger.info("老板下班。。");
    }
}

результат обратного вызова

Покажите мне результат! Хорошо, давайте запустим код и попробуем.

Worker worker = new Worker();
Boss boss = new Boss(worker); // 给老板指派员工
boss.makeBigDeals("coding"); // 老板有一个代码要写

Результаты приведены ниже. Как видно из результатов, начальник покидает работу после назначения работы, а после ухода с работы другой поток информирует начальника о получении обратной связи «кодирование выполнено». Пока мы закончилиАсинхронный обратный вызоввесь процесс.

INFO  2019 九月 20 11:30:54,780 [main]  - 分配工作
 INFO  2019 九月 20 11:30:54,784 [main]  - 分配完成
 INFO  2019 九月 20 11:30:54,784 [main]  - 老板下班。。
 INFO  2019 九月 20 11:30:54,787 [Thread-0]  - Boss got: coding is done!

Я загружу образец кода на Github для справки.пример кода обратного вызова

Преимущество обратных вызовов

  • разъединение, обратный вызов будетподпроцессотосновной процессразвязка. Один и тот же ввод может обрабатываться по-разному. В функции обратного вызова мы завершаем основной процесс (такой как вышеBossкласс), для подпроцессов внутри процедуры (например, вышеWorkerкласс) из основного процесса. Для основного процесса нас интересуют только ввод и вывод подпроцесса, ввод в приведенном выше примереWorker.workпараметры в подпроцессе, а вывод подпроцесса является выводом основного процессаcallbackпараметры метода.
  • Асинхронный обратный вызовне блокируетосновной поток. Приведенный выше пример ясно показывает, что начальник уже ушел с работы до того, как сотрудник завершил работу, когда работа будет завершена, начальник будет уведомлен через другой поток. Боссу не нужно ждать дочернего процесса в этом процессе.

ад обратного звонка

общий дизайн

Мы расширяем вышеперечисленные функции, босс сначала передает работу продакт-менеджеру по дизайну, после завершения дизайна передает программисту для завершения кодирования. Схема процесса представлена ​​на рисунке.

回调地狱
ад обратного звонка

Поставьте задачу продакт-менеджеру

Во-первых, напишите Обратный звонок, новый внутренний менеджер продуктаWorker,существуетmakeBigDealМетод реализует основную задачу и передает задачу продакт-менеджеру, в перегруженномcallbackметод, получить вывод менеджера по продукту.

new Callback<String>() {
            private Worker productManager = new Worker();
            @Override
            public void callback(String s) {
                System.out.println("产品经理 output: " + s); // 获取产品经理的输出
            }

            public void makeBigDeals(String bigDeal) {
                System.out.println("Boss将任务交给产品");
                new Thread(() -> {
                    this.productManager.work(this, bigDeal); // 异步调用产品经理处理过程
                }).start();
            }
        }.makeBigDeals("design");

Затем передайте результат менеджера по продукту в разработку.

После получения результата менеджера по продукту, результат передается в разработку. Итак, мы снова реализуемCallbackинтерфейс. Так же,Callback, разработан новыйWorker,существуетcodingметод, вызовWorkerразвиваться; в перегруженномcallbackметод, получить результат после обработки проявления.

@Override
public void callback(String s) {
	System.out.println("产品经理 output: " + s); // 产品经理的输出
	String midResult = s + " coding";
	System.out.println("产品经理设计完成,再将任务交给开发");
	new Callback<String>() {
		private Worker coder = new Worker();

		@Override
		public void callback(String s) {
			System.out.println("result: " + s); // 获取开发后的结果
		}

		public void coding(String coding) {
			new Thread(() -> coder.work(this, coding)).start(); // 调用开发的Worker进行开发
		}
	}.coding(midResult); // 将产品经理的输出交给开发
}

полная реализация

new Callback<String>() {
            private Worker productManager = new Worker();
            @Override
            public void apply(String s) {
                System.out.println("产品经理 output: " + s);
                String midResult = s + " coding";
                System.out.println("产品经理设计完成,再将任务交给开发");
                new Callback<String>() {
					private Worker coder = new Worker();
                    @Override
                    public void apply(String s) {
                        System.out.println("result: " + s);
                    }
                    public void coding(String coding) {
                        new Thread(() -> coder.work(this, coding)).start();
                    }
                }.coding(midResult);
            }

            public void makeBigDeals(String bigDeal) {
                System.out.println("Boss将任务交给产品");
                new Thread(() -> this.productManager.work(this, bigDeal)).start();
            }
        }.makeBigDeals("design");

Что ж, простой ад обратного вызова готов. Покажи мне результат!

Boss将任务交给产品
产品经理 output: design is done!
产品经理设计完成,再将任务交给开发
result: design is done! coding is done!

Что приносит ад обратного вызова?

Что такое ад обратного вызова? Проще говоря, ад обратного вызова — это еще один обратный вызов внутри обратного вызова, но если слоев вложенности слишком много, кажется, что он попадает в ад, поэтому существует поговорка об аде обратного вызова.

Преимущество:Что нам приносит ад обратного звонка? По факту,Код обратного вызова похож на трубу,Получите входные данные и выведите обработанный контент на следующий шаг.而回调地狱,则是多个管道连接,形成的一个流程,而各个子流程(管道)相互独立。前端的朋友可能会更熟悉一些,例如Promise.then().then().then()Формируется множество технологических каналов.

Недостаток:Хотя метод обратного вызова отделяет подпроцесс, удобочитаемость кода обратного вызова снижается, а сложность значительно возрастает.

Пример Callback Hell:Callback Hell

Сравните с будущим

Выше мы упомянули, что асинхронные обратные вызовы не будут блокировать основной поток, поэтому использование Future не будет блокировать.В чем разница между асинхронными обратными вызовами и асинхронными обратными вызовами?

Давайте напишем пример, который использует будущее, чтобы позвонить асинхронно:

logger.info("分配工作...");
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> worker.work(someDetail));
logger.info("分配完工作。");
logger.info("老板下班回家了。。。");
logger.info("boss got the feedback from worker: {}", future.get());

В приведенном выше коде мы видим, что хотяWorkerРабота асинхронная, но босс получает результат работы (future.get()), ему нужно подождать, и этот процесс ожидания заблокирован. Это существенное различие между обратными вызовами и фьючерсами.

Сравнение обратных вызовов и фьючерсов:Сравнение обратного вызова и будущего

Как решить

Как решить проблему callback hell, чаще всего используется реактивное программированиеRxJavaиReactor, и сопрограмма Kotlin Coroutine, которая выполняется OpenJDK.Project Loom. Каждый из них имеет свои преимущества, которые перечислены ниже.

Суммировать

в заключении:

  1. что такое обратный вызов.回调是调用方在调用被调方后,被调方还将结果反馈给调用方。 (A вызывает B, и после завершения B результат возвращается обратно в A)
  2. Преимущество обратных вызовов. 1) Разделение подпроцесса и основного процесса. 2) Вызывается асинхронно и не блокирует основной поток.
  3. что такое ад обратного вызова. Ад обратного вызова заключается в том, что функции обратного вызова вложены в несколько слоев, так много, что вы не можете ясно видеть =. знак равно
  4. Почему происходит Callback Hell. Каждый обратный вызов похож на конвейер, принимающий выходные данные и выводящий результат в следующий конвейер после обработки. Процесс обработки каждого конвейера независим, и несколько конвейеров формируют весь процесс обработки.
  5. В чем разница между обратным вызовом и будущим. 1)两者机制不同;2)Future在等待结果时会阻塞,而回调不会阻塞。
  6. Как исправить ад обратного вызова. Наиболее распространенными являются реактивное программирование RxJava и Reactor.